Files
parallel-dc/core/nullDC.cpp
T
LibretroAdmin a2343b3fbf naomi: a flat image starts without a BIOS
With no naomi.zip (naomi2.zip) a NAOMI game stopped at "cannot load
BIOS". A flat image now starts all the same: reios does for it what the
BIOS does. The BIOS is used as before when it is there, and a romset
still needs one.

What that turned out to be - found by letting the real BIOSes
(epr-21576h, and the NAOMI 2's epr-23605c) run up to a game's first
instruction and looking at what they leave, by reading the BIOS's code
for the parts games call, and by tracing Dead or Alive 2 and Virtua
Fighter 4 with the BIOS and without until the two runs went the same
way:

The start. The image's header says what to load where (up to eight
pieces at 0x360) and where the game begins (0x420). The BIOS hands over
with the display in its 640x480 mode, a copy of the header at 0c01f400,
the clock's time at 0c01f130, and registers pointing at both.

The system routines. The BIOS leaves a table of 24 routines at 0c018000
that games call for what every cabinet does alike: the operator's
settings and the check of them, the 28 coin settings, coins into
credits (bonus coins, service credits, the most credits, the meters,
the queue of sounds), what each player can do with the credits there
are, the board's region and the game's serial. They are written here
from what the BIOS's do; a routine's place in the table holds the trap
reios answers. The ones for the GD-ROM's DIMM board and the network
board answer 0 and say so once: no cartridge game seen calls them.
There being no test menu, the settings are the BIOS's defaults: one
coin, one credit.

The interrupt service. A game never takes the processor's vectors: the
vector base stays at the BIOS's 0c000000, and the game writes its
handlers into the BIOS's table at 0c0001c0. The vectors are traps here:
one puts the interrupted program's registers by, in the machine's own
memory so that a saved state has them, and sends the processor to the
handler; the handler returns to the other, which puts them back.

Two things a game looks at in the boot ROM itself:

- Before it will use the routines, a game compares 112 bytes at 1ffd00
  with a copy it carries - the BIOS's "COPYRIGHT (C)SEGA ENTERPRISES"
  lines and "NAOMI BOOT ROM" - and otherwise goes a way that never
  ends. Those bytes are put there. (Both games carry the same copy.)
- Dead or Alive 2's start-up code waits for a word that it reads
  through a pointer it has not yet set, which makes it the boot ROM's
  word at fc0. It is given the value the BIOS has there.

naomi_cart: bios_loaded is set when a flat image's BIOS was found; it
was only set for romsets, and nothing had asked until now.

Tested with no BIOS in the system directory (RetroArch nightly, glcore,
and the headless harness): Dead or Alive 2, Dead or Alive 2 Millennium
(from its RAR archive) and Virtua Fighter 4 (NAOMI 2) reach their
attract modes; a coin in Virtua Fighter 4 gives CREDIT(S) 1 and PRESS
START BUTTON, as it does with the BIOS; a reset starts the game again;
a state saved at frame 1400 and loaded at 1800 leaves the machine, 800
frames on, where 2200 frames straight leave it but for 270 bytes of
92 MB - and that state was taken inside an interrupt handler. With the
BIOS all three run as before, and the Dreamcast test disc passes.

Not done: romsets (the cartridge would have to be read through its
decryption), the test menu, and so any setting but the defaults. Of the
coin routines only the paths the defaults take have been run: bonus
coins, a chute to each player and service credits are as the BIOS's
code has them, and not exercised.
2026-10-08 06:45:09 +02:00

611 lines
16 KiB
C++

// nullDC.cpp : Makes magic cookies
//
//initialse Emu
#include <retro_atomic.h>
#include "types.h"
#include "hw/mem/_vmem.h"
#include "hw/mem/vmem32.h"
#include "stdclass.h"
#include "types.h"
#include "hw/flashrom/flashrom.h"
#include "hw/maple/maple_cfg.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/arm7/arm7.h"
#include "hw/naomi/naomi_cart.h"
#include "reios/reios.h"
extern bool bios_loaded;
#include <libretro.h>
extern RomChip sys_rom;
extern SRamChip sys_nvmem_sram;
extern DCFlashChip sys_nvmem_flash;
unsigned FLASH_SIZE;
unsigned BBSRAM_SIZE;
unsigned BIOS_SIZE;
unsigned RAM_SIZE;
unsigned ARAM_SIZE;
unsigned VRAM_SIZE;
unsigned RAM_MASK;
unsigned ARAM_MASK;
unsigned VRAM_MASK;
settings_t settings;
extern char game_dir[1024];
extern char *game_data;
extern bool boot_to_bios;
extern retro_atomic_int_t reset_requested;
/*
libndc
//initialise (and parse the command line)
ndc_init(argc,argv);
...
//run a dreamcast slice
//either a frame, or up to 25 ms of emulation
//returns 1 if the frame is ready (fb needs to be flipped -- i'm looking at you android)
ndc_step();
...
//terminate (and free everything)
ndc_term()
*/
int GetFile(char *szFileName, char *szParse=0,u32 flags=0)
{
/* No disc image when the content is an ELF: the drive stays empty */
if (!boot_to_bios && game_data)
{
strcpy(szFileName, game_data);
strcpy(settings.imgread.DefaultImage, szFileName);
}
return 1;
}
/* On failure nothing stays initialised: each plugin brought up before the
* failing one is terminated again, so the caller sees the same state it
* started from. */
s32 plugins_Init()
{
s32 rv;
if ((rv = libPvr_Init()))
return rv;
if ((rv = libGDR_Init()))
goto fail_pvr;
if (settings.System != DC_PLATFORM_DREAMCAST)
{
if (!naomi_cart_SelectFile())
{
rv = -1;
goto fail_gdr;
}
}
if ((rv = libAICA_Init()))
goto fail_gdr;
if ((rv = libARM_Init()))
goto fail_aica;
return 0;
fail_aica:
libAICA_Term();
fail_gdr:
naomi_cart_Close();
libGDR_Term();
fail_pvr:
libPvr_Term();
return rv;
}
void plugins_Term(void)
{
//term all plugins
//libExtDevice_Term();
//arm7_Term ?
libAICA_Term();
libGDR_Term();
libPvr_Term();
}
void plugins_Reset(bool hard)
{
libPvr_Reset(hard);
libGDR_Reset(hard);
libAICA_Reset(hard);
libARM_Reset(hard);
//libExtDevice_Reset(Manual);
}
#include "rom_luts.h"
static void LoadSpecialSettingsCPU(void)
{
#if FEAT_SHREC != DYNAREC_NONE
if(settings.dynarec.Enable)
{
Get_Sh4Recompiler(&sh4_cpu);
INFO_LOG(DYNAREC, "Using Recompiler");
}
else
#endif
{
Get_Sh4Interpreter(&sh4_cpu);
INFO_LOG(INTERPRETER, "Using Interpreter");
}
sh4_cpu.Reset(false);
}
static void LoadSpecialSettings(void)
{
unsigned i;
char prod_id[sizeof(ip_meta.product_number) + 1] = {0};
memcpy(prod_id, ip_meta.product_number, sizeof(ip_meta.product_number));
NOTICE_LOG(BOOT, "[LUT]: Product number: %s.", prod_id);
if (ip_meta.isWindowsCE() || settings.dreamcast.ForceWinCE
|| !strncmp("T26702N", prod_id, 7)) // PBA Tour Bowling 2001
{
NOTICE_LOG(BOOT, "Enabling Full MMU and Extra depth scaling for Windows CE game");
settings.rend.ExtraDepthScale = 0.1;
settings.dreamcast.FullMMU = true;
}
for (i = 0; i < sizeof(lut_games)/sizeof(lut_games[0]); i++)
{
if (!strncmp(lut_games[i].product_number, prod_id, sizeof(prod_id) - 1))
{
INFO_LOG(BOOT, "[LUT]: Found game in LUT database..");
if (lut_games[i].alpha_sort_mode != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying alpha sort hack.");
settings.pvr.Emulation.AlphaSortMode = lut_games[i].alpha_sort_mode;
}
if (lut_games[i].translucentPolygonDepthMask != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying translucent polygon depth mask hack.");
settings.rend.TranslucentPolygonDepthMask = lut_games[i].translucentPolygonDepthMask;
}
if (lut_games[i].disable_div != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying Disable DIV hack.");
settings.dynarec.ForceDisableDivMatching = settings.dynarec.DisableDivMatching = lut_games[i].disable_div;
}
if (lut_games[i].extra_depth_scale != 1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying auto extra depth scale.");
settings.rend.ExtraDepthScale = lut_games[i].extra_depth_scale;
}
if (lut_games[i].disable_vmem32 == 1)
{
NOTICE_LOG(BOOT, "[Hack]: Disabling WinCE virtual memory.");
settings.dynarec.disable_vmem32 = true;
}
if (lut_games[i].no_rgb_component == 1)
{
NOTICE_LOG(BOOT, "Disabling RGB component output");
settings.dreamcast.cable = 3; // TV composite
}
break;
}
}
std::string areas(ip_meta.area_symbols, sizeof(ip_meta.area_symbols));
bool region_usa = areas.find('U') != std::string::npos;
bool region_eu = areas.find('E') != std::string::npos;
bool region_japan = areas.find('J') != std::string::npos;
if (region_usa || region_eu || region_japan)
{
switch (settings.dreamcast.region)
{
case 0: // Japan
if (!region_japan)
{
NOTICE_LOG(BOOT, "Japan region not supported. Using %s instead", region_usa ? "USA" : "Europe");
settings.dreamcast.region = region_usa ? 1 : 2;
}
break;
case 1: // USA
if (!region_usa)
{
NOTICE_LOG(BOOT, "USA region not supported. Using %s instead", region_eu ? "Europe" : "Japan");
settings.dreamcast.region = region_eu ? 2 : 0;
}
break;
case 2: // Europe
if (!region_eu)
{
NOTICE_LOG(BOOT, "Europe region not supported. Using %s instead", region_usa ? "USA" : "Japan");
settings.dreamcast.region = region_usa ? 1 : 0;
}
break;
case 3: // Default
if (region_usa)
settings.dreamcast.region = 1;
else if (region_eu)
settings.dreamcast.region = 2;
else
settings.dreamcast.region = 0;
break;
}
}
else
WARN_LOG(BOOT, "No region specified in IP.BIN");
if (settings.dreamcast.cable <= 1 && !ip_meta.supportsVGA())
{
NOTICE_LOG(BOOT, "Game doesn't support VGA. Using TV Composite instead");
settings.dreamcast.cable = 3;
}
}
static void LoadSpecialSettingsNaomi(const char *name)
{
unsigned i;
NOTICE_LOG(BOOT, "[LUT]: Naomi ROM name is: %s.", name);
for (i = 0; i < sizeof(lut_games_naomi)/sizeof(lut_games_naomi[0]); i++)
{
if (strstr(lut_games_naomi[i].product_number, name))
{
INFO_LOG(BOOT, "[LUT]: Found game in LUT database..");
if (lut_games_naomi[i].alpha_sort_mode != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying alpha sort hack.");
settings.pvr.Emulation.AlphaSortMode = lut_games_naomi[i].alpha_sort_mode;
}
if (lut_games_naomi[i].translucentPolygonDepthMask != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying translucent polygon depth mask hack.");
settings.rend.TranslucentPolygonDepthMask = lut_games_naomi[i].translucentPolygonDepthMask;
}
if (lut_games_naomi[i].disable_div != -1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying Disable DIV hack.");
settings.dynarec.ForceDisableDivMatching = settings.dynarec.DisableDivMatching = lut_games_naomi[i].disable_div;
}
if (lut_games_naomi[i].jamma_setup != JVS::Default)
{
NOTICE_LOG(BOOT, "[Hack]: Applying alternate Jamma I/O board setup.");
settings.mapping.JammaSetup = lut_games_naomi[i].jamma_setup;
}
if (lut_games_naomi[i].extra_depth_scale != 1)
{
NOTICE_LOG(BOOT, "[Hack]: Applying auto extra depth scale.");
settings.rend.ExtraDepthScale = lut_games_naomi[i].extra_depth_scale;
}
if (lut_games_naomi[i].game_inputs != NULL)
{
NOTICE_LOG(BOOT, "Setting custom input descriptors\n");
naomi_game_inputs = lut_games_naomi[i].game_inputs;
}
break;
}
}
}
void dc_prepare_system()
{
BBSRAM_SIZE = (32*1024);
switch (settings.System)
{
case DC_PLATFORM_DREAMCAST:
//DC : 16 mb ram, 8 mb vram, 2 mb aram, 2 mb bios, 128k flash
FLASH_SIZE = (128*1024);
BIOS_SIZE = (2*1024*1024);
RAM_SIZE = (16*1024*1024);
ARAM_SIZE = (2*1024*1024);
VRAM_SIZE = (8*1024*1024);
sys_nvmem_flash.Allocate(FLASH_SIZE);
sys_rom.Allocate(BIOS_SIZE);
break;
case DC_PLATFORM_DEV_UNIT:
//Devkit : 32 mb ram, 8? mb vram, 2? mb aram, 2? mb bios, ? flash
FLASH_SIZE = (128*1024);
BIOS_SIZE = (2*1024*1024);
RAM_SIZE = (32*1024*1024);
ARAM_SIZE = (2*1024*1024);
VRAM_SIZE = (8*1024*1024);
sys_nvmem_flash.Allocate(FLASH_SIZE);
sys_rom.Allocate(BIOS_SIZE);
break;
case DC_PLATFORM_NAOMI:
//Naomi : 32 mb ram, 16 mb vram, 8 mb aram, 2 mb bios, ? flash
BIOS_SIZE = (2*1024*1024);
RAM_SIZE = (32*1024*1024);
ARAM_SIZE = (8*1024*1024);
VRAM_SIZE = (16*1024*1024);
sys_nvmem_sram.Allocate(BBSRAM_SIZE);
sys_rom.Allocate(BIOS_SIZE);
break;
case DC_PLATFORM_NAOMI2:
//Naomi2 : 32 mb ram, 16 mb vram, 8 mb aram, 2 mb bios, ? flash
BIOS_SIZE = (2*1024*1024);
RAM_SIZE = (32*1024*1024);
ARAM_SIZE = (8*1024*1024);
VRAM_SIZE = (16*1024*1024);
sys_nvmem_sram.Allocate(BBSRAM_SIZE);
sys_rom.Allocate(BIOS_SIZE);
break;
case DC_PLATFORM_ATOMISWAVE:
//Atomiswave : 16 mb ram, 8 mb vram, 8 mb aram, 128kb bios+flash, 128k BBSRAM
FLASH_SIZE = 0;
BIOS_SIZE = (128*1024);
RAM_SIZE = (16*1024*1024);
ARAM_SIZE = (8*1024*1024);
VRAM_SIZE = (8*1024*1024);
BBSRAM_SIZE = (128*1024);
sys_nvmem_flash.Allocate(BIOS_SIZE);
sys_nvmem_flash.write_protect_size = BIOS_SIZE / 2;
sys_nvmem_sram.Allocate(BBSRAM_SIZE);
break;
}
RAM_MASK = (RAM_SIZE-1);
ARAM_MASK = (ARAM_SIZE-1);
VRAM_MASK = (VRAM_SIZE-1);
}
void dc_reset(bool hard)
{
plugins_Reset(hard);
mem_Reset(hard);
sh4_cpu.Reset(hard);
}
/* Stages of dc_init() that must be undone when a later one fails. */
enum
{
DC_INIT_SETUP, /* exception handlers installed */
DC_INIT_VMEM, /* + address space reserved */
DC_INIT_CPU, /* + sh4, memory map, unwind table */
DC_INIT_PLUGINS /* + plugins, cartridge */
};
static void dc_init_unwind(int stage)
{
extern void common_libretro_cleanup(void);
switch (stage)
{
case DC_INIT_PLUGINS:
naomi_cart_Close();
plugins_Term();
/* fall through */
case DC_INIT_CPU:
sh4_cpu.Term();
mem_Term();
/* fall through */
case DC_INIT_VMEM:
_vmem_release();
/* fall through */
case DC_INIT_SETUP:
common_libretro_cleanup();
break;
}
}
int dc_init()
{
extern void common_libretro_setup(void);
extern char game_dir_no_slash[1024];
char new_system_dir[1024];
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
common_libretro_setup();
if (!_vmem_reserve())
{
ERROR_LOG(VMEM, "Failed to alloc mem");
dc_init_unwind(DC_INIT_SETUP);
return -1;
}
reios_init();
LoadSettings();
#ifdef _WIN32
sprintf(new_system_dir, "%s\\", game_dir_no_slash);
#else
sprintf(new_system_dir, "%s/", game_dir_no_slash);
#endif
if (settings.System == DC_PLATFORM_DREAMCAST)
{
if (settings.bios.UseReios || !LoadRomFiles(new_system_dir))
{
/* Booting the BIOS requires a BIOS file */
if (boot_to_bios || !LoadHle(new_system_dir))
{
dc_init_unwind(DC_INIT_VMEM);
return -3;
}
WARN_LOG(COMMON, "Did not load bios, using reios");
}
}
else
{
LoadRomFiles(new_system_dir);
}
LoadSpecialSettingsCPU();
sh4_cpu.Init();
mem_Init();
#ifdef _WIN64
{
extern void setup_seh();
setup_seh();
}
#endif
if (plugins_Init())
{
dc_init_unwind(DC_INIT_CPU);
return -4;
}
if (SYSTEM_IS_NAOMI() && !bios_loaded)
{
/* A NAOMI game with no BIOS to start it (a flat image is let
* through without one): the boot ROM is reios's, which does what
* the BIOS does to hand over to the game. */
LoadHle(new_system_dir);
WARN_LOG(COMMON, "No NAOMI BIOS, using reios");
}
mem_map_default();
dc_reset(true);
switch (settings.System)
{
case DC_PLATFORM_DREAMCAST:
if (libGDR_GetDiscType() == NoDisk && settings.reios.ElfFile.empty())
{
/* Content loading failed so force HLE off and boot the BIOS */
settings.bios.UseReios = false;
if (!LoadRomFiles(new_system_dir))
{
dc_init_unwind(DC_INIT_PLUGINS);
return -3;
}
settings.imgread.DefaultImage[0] = '\0';
}
reios_disk_id();
LoadSpecialSettings();
break;
case DC_PLATFORM_ATOMISWAVE:
case DC_PLATFORM_NAOMI:
case DC_PLATFORM_NAOMI2:
LoadSpecialSettingsNaomi(naomi_game_id);
break;
}
FixUpFlash();
mcfg_CreateDevices();
return 0;
}
void dc_stop();
/*
* Hard backstop guaranteeing dc_run() returns within a bounded emulated-cycle
* budget so retro_run() can never be starved of control.
*
* Control normally returns at vblank (os_DoEvents), on whichever thread runs
* the machine. That
* relies on the SPG raising vblank, which needs valid video timing; before the
* BIOS programs it, or if a title misprograms vstart, vblank may not fire and
* dc_run() would otherwise run unbounded. This scheduler event forces a stop
* after at most FRAME_DEADLINE_CYCLES and is re-armed at every dc_run() entry,
* so under normal operation vblank always fires first (well inside the budget)
* and it never triggers.
*/
#define FRAME_DEADLINE_CYCLES (SH4_MAIN_CLOCK / 20) /* >= ~2.5 frames; catches a stalled vblank */
static int frame_deadline_schid = -1;
static int frame_deadline_sched(int tag, int c, int j)
{
dc_stop();
return 0; /* one-shot; re-armed by dc_run() */
}
void dc_run()
{
if (frame_deadline_schid == -1)
frame_deadline_schid = sh4_sched_register(0, &frame_deadline_sched);
sh4_sched_request(frame_deadline_schid, FRAME_DEADLINE_CYCLES);
sh4_cpu.Run();
}
void dc_term()
{
/* Remove the exception handler and dynarec unwind table before freeing the
* code cache and vmem they reference, so a stale handler can't fire during
* or after teardown. */
extern void common_libretro_cleanup(void);
common_libretro_cleanup();
SaveRomFiles(get_writable_data_path(""));
sh4_cpu.Term();
naomi_cart_Close();
plugins_Term();
mem_Term();
_vmem_release();
}
void dc_stop()
{
sh4_cpu.Stop();
}
bool dc_is_running()
{
return sh4_cpu.IsCpuRunning();
}
// Called on the emulator thread for soft reset
void dc_request_reset()
{
retro_atomic_store_release_int(&reset_requested, 1);
dc_stop();
}
void LoadSettings(void)
{
settings.dynarec.Enable = 1;
//settings.dynarec.DisableDivMatching = 0;
//disable_nvmem can't be loaded, because nvmem init is before cfg load
settings.dynarec.disable_vmem32 = false;
settings.dreamcast.FullMMU = false;
//settings.pvr.Emulation.AlphaSortMode= 0;
settings.rend.ExtraDepthScale = 1.f;
settings.rend.TranslucentPolygonDepthMask = false;
settings.network.ActAsServer = false;
settings.network.dns = "46.101.91.123"; // Dreamcast Live DNS
settings.network.server = "";
settings.network.EmulateBBA = false;
}