Skip to content

Commit

Permalink
[wip] melonds test wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
mike8699 committed Sep 9, 2024
1 parent 279de06 commit 6b27ef3
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 4 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ test = [
]
desmume = [
"py-desmume==0.0.6",
"birds-eye-lib@git+https://github.com/mike8699/birds-eye.git@nds-joypad",
]
types = [
"mypy==1.8.0",
Expand Down
9 changes: 5 additions & 4 deletions tests/desmume/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ph_rando.patcher._util import apply_base_patch

from .desmume_utils import DeSmuMEWrapper
from .melonds import MelonDSWrapper


@pytest.fixture(autouse=True)
Expand All @@ -36,9 +37,9 @@ def test_teardown(rom_path: Path, request):
file.unlink()


@pytest.fixture(scope='session')
def desmume_instance():
desmume_emulator = DeSmuMEWrapper()
@pytest.fixture(scope='session', params=[MelonDSWrapper, DeSmuMEWrapper])
def desmume_instance(request):
desmume_emulator = request.param()
yield desmume_emulator
desmume_emulator.destroy()

Expand All @@ -60,7 +61,7 @@ def desmume_emulator(desmume_instance: DeSmuMEWrapper, rom_path: Path) -> DeSmuM

@pytest.fixture
def rom_path(tmp_path: Path) -> Path:
base_rom_path = Path(os.environ['PH_ROM_PATH'])
base_rom_path = Path('C:\\Users\\Mike\\Downloads\\PH_US_DPAD.nds')
python_version = sys.version_info

# The directory where py-desmume keeps its save files. This appears to vary from system
Expand Down
149 changes: 149 additions & 0 deletions tests/desmume/melonds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
from collections.abc import Callable
from .desmume_utils import AbstractEmulatorWrapper

import hashlib
import pytest
import birdseyelib as bird
import subprocess
from pathlib import Path
import time
import socket
import os
import json

EMUHAWK_PATH = Path(os.environ.get('EMUHAWK_PATH', 'C:\\Users\\Mike\\Downloads\\BizHawk-2.9.1-win-x64\\EmuHawk.exe'))
HOST = "127.0.0.1"
PORT = 8080


class MelonDSWrapper(AbstractEmulatorWrapper):
def __init__(self):
self._bizhawk_process = None
self._client = None
self._memory = None
self._emulation = None
self._external_tool = None
self._controller = None

def open(self, rom_path: str):
# Add BirdsEye to the list of trusted external tools
emuhawk_config_file = EMUHAWK_PATH.parent / 'config.ini'
if not emuhawk_config_file.exists():
emuhawk_config_file.write_text(json.dumps({}))
emuhawk_config = json.loads(emuhawk_config_file.read_text())
if 'TrustedExtTools' not in emuhawk_config:
emuhawk_config['TrustedExtTools'] = {}
birds_eye_external_tool = EMUHAWK_PATH.parent / 'ExternalTools' / 'BirdsEye.dll'
be_tool_sha1 = hashlib.sha1(birds_eye_external_tool.read_bytes()).hexdigest().upper()
emuhawk_config['TrustedExtTools'][str(birds_eye_external_tool.resolve())] = f'SHA1:{be_tool_sha1}'
emuhawk_config_file.write_text(json.dumps(emuhawk_config))

# Launch BizHawk with the ROM and BirdsEye
emuhawk_args = [
EMUHAWK_PATH.name,
rom_path,
'--open-ext-tool-dll=BirdsEye',
]
self._bizhawk_process = subprocess.Popen(emuhawk_args, cwd=EMUHAWK_PATH.parent, shell=True)

# Wait for BirdsEye to connect
while True:
try:
with socket.create_connection((HOST, PORT), timeout=5):
print(f"Server {HOST}:{PORT} is available!")
break
except (socket.timeout, ConnectionRefusedError):
print(f"Waiting for server {HOST}:{PORT}...")
time.sleep(1)

self._client = bird.Client(HOST, PORT)
self._memory = bird.Memory(self._client)
self._emulation = bird.Emulation(self._client)
self._external_tool = bird.ExternalTool(self._client)
self._controller = bird.ControllerInput(self._client)
self._joypad = bird.NDSJoypad()
self._controller.set_joypad(self._joypad)
self._client.connect()
self._external_tool.set_commandeer(enabled=True)

def destroy(self):
if self._bizhawk_process is not None:
self._bizhawk_process.terminate()

def wait(self, frames: int):
for _ in range(frames):
self._memory.request_memory()
self._emulation.request_framecount()
self._client.advance_frame()

def button_input(self, buttons: int | list[int], frames: int = 1):
raise NotImplementedError

def touch_set(self, x: int, y: int):
self._joypad.analog_controls['Touch X'] = str(x)
self._joypad.analog_controls['Touch Y'] = str(y)
self._joypad.controls['Touch'] = True
self._controller.set_controller_input(joypad=self._joypad)

def touch_release(self):
self._joypad.controls['Touch'] = False
self._controller.set_controller_input(joypad=self._joypad)

def touch_set_and_release(self, position: tuple[int, int], frames: int = 1):
self.wait(1)
self.touch_set(position[0], position[1])
self.wait(frames + 1)
self.touch_release()

def read_memory(self, start: int, stop: int | None = None):
self._memory.add_address(start)
self._memory.request_memory()
self._client.advance_frame()
return self._memory.get_memory()[hex(start)]

def write_memory(self, address: int, data: bytes | int):
pytest.skip('MelonDSWrapper does not support memory writing yet')

def set_read_breakpoint(self, address: int, callback: Callable[[int, int], None]):
pytest.skip('MelonDSWrapper does not support breakpoints yet')

def set_write_breakpoint(self, address: int, callback: Callable[[int, int], None]):
pytest.skip('MelonDSWrapper does not support breakpoints yet')

def set_exec_breakpoint(self, address: int, callback: Callable[[int, int], None]):
pytest.skip('MelonDSWrapper does not support breakpoints yet')

@property
def r0(self):
pytest.skip('MelonDSWrapper does not support reading registers yet')

@r0.setter
def r0(self, value: int):
pytest.skip('MelonDSWrapper does not support writing registers yet')

@property
def r1(self):
pytest.skip('MelonDSWrapper does not support reading registers yet')

@r1.setter
def r1(self, value: int):
pytest.skip('MelonDSWrapper does not support writing registers yet')

@property
def r2(self):
pytest.skip('MelonDSWrapper does not support reading registers yet')

@r2.setter
def r2(self, value: int):
pytest.skip('MelonDSWrapper does not support writing registers yet')

@property
def r3(self):
pytest.skip('MelonDSWrapper does not support reading registers yet')

@r3.setter
def r3(self, value: int):
pytest.skip('MelonDSWrapper does not support writing registers yet')

def reset(self):
raise NotImplementedError

0 comments on commit 6b27ef3

Please sign in to comment.