mirror of
https://github.com/libretro/virtualjaguar-libretro.git
synced 2026-11-07 04:36:00 -10:00
feat(core): frontend BIOS queries, BIOS_STAGED events and a cart-ROM no-content boot
Adds VJ_ENV_GET_BIOS_PATH / VJ_ENV_GET_BOOT_TARGET behind a new VJ_CAP_BIOS capability on the private frontend channel (ABI unchanged), so the standalone app can name the exact CD BIOS / cart boot ROM file to run and choose which BIOS a no-content boot enters (CD BIOS, or the cart boot ROM against an empty slot). The core still validates every file and falls back to its own search and the built-in images; RetroArch never answers HELLO, so nothing changes there. VJ_EVT_BIOS_STAGED / VJ_EVT_BIOS_REJECTED say which image was staged. Refs #838 Refs #833 Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
d405223ee7
commit
d5adfdee61
4 files changed
+428
-38
No files matched your search
+128
-28
@@ -5676,6 +5676,24 @@ static bool has_extension(const char *path, const char *ext)
|
||||
#define CD_BIOS_CRC_RETAIL 0x687068D5u
|
||||
#define CD_BIOS_CRC_DEV 0x55A0669Cu
|
||||
|
||||
/* Path of the external BIOS file the last successful try_load_*() accepted,
|
||||
* for the VJ_EVT_BIOS_STAGED event. Overwritten on every success, read only
|
||||
* straight after one, so it needs no reset. */
|
||||
static char last_bios_path[4096];
|
||||
|
||||
/* Tell an answering frontend (the standalone app) which BIOS image the core
|
||||
* is about to run, and where it came from. One never-taken branch under
|
||||
* RetroArch. src: VJ_BIOS_SRC_*; name: file path or built-in image name. */
|
||||
static void bios_staged_event(unsigned role, const uint8_t *img,
|
||||
unsigned len, unsigned src, const char *name)
|
||||
{
|
||||
if (!VJExtActive())
|
||||
return;
|
||||
VJExtEmit(VJ_EVT_BIOS_STAGED, role,
|
||||
(unsigned)crc32_calcCheckSum((unsigned char *)img, len),
|
||||
src, 0, name);
|
||||
}
|
||||
|
||||
/* Try to load a 256 KB CD BIOS image from the given path.
|
||||
* Returns true on success and sets cd_bios_loaded_externally.
|
||||
* A file that exists but fails validation bumps *rejected so the
|
||||
@@ -5712,6 +5730,7 @@ static bool try_load_cd_bios_file(const char *path, int *rejected)
|
||||
return false;
|
||||
}
|
||||
rfclose(f);
|
||||
snprintf(last_bios_path, sizeof(last_bios_path), "%s", path);
|
||||
|
||||
/* Known dumps are accepted by checksum, no header check needed. */
|
||||
crc = (uint32_t)crc32_calcCheckSum(external_cd_bios, 0x40000);
|
||||
@@ -5859,31 +5878,60 @@ static bool load_external_cd_bios(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Stage the CD BIOS for the real-BIOS boot path: prefer an external ROM
|
||||
* file from the system directory (users may carry a different BIOS
|
||||
* revision), else fall back to an embedded CD BIOS so real-BIOS boot
|
||||
* works with zero files. Which embedded image is used follows the
|
||||
* 'CD BIOS Type' option: retail (default) or the developer BIOS, which
|
||||
* skips some of the retail BIOS's disc checks.
|
||||
/* Stage the CD BIOS for the real-BIOS boot path. Order:
|
||||
* 1. the file an answering frontend names (VJ_ENV_GET_BIOS_PATH) -- the
|
||||
* standalone app's "use this BIOS"; validated like any external file;
|
||||
* 2. an external ROM file from the system directory (users may carry a
|
||||
* different BIOS revision);
|
||||
* 3. an embedded CD BIOS, so real-BIOS boot works with zero files. Which
|
||||
* embedded image is used follows the 'CD BIOS Type' option: retail
|
||||
* (default) or the developer BIOS, which skips some of the retail
|
||||
* BIOS's disc checks.
|
||||
*
|
||||
* An external file always wins over both — it is the user explicitly
|
||||
* supplying a revision, so the type selection does not override it. */
|
||||
* Under RetroArch step 1 does not exist. An external file always wins over
|
||||
* the embedded images -- it is the user explicitly supplying a revision, so
|
||||
* the type selection does not override it. */
|
||||
static void stage_cd_bios(void)
|
||||
{
|
||||
char fpath[4096];
|
||||
int rejected = 0;
|
||||
|
||||
if (VJExtBiosPath(VJ_BIOS_CD, fpath, sizeof(fpath)))
|
||||
{
|
||||
if (try_load_cd_bios_file(fpath, &rejected))
|
||||
{
|
||||
bios_staged_event(VJ_BIOS_CD, external_cd_bios, 0x40000,
|
||||
VJ_BIOS_SRC_FRONTEND, fpath);
|
||||
return;
|
||||
}
|
||||
LOG_WRN("[CD-BIOS] the frontend-selected CD BIOS %s is unusable "
|
||||
"(missing, not exactly 262144 bytes, or not a CD BIOS); "
|
||||
"searching the system directory instead\n", fpath);
|
||||
VJExtEmit(VJ_EVT_BIOS_REJECTED, VJ_BIOS_CD, 0, 0, 0, fpath);
|
||||
}
|
||||
|
||||
if (load_external_cd_bios())
|
||||
{
|
||||
bios_staged_event(VJ_BIOS_CD, external_cd_bios, 0x40000,
|
||||
VJ_BIOS_SRC_SYSTEM, last_bios_path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (vjs.cdBiosType == CDBIOS_DEV)
|
||||
{
|
||||
memcpy(external_cd_bios, jaguarDevCDBootROM, 0x40000);
|
||||
cd_bios_loaded_externally = true;
|
||||
LOG_INF("[CD-BIOS] using embedded developer CD BIOS\n");
|
||||
bios_staged_event(VJ_BIOS_CD, external_cd_bios, 0x40000,
|
||||
VJ_BIOS_SRC_BUILTIN, "developer CD BIOS");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(external_cd_bios, jaguarCDBootROM, 0x40000);
|
||||
cd_bios_loaded_externally = true;
|
||||
LOG_INF("[CD-BIOS] using embedded retail CD BIOS\n");
|
||||
bios_staged_event(VJ_BIOS_CD, external_cd_bios, 0x40000,
|
||||
VJ_BIOS_SRC_BUILTIN, "retail CD BIOS");
|
||||
}
|
||||
|
||||
static void cart_bios_type_from_path(const char *path)
|
||||
@@ -5992,6 +6040,7 @@ static bool try_load_cart_boot_rom_file(const char *path)
|
||||
path, (unsigned)crc);
|
||||
LOG_INF("[BOOT] external cart boot ROM %s: %s (crc %08x)\n",
|
||||
path, name, (unsigned)crc);
|
||||
snprintf(last_bios_path, sizeof(last_bios_path), "%s", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6055,15 +6104,40 @@ static void stage_cart_boot_rom(void)
|
||||
RFILE *f;
|
||||
char path[4096];
|
||||
|
||||
/* A file the frontend named (VJ_ENV_GET_BIOS_PATH) beats everything below,
|
||||
* 'Cart BIOS Type' included: it is an explicit pick of one image. It only
|
||||
* matters when the boot ROM actually runs (BIOS (Cartridges) = Real, a
|
||||
* GPU-only cart, or a no-content boot), but staging it is harmless when
|
||||
* it does not. Anything but exactly 128 KB is refused and we carry on. */
|
||||
if (VJExtBiosPath(VJ_BIOS_CART_BOOT, path, sizeof(path)))
|
||||
{
|
||||
if (try_load_cart_boot_rom_file(path))
|
||||
{
|
||||
bios_staged_event(VJ_BIOS_CART_BOOT, jagMemSpace + 0xE00000, 0x20000,
|
||||
VJ_BIOS_SRC_FRONTEND, path);
|
||||
return;
|
||||
}
|
||||
LOG_WRN("[BOOT] the frontend-selected cart boot ROM %s is unusable "
|
||||
"(missing or not exactly 131072 bytes); using the usual "
|
||||
"search instead\n", path);
|
||||
VJExtEmit(VJ_EVT_BIOS_REJECTED, VJ_BIOS_CART_BOOT, 0, 0, 0, path);
|
||||
}
|
||||
|
||||
if (vjs.biosType == BT_CUSTOM)
|
||||
{
|
||||
if (load_external_cart_boot_rom())
|
||||
{
|
||||
bios_staged_event(VJ_BIOS_CART_BOOT, jagMemSpace + 0xE00000, 0x20000,
|
||||
VJ_BIOS_SRC_SYSTEM, last_bios_path);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WRN("[BOOT] Custom cart boot ROM selected but no usable file "
|
||||
"found -- falling back to embedded Series K\n");
|
||||
memcpy(jagMemSpace + 0xE00000, jaguarBootROM, 0x20000);
|
||||
LOG_INF("[BOOT] cart boot ROM: Series K (fallback)\n");
|
||||
bios_staged_event(VJ_BIOS_CART_BOOT, jagMemSpace + 0xE00000, 0x20000,
|
||||
VJ_BIOS_SRC_BUILTIN, "Series K boot ROM");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6083,6 +6157,8 @@ static void stage_cart_boot_rom(void)
|
||||
{
|
||||
rfclose(f);
|
||||
LOG_INF("[BOOT] using external Model-M boot ROM %s\n", path);
|
||||
bios_staged_event(VJ_BIOS_CART_BOOT, jagMemSpace + 0xE00000,
|
||||
0x20000, VJ_BIOS_SRC_SYSTEM, path);
|
||||
return;
|
||||
}
|
||||
rfclose(f);
|
||||
@@ -6092,6 +6168,10 @@ static void stage_cart_boot_rom(void)
|
||||
memcpy(jagMemSpace + 0xE00000, src, 0x20000);
|
||||
LOG_INF("[BOOT] cart boot ROM: %s\n",
|
||||
(vjs.biosType == BT_M_SERIES) ? "Model M" : "Series K");
|
||||
bios_staged_event(VJ_BIOS_CART_BOOT, jagMemSpace + 0xE00000, 0x20000,
|
||||
VJ_BIOS_SRC_BUILTIN,
|
||||
(vjs.biosType == BT_M_SERIES) ? "Model M boot ROM"
|
||||
: "Series K boot ROM");
|
||||
}
|
||||
|
||||
/* Fill the entire framebuffer allocation with opaque black.
|
||||
@@ -7065,28 +7145,48 @@ bool retro_load_game(const struct retro_game_info *info)
|
||||
* memcpys the staged BIOS, takes the run address from $800404 and
|
||||
* resets -- so the BIOS comes up on its own insert-disc screen with
|
||||
* zero files present. */
|
||||
stage_cd_bios();
|
||||
if (VJExtBootTarget() == VJ_BOOT_CART_BOOT_ROM)
|
||||
{
|
||||
/* An answering frontend (the standalone app's "Boot" on a cart boot
|
||||
* ROM) asked for the other no-content boot: the cartridge boot ROM
|
||||
* against an empty slot -- cd_boot_strategy_none, the pre-#726
|
||||
* behaviour. The boot ROM shows its animation and then sits, like a
|
||||
* Jaguar with nothing in the slot. No CD unit, so no Memory Track
|
||||
* (it plugs into the CD unit) and jaguar_cd_mode stays false.
|
||||
* Nothing here runs under RetroArch: it never answers HELLO. */
|
||||
vjs.useJaguarBIOS = true;
|
||||
bootConfig.isCDGame = false;
|
||||
bootConfig.showBootROM = true;
|
||||
bootConfig.cdBiosAvailable = false;
|
||||
bootConfig.strategy = &cd_boot_strategy_none;
|
||||
LOG_INF("[BOOT] no-content boot: cartridge boot ROM, empty slot "
|
||||
"(frontend request)\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
stage_cd_bios();
|
||||
|
||||
/* The CD unit is attached -- that is WHY the BIOS is running -- so
|
||||
* this is CD mode with nothing in the tray, not cartridge mode.
|
||||
* Both flags mirror open_disc_and_resolve_boot(); only the disc is
|
||||
* missing. Without them a machine sitting in the CD BIOS gets the
|
||||
* options menu inverted against it (update_option_visibility() keys
|
||||
* both gates off jaguar_cd_mode, so CD Boot Mode / CD BIOS Type
|
||||
* would be HIDDEN and the cartridge-BIOS option shown), and the
|
||||
* Memory Track cart -- which plugs into the CD unit and is present
|
||||
* whether or not a disc is -- would stay absent until the first
|
||||
* insert. Safe with no disc: the three disc-identity accessors
|
||||
* return zero when nothing is mounted (see the savestate chunk),
|
||||
* and the MT save-buffer paths only touch mtMem. */
|
||||
jaguar_cd_mode = true;
|
||||
jaguarMemTrackInserted = opt_memory_track;
|
||||
/* The CD unit is attached -- that is WHY the BIOS is running -- so
|
||||
* this is CD mode with nothing in the tray, not cartridge mode.
|
||||
* Both flags mirror open_disc_and_resolve_boot(); only the disc is
|
||||
* missing. Without them a machine sitting in the CD BIOS gets the
|
||||
* options menu inverted against it (update_option_visibility() keys
|
||||
* both gates off jaguar_cd_mode, so CD Boot Mode / CD BIOS Type
|
||||
* would be HIDDEN and the cartridge-BIOS option shown), and the
|
||||
* Memory Track cart -- which plugs into the CD unit and is present
|
||||
* whether or not a disc is -- would stay absent until the first
|
||||
* insert. Safe with no disc: the three disc-identity accessors
|
||||
* return zero when nothing is mounted (see the savestate chunk),
|
||||
* and the MT save-buffer paths only touch mtMem. */
|
||||
jaguar_cd_mode = true;
|
||||
jaguarMemTrackInserted = opt_memory_track;
|
||||
|
||||
vjs.useJaguarBIOS = true;
|
||||
bootConfig.isCDGame = false;
|
||||
bootConfig.showBootROM = true;
|
||||
bootConfig.cdBiosAvailable = true;
|
||||
bootConfig.strategy = &cd_boot_strategy_bios;
|
||||
vjs.useJaguarBIOS = true;
|
||||
bootConfig.isCDGame = false;
|
||||
bootConfig.showBootROM = true;
|
||||
bootConfig.cdBiosAvailable = true;
|
||||
bootConfig.strategy = &cd_boot_strategy_bios;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -5,10 +5,13 @@
|
||||
|
||||
#include "vj_frontend_ext.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* File scope, reset in VJExtDeinit(): iOS never dlcloses the core. */
|
||||
static retro_environment_t vj_ext_env = 0;
|
||||
static int vj_ext_on = 0;
|
||||
static unsigned vj_ext_sent = 0;
|
||||
static unsigned vj_ext_caps = 0;
|
||||
|
||||
void VJExtInit(retro_environment_t env_cb, const char *core_version)
|
||||
{
|
||||
@@ -17,6 +20,7 @@ void VJExtInit(retro_environment_t env_cb, const char *core_version)
|
||||
vj_ext_env = 0;
|
||||
vj_ext_on = 0;
|
||||
vj_ext_sent = 0;
|
||||
vj_ext_caps = 0;
|
||||
if (!env_cb)
|
||||
return;
|
||||
|
||||
@@ -25,8 +29,9 @@ void VJExtInit(retro_environment_t env_cb, const char *core_version)
|
||||
hello.caps_out = 0;
|
||||
if (env_cb(VJ_ENV_HELLO, &hello) && (hello.caps_out & VJ_CAP_EVENTS))
|
||||
{
|
||||
vj_ext_env = env_cb;
|
||||
vj_ext_on = 1;
|
||||
vj_ext_env = env_cb;
|
||||
vj_ext_on = 1;
|
||||
vj_ext_caps = hello.caps_out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +40,7 @@ void VJExtDeinit(void)
|
||||
vj_ext_env = 0;
|
||||
vj_ext_on = 0;
|
||||
vj_ext_sent = 0;
|
||||
vj_ext_caps = 0;
|
||||
}
|
||||
|
||||
int VJExtActive(void)
|
||||
@@ -42,6 +48,43 @@ int VJExtActive(void)
|
||||
return vj_ext_on;
|
||||
}
|
||||
|
||||
int VJExtHasCap(unsigned cap)
|
||||
{
|
||||
return vj_ext_on && (vj_ext_caps & cap) == cap;
|
||||
}
|
||||
|
||||
int VJExtBiosPath(unsigned role, char *buf, unsigned size)
|
||||
{
|
||||
struct vj_env_bios_path q;
|
||||
size_t n;
|
||||
|
||||
if (!buf || size == 0)
|
||||
return 0;
|
||||
buf[0] = '\0';
|
||||
if (!VJExtHasCap(VJ_CAP_BIOS))
|
||||
return 0;
|
||||
q.role = role;
|
||||
q.path = 0;
|
||||
if (!vj_ext_env(VJ_ENV_GET_BIOS_PATH, &q) || !q.path || !q.path[0])
|
||||
return 0;
|
||||
n = strlen(q.path);
|
||||
if (n >= size)
|
||||
return 0;
|
||||
memcpy(buf, q.path, n + 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned VJExtBootTarget(void)
|
||||
{
|
||||
unsigned t = VJ_BOOT_CD_BIOS;
|
||||
|
||||
if (!VJExtHasCap(VJ_CAP_BIOS))
|
||||
return VJ_BOOT_CD_BIOS;
|
||||
if (!vj_ext_env(VJ_ENV_GET_BOOT_TARGET, &t))
|
||||
return VJ_BOOT_CD_BIOS;
|
||||
return (t == VJ_BOOT_CART_BOOT_ROM) ? VJ_BOOT_CART_BOOT_ROM : VJ_BOOT_CD_BIOS;
|
||||
}
|
||||
|
||||
void VJExtEmit(unsigned type, unsigned a, unsigned b, unsigned c, unsigned d,
|
||||
const char *s)
|
||||
{
|
||||
|
||||
@@ -14,18 +14,29 @@
|
||||
* caps_out). RetroArch and every other frontend answers false; the core
|
||||
* then latches the channel off and never calls VJ_ENV_EVENT at all, so
|
||||
* under RetroArch the whole feature costs one branch per would-be event.
|
||||
* - Synchronous, on the core's thread. VJ_ENV_EVENT is only ever issued
|
||||
* from inside retro_run() (the emulation thread), and the frontend's
|
||||
* handler runs inline. Nothing is queued, nothing is called from a
|
||||
* helper thread.
|
||||
* - Synchronous, on the core's thread. Everything is issued from the thread
|
||||
* that called retro_load_game() / retro_run() (the emulation thread), and
|
||||
* the frontend's handler runs inline. VJ_ENV_EVENT comes from retro_run()
|
||||
* except for the load-time VJ_EVT_BIOS_* events, which come from inside
|
||||
* retro_load_game() (or a disk-control insert). Nothing is queued, nothing is called
|
||||
* from a helper thread.
|
||||
* - Pointers (struct vj_event.s, struct vj_env_hello.core_version) are valid
|
||||
* for the duration of the call only. Copy what you keep.
|
||||
* - Events are informational. Existing log lines are unchanged and remain
|
||||
* the fallback for a frontend that never answered HELLO.
|
||||
* - The frontend's answer to VJ_ENV_EVENT is ignored.
|
||||
*
|
||||
* Adding an event type or a trailing struct field is compatible; anything
|
||||
* else bumps VJ_FRONTEND_EXT_ABI.
|
||||
* - Queries. A frontend that sets VJ_CAP_BIOS may also be asked, while the
|
||||
* core stages a BIOS (inside retro_load_game() or a disk-control insert,
|
||||
* never from a helper thread), which BIOS FILE to use (VJ_ENV_GET_BIOS_PATH) and what
|
||||
* a no-content boot should enter (VJ_ENV_GET_BOOT_TARGET). Both are
|
||||
* optional: a refusal (false, or an empty answer) leaves the core's own
|
||||
* logic -- system-folder search, then the built-in image -- exactly as it
|
||||
* was. The core validates whatever file it is handed (size, header or
|
||||
* checksum) and falls back the same way if it does not pass.
|
||||
*
|
||||
* Adding an event type, a command id, a capability bit or a trailing struct
|
||||
* field is compatible; anything else bumps VJ_FRONTEND_EXT_ABI.
|
||||
*/
|
||||
#ifndef VJ_FRONTEND_EXT_H
|
||||
#define VJ_FRONTEND_EXT_H
|
||||
@@ -42,9 +53,26 @@ extern "C" {
|
||||
#define VJ_ENV_BASE (0x5600 | RETRO_ENVIRONMENT_PRIVATE)
|
||||
#define VJ_ENV_HELLO (VJ_ENV_BASE + 0) /* data: struct vj_env_hello* */
|
||||
#define VJ_ENV_EVENT (VJ_ENV_BASE + 1) /* data: const struct vj_event* */
|
||||
#define VJ_ENV_GET_BIOS_PATH (VJ_ENV_BASE + 2) /* data: struct vj_env_bios_path*; needs VJ_CAP_BIOS */
|
||||
#define VJ_ENV_GET_BOOT_TARGET (VJ_ENV_BASE + 3) /* data: unsigned* (VJ_BOOT_*); needs VJ_CAP_BIOS */
|
||||
|
||||
/* Capability bits the frontend may report in caps_out. */
|
||||
#define VJ_CAP_EVENTS 0x1u /* will accept VJ_ENV_EVENT (always set by an answering frontend) */
|
||||
#define VJ_CAP_BIOS 0x2u /* answers VJ_ENV_GET_BIOS_PATH / VJ_ENV_GET_BOOT_TARGET */
|
||||
|
||||
/* BIOS roles, for VJ_ENV_GET_BIOS_PATH and the VJ_EVT_BIOS_* events. */
|
||||
#define VJ_BIOS_CART_BOOT 1u /* the 128 KB console boot ROM at $E00000 */
|
||||
#define VJ_BIOS_CD 2u /* the 256 KB Jaguar CD BIOS (mapped at $800000) */
|
||||
|
||||
/* What a no-content boot (retro_load_game(NULL)) enters. */
|
||||
#define VJ_BOOT_CD_BIOS 0u /* the CD BIOS with the CD unit attached (the default) */
|
||||
#define VJ_BOOT_CART_BOOT_ROM 1u /* the cartridge boot ROM against an empty slot */
|
||||
|
||||
struct vj_env_bios_path
|
||||
{
|
||||
unsigned role; /* in: VJ_BIOS_* */
|
||||
const char *path; /* out: file to use, valid for the call only; NULL/"" = no preference */
|
||||
};
|
||||
|
||||
struct vj_env_hello
|
||||
{
|
||||
@@ -59,10 +87,18 @@ enum vj_event_type
|
||||
VJ_EVT_LINK_DOWN, /* s = mode name ("tcp_client", "tcp_server", ...) */
|
||||
VJ_EVT_LINK_STATS, /* a = hops/s, b = mean hop ms x100, c = slipped hops, d = timeouts */
|
||||
VJ_EVT_WIRE_SPEED, /* a = multiplier (1 = stock), b = 1 confirmed / 0 back to stock */
|
||||
VJ_EVT_CRASH_WATCHDOG /* s = signature ("gpu_pc_escape", "video_stall", ...); a = frame number, b = the
|
||||
VJ_EVT_CRASH_WATCHDOG, /* s = signature ("gpu_pc_escape", "video_stall", ...); a = frame number, b = the
|
||||
PC / hash / B_COUNT the line reports (signature specific), c,d = extras */
|
||||
VJ_EVT_BIOS_STAGED, /* a = VJ_BIOS_* role, b = CRC32 of the image the core will run, c = where it came
|
||||
from (VJ_BIOS_SRC_*), s = the file path, or the built-in image's name */
|
||||
VJ_EVT_BIOS_REJECTED /* a = role, s = the path the frontend named; the core did not accept it (wrong
|
||||
size or not a BIOS) and fell back to its own search */
|
||||
};
|
||||
|
||||
#define VJ_BIOS_SRC_BUILTIN 0u /* the image compiled into the core */
|
||||
#define VJ_BIOS_SRC_SYSTEM 1u /* found by the core's own search of the system directory */
|
||||
#define VJ_BIOS_SRC_FRONTEND 2u /* the file the frontend named (VJ_ENV_GET_BIOS_PATH) */
|
||||
|
||||
struct vj_event
|
||||
{
|
||||
unsigned type; /* enum vj_event_type */
|
||||
@@ -77,6 +113,13 @@ void VJExtInit(retro_environment_t env_cb, const char *core_version);
|
||||
void VJExtDeinit(void);
|
||||
/* Nonzero once a frontend answered HELLO. */
|
||||
int VJExtActive(void);
|
||||
/* True when the answering frontend reported every bit of `cap`. */
|
||||
int VJExtHasCap(unsigned cap);
|
||||
/* Ask the frontend which file to use for `role` (VJ_ENV_GET_BIOS_PATH): copies the path into
|
||||
* buf and returns 1, or returns 0 (inactive, no VJ_CAP_BIOS, refused, empty, or too long for buf). */
|
||||
int VJExtBiosPath(unsigned role, char *buf, unsigned size);
|
||||
/* What a no-content boot should enter: VJ_BOOT_CD_BIOS unless an answering frontend says otherwise. */
|
||||
unsigned VJExtBootTarget(void);
|
||||
/* No-ops (one branch) while inactive. */
|
||||
void VJExtEmit(unsigned type, unsigned a, unsigned b, unsigned c, unsigned d,
|
||||
const char *s);
|
||||
|
||||
+205
-1
@@ -12,6 +12,13 @@
|
||||
* D) refuse again after B in the same process (the iOS static-state rule:
|
||||
* retro_deinit() drops the latch): silent again.
|
||||
*
|
||||
* Part 3: the BIOS queries (VJ_CAP_BIOS): which BIOS file to use, and what a
|
||||
* no-content boot enters. A frontend that refuses HELLO (RetroArch) is never
|
||||
* asked; one that answers without VJ_CAP_BIOS is never asked either; one that
|
||||
* names a file gets that image staged (and told so with VJ_EVT_BIOS_STAGED);
|
||||
* a file the core cannot accept is refused with VJ_EVT_BIOS_REJECTED and the
|
||||
* core's own search takes over.
|
||||
*
|
||||
* Part 2: m68k/risc clock-scale parsing. The listed values map exactly as
|
||||
* before; "175" / "175%" are an extra accepted form clamped to 25..400;
|
||||
* everything else the old ladder did not know stays 100.
|
||||
@@ -28,6 +35,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int g_fail = 0, g_checks = 0;
|
||||
#define CHECK(cond) \
|
||||
@@ -48,6 +57,12 @@ static char g_hello_version[64];
|
||||
static unsigned g_link_up_events;
|
||||
static unsigned g_link_up_device = 99;
|
||||
static char g_link_up_desc[64];
|
||||
/* Part 3 */
|
||||
static const char *g_bios_path[3]; /* by role; NULL = no preference */
|
||||
static unsigned g_boot_target;
|
||||
static unsigned g_path_calls[3], g_target_calls;
|
||||
static unsigned g_staged_n[3], g_staged_src[3], g_staged_crc[3], g_rejected_n[3];
|
||||
static char g_staged_s[3][512];
|
||||
|
||||
static bool env_hook(unsigned cmd, void *data)
|
||||
{
|
||||
@@ -61,12 +76,36 @@ static bool env_hook(unsigned cmd, void *data)
|
||||
}
|
||||
if (!g_answer_hello || !h)
|
||||
return false;
|
||||
h->caps_out = (g_answer_hello == 1) ? VJ_CAP_EVENTS : 0;
|
||||
h->caps_out = (g_answer_hello == 1) ? VJ_CAP_EVENTS
|
||||
: (g_answer_hello == 3) ? (VJ_CAP_EVENTS | VJ_CAP_BIOS) : 0;
|
||||
return true;
|
||||
}
|
||||
if (cmd == VJ_ENV_GET_BIOS_PATH) {
|
||||
struct vj_env_bios_path *q = (struct vj_env_bios_path *)data;
|
||||
if (!q || q->role < 1 || q->role > 2)
|
||||
return false;
|
||||
g_path_calls[q->role]++;
|
||||
if (!g_bios_path[q->role])
|
||||
return false;
|
||||
q->path = g_bios_path[q->role];
|
||||
return true;
|
||||
}
|
||||
if (cmd == VJ_ENV_GET_BOOT_TARGET) {
|
||||
g_target_calls++;
|
||||
*(unsigned *)data = g_boot_target;
|
||||
return true;
|
||||
}
|
||||
if (cmd == VJ_ENV_EVENT) {
|
||||
const struct vj_event *e = (const struct vj_event *)data;
|
||||
g_event_calls++;
|
||||
if (e && e->type == VJ_EVT_BIOS_STAGED && e->a >= 1 && e->a <= 2) {
|
||||
g_staged_n[e->a]++;
|
||||
g_staged_src[e->a] = e->c;
|
||||
g_staged_crc[e->a] = e->b;
|
||||
snprintf(g_staged_s[e->a], sizeof g_staged_s[e->a], "%s", e->s ? e->s : "(null)");
|
||||
}
|
||||
if (e && e->type == VJ_EVT_BIOS_REJECTED && e->a >= 1 && e->a <= 2)
|
||||
g_rejected_n[e->a]++;
|
||||
if (e && e->type == VJ_EVT_LINK_UP) {
|
||||
g_link_up_events++;
|
||||
g_link_up_device = e->a;
|
||||
@@ -225,10 +264,175 @@ static void part2(int argc, char **argv)
|
||||
harness_shutdown(&cfg);
|
||||
}
|
||||
|
||||
|
||||
/* ---- Part 3: BIOS queries --------------------------------------------------- */
|
||||
static unsigned crc32_of(const unsigned char *p, size_t n)
|
||||
{
|
||||
unsigned c = 0xFFFFFFFFu;
|
||||
size_t i;
|
||||
int k;
|
||||
for (i = 0; i < n; i++) {
|
||||
c ^= p[i];
|
||||
for (k = 0; k < 8; k++)
|
||||
c = (c >> 1) ^ (0xEDB88320u & (0u - (c & 1u)));
|
||||
}
|
||||
return ~c;
|
||||
}
|
||||
|
||||
/* Write n bytes of a recognisable pattern; the CD variant carries a plausible run address at $404. */
|
||||
static unsigned write_image(const char *path, size_t n, int cd)
|
||||
{
|
||||
unsigned char *b = (unsigned char *)malloc(n);
|
||||
unsigned crc;
|
||||
size_t i;
|
||||
FILE *f;
|
||||
for (i = 0; i < n; i++)
|
||||
b[i] = (unsigned char)((i * 7u + 13u) ^ (i >> 8));
|
||||
if (cd) {
|
||||
b[0x404] = 0x00; b[0x405] = 0x80; b[0x406] = 0x20; b[0x407] = 0x00;
|
||||
}
|
||||
f = fopen(path, "wb");
|
||||
if (f) { fwrite(b, 1, n, f); fclose(f); }
|
||||
crc = crc32_of(b, n);
|
||||
free(b);
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void reset_part3(void)
|
||||
{
|
||||
memset(g_bios_path, 0, sizeof g_bios_path);
|
||||
memset(g_path_calls, 0, sizeof g_path_calls);
|
||||
memset(g_staged_n, 0, sizeof g_staged_n);
|
||||
memset(g_staged_src, 0xFF, sizeof g_staged_src);
|
||||
memset(g_staged_crc, 0, sizeof g_staged_crc);
|
||||
memset(g_rejected_n, 0, sizeof g_rejected_n);
|
||||
memset(g_staged_s, 0, sizeof g_staged_s);
|
||||
g_boot_target = 0;
|
||||
g_target_calls = 0;
|
||||
}
|
||||
|
||||
static int start_nocontent(harness_config *cfg, int argc, char **argv, int answer)
|
||||
{
|
||||
harness_config fresh = HARNESS_CONFIG_DEFAULT;
|
||||
*cfg = fresh;
|
||||
cfg->frames = 1;
|
||||
cfg->quiet = 1;
|
||||
g_answer_hello = answer;
|
||||
g_hello_calls = g_event_calls = 0;
|
||||
harness_set_env_hook(env_hook);
|
||||
if (!harness_init_from_args(cfg, argc, argv))
|
||||
return 0;
|
||||
return harness_load_no_content(cfg) ? 1 : 0;
|
||||
}
|
||||
|
||||
static void part3(int argc, char **argv)
|
||||
{
|
||||
harness_config cfg;
|
||||
char dir[512], cdp[600], cartp[600], badp[600];
|
||||
unsigned cdcrc, cartcrc;
|
||||
const char *tmp = getenv("TMPDIR");
|
||||
|
||||
snprintf(dir, sizeof dir, "%s/vjbios_%d", (tmp && tmp[0]) ? tmp : "/tmp", (int)getpid());
|
||||
mkdir(dir, 0700);
|
||||
snprintf(cdp, sizeof cdp, "%s/cd.bin", dir);
|
||||
snprintf(cartp, sizeof cartp, "%s/boot.rom", dir);
|
||||
snprintf(badp, sizeof badp, "%s/short.bin", dir);
|
||||
cdcrc = write_image(cdp, 0x40000, 1);
|
||||
cartcrc = write_image(cartp, 0x20000, 0);
|
||||
write_image(badp, 1000, 0);
|
||||
|
||||
/* A: refusing frontend (RetroArch): never asked anything, and no event. */
|
||||
reset_part3();
|
||||
g_bios_path[2] = cdp; g_bios_path[1] = cartp; g_boot_target = 1;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 0));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_path_calls[1] == 0 && g_path_calls[2] == 0 && g_target_calls == 0);
|
||||
CHECK(g_staged_n[1] == 0 && g_staged_n[2] == 0);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* B: answers with events but not VJ_CAP_BIOS: still never asked; events say built-in. */
|
||||
reset_part3();
|
||||
g_bios_path[2] = cdp; g_boot_target = 1;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 1));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_path_calls[1] == 0 && g_path_calls[2] == 0 && g_target_calls == 0);
|
||||
CHECK(g_staged_n[2] == 1 && g_staged_src[2] == VJ_BIOS_SRC_BUILTIN);
|
||||
CHECK(g_staged_crc[2] == 0x687068D5u); /* the built-in retail CD BIOS */
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* C: BIOS cap, no preference: the default no-content boot is the CD BIOS. */
|
||||
reset_part3();
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_target_calls == 1 && g_path_calls[2] == 1);
|
||||
CHECK(g_staged_n[2] == 1 && g_staged_src[2] == VJ_BIOS_SRC_BUILTIN && g_staged_n[1] == 1);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* D: a CD BIOS file named by the frontend is the one staged. */
|
||||
reset_part3();
|
||||
g_bios_path[2] = cdp;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_staged_n[2] == 1 && g_staged_src[2] == VJ_BIOS_SRC_FRONTEND);
|
||||
CHECK(g_staged_crc[2] == cdcrc);
|
||||
CHECK(strcmp(g_staged_s[2], cdp) == 0);
|
||||
CHECK(g_rejected_n[2] == 0);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* E: a file the core cannot accept is reported and the built-in image takes over. */
|
||||
reset_part3();
|
||||
g_bios_path[2] = badp;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_rejected_n[2] == 1);
|
||||
CHECK(g_staged_n[2] == 1 && g_staged_src[2] == VJ_BIOS_SRC_BUILTIN);
|
||||
harness_shutdown(&cfg);
|
||||
reset_part3();
|
||||
g_bios_path[2] = "/nonexistent/cd.bin";
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 30);
|
||||
CHECK(g_rejected_n[2] == 1 && g_staged_src[2] == VJ_BIOS_SRC_BUILTIN);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* F: boot target = cartridge boot ROM: the cart ROM is staged (the named file), the CD BIOS is not. */
|
||||
reset_part3();
|
||||
g_boot_target = VJ_BOOT_CART_BOOT_ROM;
|
||||
g_bios_path[1] = cartp;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 60);
|
||||
CHECK(g_staged_n[1] == 1 && g_staged_src[1] == VJ_BIOS_SRC_FRONTEND && g_staged_crc[1] == cartcrc);
|
||||
CHECK(g_staged_n[2] == 0 && g_path_calls[2] == 0);
|
||||
CHECK(cfg.current_frame >= 60);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* G: cart target with no file: the built-in Series K runs. */
|
||||
reset_part3();
|
||||
g_boot_target = VJ_BOOT_CART_BOOT_ROM;
|
||||
CHECK(start_nocontent(&cfg, argc, argv, 3));
|
||||
run_frames(&cfg, 60);
|
||||
CHECK(g_staged_n[1] == 1 && g_staged_src[1] == VJ_BIOS_SRC_BUILTIN && g_staged_crc[1] == 0xFB731AAAu);
|
||||
CHECK(g_staged_n[2] == 0);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
/* H: cart content: the boot target is never asked; the cart boot ROM file is (and wins even in HLE). */
|
||||
reset_part3();
|
||||
g_bios_path[1] = cartp;
|
||||
CHECK(start(&cfg, argc, argv, 3, NULL));
|
||||
run_frames(&cfg, 10);
|
||||
CHECK(g_target_calls == 0);
|
||||
CHECK(g_staged_n[1] == 1 && g_staged_src[1] == VJ_BIOS_SRC_FRONTEND && g_staged_crc[1] == cartcrc);
|
||||
harness_shutdown(&cfg);
|
||||
|
||||
harness_set_env_hook(NULL);
|
||||
unlink(cdp); unlink(cartp); unlink(badp);
|
||||
rmdir(dir);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
part1(argc, argv);
|
||||
part2(argc, argv);
|
||||
part3(argc, argv);
|
||||
printf("frontend_ext: %d checks, %d failed\n", g_checks, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user