Skip to content

Commit

Permalink
Introduce RTC lock feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Baekalfen committed May 26, 2024
1 parent 426d269 commit 8b1aa1d
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 2 deletions.
1 change: 1 addition & 0 deletions pyboy/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def mock_PyBoy(filename, *args, **kwargs):
mock.patch("pyboy.PyBoy.game_area", return_value=tetris_game_area), \
mock.patch("pyboy.PyBoy.load_state", return_value=None), \
mock.patch("pyboy.PyBoy.stop", return_value=None), \
mock.patch("pyboy.PyBoy.rtc_lock_experimental", return_value=None), \
mock.patch("PIL.Image.Image.show", return_value=None):

pyboy.set_emulation_speed(0)
Expand Down
1 change: 1 addition & 0 deletions pyboy/core/cartridge/rtc.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ cdef class RTC:
cdef str filename
cdef bint latch_enabled
cdef cython.double timezero
cdef bint timelock
cdef uint64_t sec_latch
cdef uint64_t min_latch
cdef uint64_t hour_latch
Expand Down
10 changes: 8 additions & 2 deletions pyboy/core/cartridge/rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ def load_state(self, f, state_version):
self.day_carry = f.read()

def latch_rtc(self):
t = time.time() - self.timezero
if self.timelock:
t = 0
else:
t = time.time() - self.timezero
self.sec_latch = int(t % 60)
self.min_latch = int((t//60) % 60)
self.hour_latch = int((t//3600) % 24)
Expand Down Expand Up @@ -99,7 +102,10 @@ def setregister(self, register, value):
if not self.latch_enabled:
logger.debug("RTC: Set register, but nothing is latched! 0x%0.4x, 0x%0.2x", register, value)

t = time.time() - self.timezero
if self.timelock:
t = 0
else:
t = time.time() - self.timezero
if register == 0x08:
# TODO: What happens, when these value are larger than allowed?
self.timezero = self.timezero - (t%60) - value
Expand Down
27 changes: 27 additions & 0 deletions pyboy/pyboy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,33 @@ def get_tile(self, identifier):
"""
return Tile(self.mb, identifier=identifier)

def rtc_lock_experimental(self, enable):
"""
**WARN: This is an experimental API and is subject to change.**
Lock the Real Time Clock (RTC) of a supporting cartridge. It might be advantageous to lock the RTC when training
an AI in games that use it to change behavior (i.e. day and night).
The first time the game is turned on, an `.rtc` file is created with the current time. This is the epoch for the
RTC. When using `rtc_lock_experimental`, the RTC will always report this point in time. If you let the game progress first,
before using `rtc_lock_experimental`, the internal clock will move backwards and might corrupt the game.
Example:
```python
>>> pyboy = PyBoy('game_rom.gb')
>>> pyboy.rtc_lock_experimental(True) # RTC will not progress
```
**WARN: This is an experimental API and is subject to change.**
Args:
enable (float): Point in time to lock RTC to
"""
if self.mb.cartridge.rtc_enabled:
self.mb.cartridge.rtc.timelock = enable
else:
raise Exception("There's no RTC for this cartridge type")


class PyBoyMemoryView:
"""
Expand Down
31 changes: 31 additions & 0 deletions tests/test_external_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@ def test_misc(default_rom):
pyboy.stop(save=False)


def test_rtc_lock_no_rtc(default_rom):
pyboy = PyBoy(default_rom, window="null")
with pytest.raises(Exception):
pyboy.rtc_lock_experimental(True)


def test_rtc_lock(pokemon_gold_rom):
pyboy = PyBoy(pokemon_gold_rom, window="null")
pyboy.rtc_lock_experimental(False)

#Enable external RAM
pyboy.memory[0x0000] = 0x0A
Expand Down Expand Up @@ -79,6 +86,30 @@ def test_rtc_lock(pokemon_gold_rom):
pyboy.memory[0x4000] = 0x0c
assert pyboy.memory[0xA000] == 0

pyboy.rtc_lock_experimental(True)

# Pan docs:
# When writing $00, and then $01 to this register, the current time becomes latched into the RTC registers
pyboy.memory[0x6000] = 0
pyboy.memory[0x6000] = 1

# Pan docs:
# When writing a value of $08-$0C, this will map the corresponding RTC register into memory at A000-BFFF
pyboy.memory[0x4000] = 0x08
assert pyboy.memory[0xA000] == 0

pyboy.memory[0x4000] = 0x09
assert pyboy.memory[0xA000] == 0

pyboy.memory[0x4000] = 0x0a
assert pyboy.memory[0xA000] == 0

pyboy.memory[0x4000] = 0x0b
assert pyboy.memory[0xA000] == 0

pyboy.memory[0x4000] = 0x0c
assert pyboy.memory[0xA000] == 0


def test_faulty_state(default_rom):
pyboy = PyBoy(default_rom, window="null")
Expand Down

0 comments on commit 8b1aa1d

Please sign in to comment.