# 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 is 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 (*args, **kwds)`

  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 (*args, **kwds)`

  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 (*args, **kwds)`

  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 is 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 the `end_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` 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 for more details.

    ## Returns

    `list` of `int`
      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` of `int`
      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 is 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())