mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 02:11:36 -04:00
feat(runtime): MEMCARD_SLOTS for titles that only use port 1 (#704)
This commit is contained in:
@@ -2127,6 +2127,10 @@ if(BUILD_TESTING)
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_game_runtime_pgxp.py
|
||||
--cmake ${CMAKE_COMMAND})
|
||||
add_test(NAME game_runtime_memcard_slots_test
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/test_game_runtime_memcard_slots.py
|
||||
--cmake ${CMAKE_COMMAND})
|
||||
# tools/mod_channel_filter.py strips developer features from a staged
|
||||
# catalog; every entry that served one ([[requirement]] included) must
|
||||
# leave with it.
|
||||
|
||||
@@ -1453,6 +1453,18 @@ function(_psxrt_stage_mod_catalog target preloaded_dir)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Memory-card slots a title uses (MEMCARD_SLOTS, default 2). 1 for games that
|
||||
# only talk to port 1: the launcher hides slot 2 and the runtime leaves it empty.
|
||||
function(psxrt_resolve_memcard_slots out_var value)
|
||||
if(value STREQUAL "")
|
||||
set(value 2)
|
||||
endif()
|
||||
if(NOT "${value}" MATCHES "^[12]$")
|
||||
message(FATAL_ERROR "MEMCARD_SLOTS must be 1 or 2 (got ${value})")
|
||||
endif()
|
||||
set(${out_var} ${value} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(psxrecomp_add_runtime_target target)
|
||||
# PGXP: build this target's objects with -DPSX_PGXP=1 so the PGXP_*()
|
||||
# hook macros the emitter writes into ALL generated C become real calls
|
||||
@@ -1479,6 +1491,7 @@ function(psxrecomp_add_runtime_target target)
|
||||
EXE_NAME
|
||||
GAME_VERSION
|
||||
MAX_PLAYERS
|
||||
MEMCARD_SLOTS
|
||||
APP_ICON
|
||||
# The title's own mod catalog source directory -- the one shaped like
|
||||
# <repo>/mods/preloaded, holding packages/<id>/<version>/manifest.toml
|
||||
@@ -2010,6 +2023,8 @@ function(psxrecomp_add_runtime_target target)
|
||||
endif()
|
||||
message(STATUS "psxrecomp ${target}: PSX_MAX_PLAYERS=${PSXRT_MAX_PLAYERS}")
|
||||
|
||||
psxrt_resolve_memcard_slots(PSXRT_MEMCARD_SLOTS "${PSXRT_MEMCARD_SLOTS}")
|
||||
|
||||
target_compile_definitions(${target} PRIVATE
|
||||
DEFAULT_DEBUG_PORT=${PSXRT_DEBUG_PORT}
|
||||
PSX_DEFAULT_BIOS_PATH="${PSXRT_DEFAULT_BIOS_PATH}"
|
||||
@@ -2025,6 +2040,7 @@ function(psxrecomp_add_runtime_target target)
|
||||
PSX_DEFAULT_GAME_CONFIG_PATH="${PSXRT_DEFAULT_GAME_CONFIG_PATH}"
|
||||
PSX_WINDOW_TITLE="${PSXRT_WINDOW_TITLE}"
|
||||
PSX_MAX_PLAYERS=${PSXRT_MAX_PLAYERS}
|
||||
PSX_MEMCARD_SLOTS=${PSXRT_MEMCARD_SLOTS}
|
||||
FMT_HEADER_ONLY=1
|
||||
$<$<PLATFORM_ID:Windows>:NOMINMAX>
|
||||
$<$<BOOL:${PSX_SDL3}>:PSX_SDL3=1>
|
||||
@@ -2630,6 +2646,7 @@ endfunction()
|
||||
# LAUNCHER_BOXART "${CMAKE_CURRENT_SOURCE_DIR}/launcher_assets/img/boxart.tga"
|
||||
# APP_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/psxrecomp.ico"
|
||||
# MAX_PLAYERS 2
|
||||
# MEMCARD_SLOTS 1 # optional; title only uses memory card port 1
|
||||
# ENABLE_NETPLAY_IF_PRESENT
|
||||
# ENABLE_SETUP_WIZARD
|
||||
# PRELOADED_MODS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded"
|
||||
|
||||
+10
-1
@@ -82,6 +82,9 @@ extern "C" uint32_t g_guest_last_store_addr, g_guest_last_store_val;
|
||||
#ifndef PSX_MAX_PLAYERS
|
||||
#define PSX_MAX_PLAYERS 2
|
||||
#endif
|
||||
#ifndef PSX_MEMCARD_SLOTS
|
||||
#define PSX_MEMCARD_SLOTS 2 /* runtime.cmake MEMCARD_SLOTS */
|
||||
#endif
|
||||
#include "psx_netplay.h"
|
||||
#include "psx_stick.h" /* radial SDL-stick -> DualShock response transform */
|
||||
#include "psx_trigger.h" /* continuous SDL trigger -> 0..255 magnitude */
|
||||
@@ -16552,6 +16555,9 @@ namespace {
|
||||
* the generic Display -> View mode launcher row. */
|
||||
gi->widescreen_supported = 0;
|
||||
gi->num_players = game_players_n;
|
||||
#if defined(RECOMP_LAUNCHER_HAS_MEMCARD_SLOTS)
|
||||
gi->memcard_slots = PSX_MEMCARD_SLOTS;
|
||||
#endif
|
||||
#if defined(RECOMP_LAUNCHER_HAS_SHARED_GAMEPAD) && !defined(PSX_NO_DEBUG_TOOLS)
|
||||
gi->allow_shared_gamepad = 1;
|
||||
#endif
|
||||
@@ -20715,9 +20721,12 @@ session_reboot:
|
||||
{
|
||||
std::string mc1 = memcard1_path.string();
|
||||
std::string mc2 = memcard2_path.string();
|
||||
/* A one-slot title (MEMCARD_SLOTS 1) never reads port 2: leave it empty
|
||||
* whatever settings.toml or the launcher stored. */
|
||||
const bool slot2_on = memcard2_enabled && PSX_MEMCARD_SLOTS >= 2;
|
||||
const MemcardSlotConfig slots[2] = {
|
||||
{ mc1.empty() ? nullptr : mc1.c_str(), memcard1_enabled ? 1 : 0 },
|
||||
{ mc2.empty() ? nullptr : mc2.c_str(), memcard2_enabled ? 1 : 0 },
|
||||
{ mc2.empty() ? nullptr : mc2.c_str(), slot2_on ? 1 : 0 },
|
||||
};
|
||||
memcard_init_slots(memcard_dir_str.c_str(), slots);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""psxrecomp_add_game_runtime(... MEMCARD_SLOTS n): runs the REAL
|
||||
psxrt_resolve_memcard_slots from runtime.cmake under `cmake -P`. Unset keeps
|
||||
both slots, 1 and 2 pass through, anything else fails the configure.
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).resolve().parents[2]
|
||||
RUNTIME_CMAKE = REPO / "runtime" / "runtime.cmake"
|
||||
|
||||
|
||||
def extract_function(text: str, name: str) -> str:
|
||||
m = re.search(r"^function\(%s\b.*?^endfunction\(\)" % re.escape(name),
|
||||
text, re.S | re.M)
|
||||
if not m:
|
||||
raise SystemExit("FAIL: %s not found in runtime.cmake" % name)
|
||||
return m.group(0)
|
||||
|
||||
|
||||
def resolve(cmake: str, body: str, value: str):
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
driver = Path(td) / "driver.cmake"
|
||||
driver.write_text(body + '\npsxrt_resolve_memcard_slots(_n "%s")\n'
|
||||
'message(STATUS "SLOTS=${_n}")\n' % value)
|
||||
r = subprocess.run([cmake, "-P", str(driver)], capture_output=True, text=True)
|
||||
m = re.search(r"SLOTS=(\S+)", r.stdout + r.stderr)
|
||||
return (m.group(1) if r.returncode == 0 and m else None)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--cmake", default=shutil.which("cmake") or "cmake")
|
||||
args = ap.parse_args()
|
||||
if shutil.which(args.cmake) is None and not Path(args.cmake).is_file():
|
||||
print("SKIP: no cmake executable available (%s)" % args.cmake)
|
||||
return 0
|
||||
body = extract_function(RUNTIME_CMAKE.read_text(encoding="utf-8"),
|
||||
"psxrt_resolve_memcard_slots")
|
||||
cases = (("", "2", "unset keeps both slots"),
|
||||
("1", "1", "a port-1-only title gets one slot"),
|
||||
("2", "2", "two slots pass through"),
|
||||
("3", None, "three slots fail the configure"),
|
||||
("0", None, "zero slots fail the configure"),
|
||||
("1.5", None, "fractional slots fail the configure"),
|
||||
("one", None, "non-numeric slots fail the configure"),
|
||||
("1;2", None, "lists fail the configure"))
|
||||
failures = 0
|
||||
for value, want, what in cases:
|
||||
ok = resolve(args.cmake, body, value) == want
|
||||
print(("ok " if ok else "FAIL ") + what)
|
||||
failures += not ok
|
||||
if failures:
|
||||
print("test_game_runtime_memcard_slots: %d FAILURES" % failures)
|
||||
return 1
|
||||
print("test_game_runtime_memcard_slots: all checks passed")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user