Module pyboy.api.memory_scanner
Expand source code
from enum import Enum
from pyboy.utils import bcd_to_dec
class StandardComparisonType(Enum):
"""Enumeration for defining types of comparisons that do not require a previous value."""
EXACT = 1
LESS_THAN = 2
GREATER_THAN = 3
LESS_THAN_OR_EQUAL = 4
GREATER_THAN_OR_EQUAL = 5
class DynamicComparisonType(Enum):
"""Enumeration for defining types of comparisons that require a previous value."""
UNCHANGED = 1
CHANGED = 2
INCREASED = 3
DECREASED = 4
MATCH = 5
class ScanMode(Enum):
"""Enumeration for defining scanning modes."""
INT = 1
BCD = 2
class MemoryScanner():
"""A class for scanning memory within a given range."""
def __init__(self, pyboy):
self.pyboy = pyboy
self._memory_cache = {}
self._memory_cache_byte_width = 1
def scan_memory(
self,
target_value=None,
start_addr=0x0000,
end_addr=0xFFFF,
standard_comparison_type=StandardComparisonType.EXACT,
value_type=ScanMode.INT,
byte_width=1,
byteorder="little"
):
"""
This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included).
Example:
```python
>>> current_score = 4 # You write current score in game
>>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
[]
```
Args:
start_addr (int): The starting address for the scan.
end_addr (int): The ending address for the scan.
target_value (int or None): The value to search for. If None, any value is considered a match.
standard_comparison_type (StandardComparisonType): The type of comparison to use.
value_type (ValueType): The type of value (INT or BCD) to consider.
byte_width (int): The number of bytes to consider for each value.
byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details.
Returns:
list of int: A list of addresses where the target value is found.
"""
self._memory_cache = {}
self._memory_cache_byte_width = byte_width
for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr
# Read multiple bytes based on byte_width and byteorder
value_bytes = self.pyboy.memory[addr:addr + byte_width]
value = int.from_bytes(value_bytes, byteorder)
if value_type == ScanMode.BCD:
value = bcd_to_dec(value, byte_width, byteorder)
if target_value is None or self._check_value(value, target_value, standard_comparison_type.value):
self._memory_cache[addr] = value
return list(self._memory_cache.keys())
def rescan_memory(
self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little"
):
"""
Rescans the memory and updates the memory cache based on a dynamic comparison type.
Example:
```python
>>> current_score = 4 # You write current score in game
>>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF)
[]
>>> for _ in range(175):
... pyboy.tick(1, True) # Progress the game to change score
True...
>>> current_score = 8 # You write the new score in game
>>> from pyboy.api.memory_scanner import DynamicComparisonType
>>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH)
>>> print(addresses) # If repeated enough, only one address will remain
[]
```
Args:
new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used.
dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED.
Returns:
list of int: A list of addresses remaining in the memory cache after the rescan.
"""
for addr, value in self._memory_cache.copy().items():
current_value = int.from_bytes(
self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder
)
if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED):
if value != current_value:
self._memory_cache.pop(addr)
else:
self._memory_cache[addr] = current_value
elif (dynamic_comparison_type == DynamicComparisonType.CHANGED):
if value == current_value:
self._memory_cache.pop(addr)
else:
self._memory_cache[addr] = current_value
elif (dynamic_comparison_type == DynamicComparisonType.INCREASED):
if value >= current_value:
self._memory_cache.pop(addr)
else:
self._memory_cache[addr] = current_value
elif (dynamic_comparison_type == DynamicComparisonType.DECREASED):
if value <= current_value:
self._memory_cache.pop(addr)
else:
self._memory_cache[addr] = current_value
elif (dynamic_comparison_type == DynamicComparisonType.MATCH):
if new_value == None:
raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH")
if current_value != new_value:
self._memory_cache.pop(addr)
else:
self._memory_cache[addr] = current_value
else:
raise ValueError("Invalid comparison type")
return list(self._memory_cache.keys())
def _check_value(self, value, target_value, standard_comparison_type):
"""
Compares a value with the target value based on the specified compare type.
Args:
value (int): The value to compare.
target_value (int or None): The target value to compare against.
standard_comparison_type (StandardComparisonType): The type of comparison to use.
Returns:
bool: True if the comparison condition is met, False otherwise.
"""
if standard_comparison_type == StandardComparisonType.EXACT.value:
return value == target_value
elif standard_comparison_type == StandardComparisonType.LESS_THAN.value:
return value < target_value
elif standard_comparison_type == StandardComparisonType.GREATER_THAN.value:
return value > target_value
elif standard_comparison_type == StandardComparisonType.LESS_THAN_OR_EQUAL.value:
return value <= target_value
elif standard_comparison_type == StandardComparisonType.GREATER_THAN_OR_EQUAL.value:
return value >= target_value
else:
raise ValueError("Invalid comparison type")
Classes
class StandardComparisonType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for defining types of comparisons that do not require a previous value.
Expand source code
class StandardComparisonType(Enum): """Enumeration for defining types of comparisons that do not require a previous value.""" EXACT = 1 LESS_THAN = 2 GREATER_THAN = 3 LESS_THAN_OR_EQUAL = 4 GREATER_THAN_OR_EQUAL = 5
Ancestors
- enum.Enum
Class variables
var EXACT
var LESS_THAN
var GREATER_THAN
var LESS_THAN_OR_EQUAL
var GREATER_THAN_OR_EQUAL
class DynamicComparisonType (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for defining types of comparisons that require a previous value.
Expand source code
class DynamicComparisonType(Enum): """Enumeration for defining types of comparisons that require a previous value.""" UNCHANGED = 1 CHANGED = 2 INCREASED = 3 DECREASED = 4 MATCH = 5
Ancestors
- enum.Enum
Class variables
var UNCHANGED
var CHANGED
var INCREASED
var DECREASED
var MATCH
class ScanMode (value, names=None, *, module=None, qualname=None, type=None, start=1)
-
Enumeration for defining scanning modes.
Expand source code
class ScanMode(Enum): """Enumeration for defining scanning modes.""" INT = 1 BCD = 2
Ancestors
- enum.Enum
Class variables
var INT
var BCD
class MemoryScanner (pyboy)
-
A class for scanning memory within a given range.
Expand source code
class MemoryScanner(): """A class for scanning memory within a given range.""" def __init__(self, pyboy): self.pyboy = pyboy self._memory_cache = {} self._memory_cache_byte_width = 1 def scan_memory( self, target_value=None, start_addr=0x0000, end_addr=0xFFFF, standard_comparison_type=StandardComparisonType.EXACT, value_type=ScanMode.INT, byte_width=1, byteorder="little" ): """ This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included). Example: ```python >>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) [] ``` Args: start_addr (int): The starting address for the scan. end_addr (int): The ending address for the scan. target_value (int or None): The value to search for. If None, any value is considered a match. standard_comparison_type (StandardComparisonType): The type of comparison to use. value_type (ValueType): The type of value (INT or BCD) to consider. byte_width (int): The number of bytes to consider for each value. byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details. Returns: list of int: A list of addresses where the target value is found. """ self._memory_cache = {} self._memory_cache_byte_width = byte_width for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr # Read multiple bytes based on byte_width and byteorder value_bytes = self.pyboy.memory[addr:addr + byte_width] value = int.from_bytes(value_bytes, byteorder) if value_type == ScanMode.BCD: value = bcd_to_dec(value, byte_width, byteorder) if target_value is None or self._check_value(value, target_value, standard_comparison_type.value): self._memory_cache[addr] = value return list(self._memory_cache.keys()) def rescan_memory( self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little" ): """ Rescans the memory and updates the memory cache based on a dynamic comparison type. Example: ```python >>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) [] >>> for _ in range(175): ... pyboy.tick(1, True) # Progress the game to change score True... >>> current_score = 8 # You write the new score in game >>> from pyboy.api.memory_scanner import DynamicComparisonType >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH) >>> print(addresses) # If repeated enough, only one address will remain [] ``` Args: new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used. dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED. Returns: list of int: A list of addresses remaining in the memory cache after the rescan. """ for addr, value in self._memory_cache.copy().items(): current_value = int.from_bytes( self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder ) if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED): if value != current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.CHANGED): if value == current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.INCREASED): if value >= current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.DECREASED): if value <= current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.MATCH): if new_value == None: raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH") if current_value != new_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value else: raise ValueError("Invalid comparison type") return list(self._memory_cache.keys()) def _check_value(self, value, target_value, standard_comparison_type): """ Compares a value with the target value based on the specified compare type. Args: value (int): The value to compare. target_value (int or None): The target value to compare against. standard_comparison_type (StandardComparisonType): The type of comparison to use. Returns: bool: True if the comparison condition is met, False otherwise. """ if standard_comparison_type == StandardComparisonType.EXACT.value: return value == target_value elif standard_comparison_type == StandardComparisonType.LESS_THAN.value: return value < target_value elif standard_comparison_type == StandardComparisonType.GREATER_THAN.value: return value > target_value elif standard_comparison_type == StandardComparisonType.LESS_THAN_OR_EQUAL.value: return value <= target_value elif standard_comparison_type == StandardComparisonType.GREATER_THAN_OR_EQUAL.value: return value >= target_value else: raise ValueError("Invalid comparison type")
Methods
def scan_memory(self, target_value=None, start_addr=0, end_addr=65535, standard_comparison_type=StandardComparisonType.EXACT, value_type=ScanMode.INT, byte_width=1, byteorder='little')
-
This function scans a specified range of memory for a target value from the
start_addr
to theend_addr
(both included).Example:
>>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) []
Args
start_addr
:int
- The starting address for the scan.
end_addr
:int
- The ending address for the scan.
target_value
:int
orNone
- The value to search for. If None, any value is considered a match.
standard_comparison_type
:StandardComparisonType
- The type of comparison to use.
value_type
:ValueType
- The type of value (INT or BCD) to consider.
byte_width
:int
- The number of bytes to consider for each value.
byteorder
:str
- The endian type to use. This is only used for 16-bit values and higher. See int.from_bytes for more details.
Returns
list
ofint
- A list of addresses where the target value is found.
Expand source code
def scan_memory( self, target_value=None, start_addr=0x0000, end_addr=0xFFFF, standard_comparison_type=StandardComparisonType.EXACT, value_type=ScanMode.INT, byte_width=1, byteorder="little" ): """ This function scans a specified range of memory for a target value from the `start_addr` to the `end_addr` (both included). Example: ```python >>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) [] ``` Args: start_addr (int): The starting address for the scan. end_addr (int): The ending address for the scan. target_value (int or None): The value to search for. If None, any value is considered a match. standard_comparison_type (StandardComparisonType): The type of comparison to use. value_type (ValueType): The type of value (INT or BCD) to consider. byte_width (int): The number of bytes to consider for each value. byteorder (str): The endian type to use. This is only used for 16-bit values and higher. See [int.from_bytes](https://docs.python.org/3/library/stdtypes.html#int.from_bytes) for more details. Returns: list of int: A list of addresses where the target value is found. """ self._memory_cache = {} self._memory_cache_byte_width = byte_width for addr in range(start_addr, end_addr - (byte_width-1) + 1): # Adjust the loop to prevent reading past end_addr # Read multiple bytes based on byte_width and byteorder value_bytes = self.pyboy.memory[addr:addr + byte_width] value = int.from_bytes(value_bytes, byteorder) if value_type == ScanMode.BCD: value = bcd_to_dec(value, byte_width, byteorder) if target_value is None or self._check_value(value, target_value, standard_comparison_type.value): self._memory_cache[addr] = value return list(self._memory_cache.keys())
def rescan_memory(self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder='little')
-
Rescans the memory and updates the memory cache based on a dynamic comparison type.
Example:
>>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) [] >>> for _ in range(175): ... pyboy.tick(1, True) # Progress the game to change score True... >>> current_score = 8 # You write the new score in game >>> from pyboy.api.memory_scanner import DynamicComparisonType >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH) >>> print(addresses) # If repeated enough, only one address will remain []
Args
new_value
:int
, optional- The new value for comparison. If not provided, the current value in memory is used.
dynamic_comparison_type
:DynamicComparisonType
- The type of comparison to use. Defaults to UNCHANGED.
Returns
list
ofint
- A list of addresses remaining in the memory cache after the rescan.
Expand source code
def rescan_memory( self, new_value=None, dynamic_comparison_type=DynamicComparisonType.UNCHANGED, byteorder="little" ): """ Rescans the memory and updates the memory cache based on a dynamic comparison type. Example: ```python >>> current_score = 4 # You write current score in game >>> pyboy.memory_scanner.scan_memory(current_score, start_addr=0xC000, end_addr=0xDFFF) [] >>> for _ in range(175): ... pyboy.tick(1, True) # Progress the game to change score True... >>> current_score = 8 # You write the new score in game >>> from pyboy.api.memory_scanner import DynamicComparisonType >>> addresses = pyboy.memory_scanner.rescan_memory(current_score, DynamicComparisonType.MATCH) >>> print(addresses) # If repeated enough, only one address will remain [] ``` Args: new_value (int, optional): The new value for comparison. If not provided, the current value in memory is used. dynamic_comparison_type (DynamicComparisonType): The type of comparison to use. Defaults to UNCHANGED. Returns: list of int: A list of addresses remaining in the memory cache after the rescan. """ for addr, value in self._memory_cache.copy().items(): current_value = int.from_bytes( self.pyboy.memory[addr:addr + self._memory_cache_byte_width], byteorder=byteorder ) if (dynamic_comparison_type == DynamicComparisonType.UNCHANGED): if value != current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.CHANGED): if value == current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.INCREASED): if value >= current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.DECREASED): if value <= current_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value elif (dynamic_comparison_type == DynamicComparisonType.MATCH): if new_value == None: raise ValueError("new_value must be specified when using DynamicComparisonType.MATCH") if current_value != new_value: self._memory_cache.pop(addr) else: self._memory_cache[addr] = current_value else: raise ValueError("Invalid comparison type") return list(self._memory_cache.keys())