diff --git a/docs/index.html b/docs/index.html index fedfc0c41..01acbc673 100644 --- a/docs/index.html +++ b/docs/index.html @@ -37,9 +37,9 @@

Module pyboy

"conftest": False, } -__all__ = ["PyBoy", "PyBoyMemoryView"] +__all__ = ["PyBoy", "PyBoyMemoryView", "PyBoyRegisterFile"] -from .pyboy import PyBoy, PyBoyMemoryView +from .pyboy import PyBoy, PyBoyMemoryView, PyBoyRegisterFile
@@ -285,6 +285,25 @@

Kwargs

""" + self.register_file = PyBoyRegisterFile(self.mb.cpu) + """ + Provides a `pyboy.PyBoyRegisterFile` object for reading and writing the CPU registers of the Game Boy. + + The register file is best used inside the callback of a hook, as `PyBoy.tick` doesn't return at a specific point. + + For a more comprehensive description, see the `pyboy.PyBoyRegisterFile` class. + + Example: + ```python + >>> def my_callback(register_file): + ... print("Register A:", register_file.A) + >>> pyboy.hook_register(0, 0x100, my_callback, pyboy.register_file) + >>> pyboy.tick(70) + Register A: 1 + True + ``` + """ + self.memory_scanner = MemoryScanner(self) """ Provides a `pyboy.api.memory_scanner.MemoryScanner` object for locating addresses of interest in the memory space @@ -1375,6 +1394,20 @@

Returns

>>> pyboy.memory[0xC000] = 1 # Write to address 0xC000 with value 1
+
var register_file
+
+

Provides a PyBoyRegisterFile object for reading and writing the CPU registers of the Game Boy.

+

The register file is best used inside the callback of a hook, as PyBoy.tick() doesn't return at a specific point.

+

For a more comprehensive description, see the PyBoyRegisterFile class.

+

Example:

+
>>> def my_callback(register_file):
+...     print("Register A:", register_file.A)
+>>> pyboy.hook_register(0, 0x100, my_callback, pyboy.register_file)
+>>> pyboy.tick(70)
+Register A: 1
+True
+
+
var memory_scanner

Provides a MemoryScanner object for locating addresses of interest in the memory space @@ -3293,6 +3326,236 @@

Args

self.mb.setitem(start, v)
+
+class PyBoyRegisterFile +(cpu) +
+
+

This class cannot be used directly, but is accessed through PyBoy.register_file.

+

This class serves the purpose of reading and writing to the CPU registers. It's best used inside the callback of a +hook, as PyBoy.tick() doesn't return at a specific point.

+

See the Pan Docs: CPU registers and flags for a great overview.

+

Registers are accessed with the following names: A, F, B, C, D, E, HL, SP, PC where the last three are 16-bit and +the others are 8-bit. Trying to write a number larger than 8 or 16 bits will truncate it.

+

Example:

+
>>> def my_callback(pyboy):
+...     print("Register A:", pyboy.register_file.A)
+...     pyboy.memory[0xFF50] = 1 # Example: Disable boot ROM
+...     pyboy.register_file.A = 0x11 # Modify to the needed value
+...     pyboy.register_file.PC = 0x100 # Jump past existing code
+>>> pyboy.hook_register(-1, 0xFC, my_callback, pyboy)
+>>> pyboy.tick(120)
+Register A: 1
+True
+
+
+ +Expand source code + +
class PyBoyRegisterFile:
+    """
+    This class cannot be used directly, but is accessed through `PyBoy.register_file`.
+
+    This class serves the purpose of reading and writing to the CPU registers. It's best used inside the callback of a
+    hook, as `PyBoy.tick` doesn't return at a specific point.
+
+    See the [Pan Docs: CPU registers and flags](https://gbdev.io/pandocs/CPU_Registers_and_Flags.html) for a great overview.
+
+    Registers are accessed with the following names: `A, F, B, C, D, E, HL, SP, PC` where the last three are 16-bit and
+    the others are 8-bit. Trying to write a number larger than 8 or 16 bits will truncate it.
+
+    Example:
+    ```python
+    >>> def my_callback(pyboy):
+    ...     print("Register A:", pyboy.register_file.A)
+    ...     pyboy.memory[0xFF50] = 1 # Example: Disable boot ROM
+    ...     pyboy.register_file.A = 0x11 # Modify to the needed value
+    ...     pyboy.register_file.PC = 0x100 # Jump past existing code
+    >>> pyboy.hook_register(-1, 0xFC, my_callback, pyboy)
+    >>> pyboy.tick(120)
+    Register A: 1
+    True
+    ```
+    """
+
+    def __init__(self, cpu):
+        self.cpu = cpu
+
+    @property
+    def A(self):
+        return self.cpu.A
+    @A.setter
+    def A(self, value):
+        self.cpu.A = value & 0xFF
+
+    @property
+    def F(self):
+        return self.cpu.F
+    @F.setter
+    def F(self, value):
+        self.cpu.F = value & 0xF0
+
+    @property
+    def B(self):
+        return self.cpu.B
+    @B.setter
+    def B(self, value):
+        self.cpu.B = value & 0xFF
+
+    @property
+    def C(self):
+        return self.cpu.C
+    @C.setter
+    def C(self, value):
+        self.cpu.C = value & 0xFF
+
+    @property
+    def D(self):
+        return self.cpu.D
+    @D.setter
+    def D(self, value):
+        self.cpu.D = value & 0xFF
+
+    @property
+    def E(self):
+        return self.cpu.E
+    @E.setter
+    def E(self, value):
+        self.cpu.E = value & 0xFF
+
+    @property
+    def HL(self):
+        return self.cpu.HL
+    @HL.setter
+    def HL(self, value):
+        self.cpu.HL = value & 0xFFFF
+
+    @property
+    def SP(self):
+        return self.cpu.SP
+    @SP.setter
+    def SP(self, value):
+        self.cpu.SP = value & 0xFFFF
+
+    @property
+    def PC(self):
+        return self.cpu.PC
+    @PC.setter
+    def PC(self, value):
+        self.cpu.PC = value & 0xFFFF
+
+

Instance variables

+
+
var A
+
+
+
+ +Expand source code + +
@property
+def A(self):
+    return self.cpu.A
+
+
+
var F
+
+
+
+ +Expand source code + +
@property
+def F(self):
+    return self.cpu.F
+
+
+
var B
+
+
+
+ +Expand source code + +
@property
+def B(self):
+    return self.cpu.B
+
+
+
var C
+
+
+
+ +Expand source code + +
@property
+def C(self):
+    return self.cpu.C
+
+
+
var D
+
+
+
+ +Expand source code + +
@property
+def D(self):
+    return self.cpu.D
+
+
+
var E
+
+
+
+ +Expand source code + +
@property
+def E(self):
+    return self.cpu.E
+
+
+
var HL
+
+
+
+ +Expand source code + +
@property
+def HL(self):
+    return self.cpu.HL
+
+
+
var SP
+
+
+
+ +Expand source code + +
@property
+def SP(self):
+    return self.cpu.SP
+
+
+
var PC
+
+
+
+ +Expand source code + +
@property
+def PC(self):
+    return self.cpu.PC
+
+
+
+
@@ -3336,6 +3599,7 @@

PyBoy

  • rtc_lock_experimental
  • screen
  • memory
  • +
  • register_file
  • memory_scanner
  • tilemap_background
  • tilemap_window
  • @@ -3346,6 +3610,20 @@

    PyBoy

  • PyBoyMemoryView

  • +
  • +

    PyBoyRegisterFile

    + +
  • diff --git a/pyboy/__init__.py b/pyboy/__init__.py index 76db24215..5c93fabde 100644 --- a/pyboy/__init__.py +++ b/pyboy/__init__.py @@ -10,6 +10,6 @@ "conftest": False, } -__all__ = ["PyBoy", "PyBoyMemoryView"] +__all__ = ["PyBoy", "PyBoyMemoryView", "PyBoyRegisterFile"] -from .pyboy import PyBoy, PyBoyMemoryView +from .pyboy import PyBoy, PyBoyMemoryView, PyBoyRegisterFile diff --git a/pyboy/pyboy.pxd b/pyboy/pyboy.pxd index 5984df992..fec26265d 100644 --- a/pyboy/pyboy.pxd +++ b/pyboy/pyboy.pxd @@ -11,6 +11,7 @@ from libc.stdint cimport int64_t, uint64_t from pyboy.api.memory_scanner cimport MemoryScanner from pyboy.api.screen cimport Screen from pyboy.api.tilemap cimport TileMap +from pyboy.core.cpu cimport CPU from pyboy.core.mb cimport Motherboard from pyboy.logging.logging cimport Logger from pyboy.plugins.manager cimport PluginManager @@ -21,6 +22,9 @@ cdef Logger logger cdef double SPF +cdef class PyBoyRegisterFile: + cdef CPU cpu + cdef class PyBoyMemoryView: cdef Motherboard mb @@ -49,6 +53,7 @@ cdef class PyBoy: cdef bint initialized cdef readonly str window_title cdef readonly PyBoyMemoryView memory + cdef readonly PyBoyRegisterFile register_file cdef readonly Screen screen cdef readonly TileMap tilemap_background cdef readonly TileMap tilemap_window diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index fb814153d..01f34ec6f 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -214,6 +214,25 @@ def __init__( """ + self.register_file = PyBoyRegisterFile(self.mb.cpu) + """ + Provides a `pyboy.PyBoyRegisterFile` object for reading and writing the CPU registers of the Game Boy. + + The register file is best used inside the callback of a hook, as `PyBoy.tick` doesn't return at a specific point. + + For a more comprehensive description, see the `pyboy.PyBoyRegisterFile` class. + + Example: + ```python + >>> def my_callback(register_file): + ... print("Register A:", register_file.A) + >>> pyboy.hook_register(0, 0x100, my_callback, pyboy.register_file) + >>> pyboy.tick(70) + Register A: 1 + True + ``` + """ + self.memory_scanner = MemoryScanner(self) """ Provides a `pyboy.api.memory_scanner.MemoryScanner` object for locating addresses of interest in the memory space @@ -1274,6 +1293,107 @@ def rtc_lock_experimental(self, enable): raise Exception("There's no RTC for this cartridge type") +class PyBoyRegisterFile: + """ + This class cannot be used directly, but is accessed through `PyBoy.register_file`. + + This class serves the purpose of reading and writing to the CPU registers. It's best used inside the callback of a + hook, as `PyBoy.tick` doesn't return at a specific point. + + See the [Pan Docs: CPU registers and flags](https://gbdev.io/pandocs/CPU_Registers_and_Flags.html) for a great overview. + + Registers are accessed with the following names: `A, F, B, C, D, E, HL, SP, PC` where the last three are 16-bit and + the others are 8-bit. Trying to write a number larger than 8 or 16 bits will truncate it. + + Example: + ```python + >>> def my_callback(pyboy): + ... print("Register A:", pyboy.register_file.A) + ... pyboy.memory[0xFF50] = 1 # Example: Disable boot ROM + ... pyboy.register_file.A = 0x11 # Modify to the needed value + ... pyboy.register_file.PC = 0x100 # Jump past existing code + >>> pyboy.hook_register(-1, 0xFC, my_callback, pyboy) + >>> pyboy.tick(120) + Register A: 1 + True + ``` + """ + def __init__(self, cpu): + self.cpu = cpu + + @property + def A(self): + return self.cpu.A + + @A.setter + def A(self, value): + self.cpu.A = value & 0xFF + + @property + def F(self): + return self.cpu.F + + @F.setter + def F(self, value): + self.cpu.F = value & 0xF0 + + @property + def B(self): + return self.cpu.B + + @B.setter + def B(self, value): + self.cpu.B = value & 0xFF + + @property + def C(self): + return self.cpu.C + + @C.setter + def C(self, value): + self.cpu.C = value & 0xFF + + @property + def D(self): + return self.cpu.D + + @D.setter + def D(self, value): + self.cpu.D = value & 0xFF + + @property + def E(self): + return self.cpu.E + + @E.setter + def E(self, value): + self.cpu.E = value & 0xFF + + @property + def HL(self): + return self.cpu.HL + + @HL.setter + def HL(self, value): + self.cpu.HL = value & 0xFFFF + + @property + def SP(self): + return self.cpu.SP + + @SP.setter + def SP(self, value): + self.cpu.SP = value & 0xFFFF + + @property + def PC(self): + return self.cpu.PC + + @PC.setter + def PC(self, value): + self.cpu.PC = value & 0xFFFF + + class PyBoyMemoryView: """ This class cannot be used directly, but is accessed through `PyBoy.memory`. diff --git a/tests/test_basics.py b/tests/test_basics.py index 757ed4dca..751f4ad85 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -47,6 +47,42 @@ def test_log_level_critical(default_rom, capsys): assert captured.out == "" +def test_register_file(default_rom): + pyboy = PyBoy(default_rom, window="null") + pyboy.set_emulation_speed(0) + + pyboy.register_file.A = 0x1AB + assert pyboy.register_file.A == 0xAB + pyboy.register_file.F = 0xFF + assert pyboy.register_file.F == 0xF0 # Lower 4 bits always zero on F + pyboy.register_file.SP = 0x1234 + assert pyboy.register_file.SP == 0x1234 + + def change_sp(p): + assert p.register_file.SP == 0xFFFE, "Expected 0xFFFE from boot ROM" + p.register_file.SP = 0xFF00 # We should see this at the end too + + registers = [0] * 9 + + def dump_registers(p): + registers[0] = p.register_file.A + registers[1] = p.register_file.F + registers[2] = p.register_file.B + registers[3] = p.register_file.C + registers[4] = p.register_file.D + registers[5] = p.register_file.E + registers[6] = p.register_file.HL + registers[7] = p.register_file.SP + registers[8] = p.register_file.PC + + pyboy.hook_register(-1, 0x3, change_sp, pyboy) + pyboy.hook_register(0, 0x100, dump_registers, pyboy) + for _ in range(120): + pyboy.tick() + + assert registers == [0x1, 208, 0, 0, 0, 143, 135, 0xFF00, 0x100] + + def test_record_replay(boot_rom, default_rom): pyboy = PyBoy(default_rom, window="null", bootrom=boot_rom, record_input=True) pyboy.set_emulation_speed(0)