Files
LibretroAdmin c652c082c2 pvr/spg: a frame ends where the game renders, or reads its pads, after its vertical blank in interrupt
A retro_run - where the frontend reads its pad and shows the picture -
ended at line 0. Most games start the render of the frame they have
made, and read their pads, in their vertical blank in interrupt, which
need not be there:

- Sega Rally 2 (Windows CE) has its interrupt at line 0 and starts its
  render a line after it: that render was the first thing in the next
  run, and its picture was shown at that run's end, a frame after the
  game started it.
- Virtua Fighter 3tb and Jet Grind Radio have theirs at line 260: they
  render, then read the pads. Read in the middle of a run, the pads were
  read a run before the render that answered them could end one.

Now a run may end in the lines after the vertical blank in interrupt
(32, and never on or past the vertical blank out line, where the
hardware's maple transfer reads the pads): at a render to the screen the
game starts there - its picture is shown at once - or, before one, at a
maple transfer the game starts there, which is then made at the start of
the next run, with that run's pad. No time goes by between two runs, so
the transfer is made where the machine stands, as the game started it;
only the processor's last few hundred cycles of its time slice run before
it. A run that ended in a window ends in the next one, at either, or at
the window's end in a frame with neither (a game at 30 frames a second, a
loading screen), unless it rendered to the screen elsewhere in that frame:
then it goes on to line 0, where the runs of a game that does nothing in
the window (renders mid-frame, has the hardware read its pads) end as
before - but not past the next window: where line 0 came while the window
was open (Sega Rally 2's interrupt is at line 0), it ends at the window's
end. Each run has one vertical blank in interrupt in it. A window closed
early has the line event aimed again: one aimed at its end and taken for
a later line lost lines - a frame 13 lines (Sega Rally 2) or 233 lines
(Jet Grind Radio) short.

Measured with tools/lag: machine frames from the press to the first
picture that differs, RetroArch's default ("late") polling, the same
with threaded rendering on; this core before / after, and upstream
flycast with "Delay Frame Swapping" on (its default) / off, from states
each core made by booting the game the same way:

  Sega Rally 2, title, START          4 / 3      upstream 4 / 3
  Virtua Fighter 3tb, attract, START  3 / 2      upstream 3 / 2
  Jet Grind Radio, title, START       3-4 / 2-3  upstream 5 / 3
  Dead or Alive 2 Millennium, START   1 / 1      upstream 4 / 3
  Jet Grind Radio, street (s10), jump 3-4 / 2-3
  Jet Grind Radio, graffiti (s9), jump 3-4 / 2-3
  Virtua Fighter 4, START; DOA2, COIN 1-2 / 1-2  (they render before their
                                                 interrupt and read their
                                                 pads after line 0)

(Jet Grind Radio runs at 30 frames a second: the lag depends on which of
its two frames the press comes in, and is one frame less in the one of
them that was the longer.)

Where a run ends is decided by the machine alone, the same however the
frame is shown or not (a frame run ahead, fast-forward's frameskip): a
run starting in a window is one that ended in a window, a transfer
waiting for the next run is one started with nothing scheduled to end it
(it is made at the start of the next run, whether or not a state was
loaded in between), no run ends with the window open (dc_run()'s
backstop closes it too), and whether the game has started a render since
the last vertical blank - which the vertical blank goes by to show a
framebuffer the game wrote itself, and which was never so at line 0 - is
now in the state: bit 1 of pvr_serialize_v22()'s first byte, 0 in every
state before, which a state saved at line 0 has too. The state's size is
the same, and old states load as before. Without that bit, a frame run
ahead on the live test disc, which writes its framebuffer itself, was not
the frame run straight through (tools/threads/headless.sh).

tools/determinism (plain, again, reload, run-ahead, rollback, and frames
not shown, every one or RetroArch's run-ahead way) passes with threaded
rendering on and off on Jet Grind Radio, Sega Rally 2, Virtua Fighter
3tb, Virtua Fighter 4, Dead or Alive 2 and the live test disc;
tools/threads/headless.sh passes; tools/lag/samestep.sh has every
renderer in step on Jet Grind Radio, Sega Rally 2, Virtua Fighter 4 and
Dead or Alive 2. The sound each retro_run makes stays a frame's on
average, with no drift: over 1800 frames 735/736 samples a run in the
Dreamcast games and 737/738 in the NAOMI ones, as before; Sega Rally 2
alternates 713-718 and 754-759 where it renders every other frame (the
run ends at its render one frame and at the window's end the next), and
the first run after loading a state saved at line 0 is shorter, once.

A frame is a duplicate (video_cb with no picture) when no picture was
made in its run: a render to the screen or of the framebuffer the game
wrote itself. That was asked of the vblank, which no longer always ends
the run; a render to a texture alone no longer counts as a new picture.
2026-10-24 06:01:45 +02:00

784 lines
23 KiB
C++

// nullDC.cpp : Makes magic cookies
//
//initialse Emu
#include <retro_atomic.h>
#include "types.h"
#include "hw/mem/_vmem.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/pvr/spg.h"
#include "hw/maple/maple_if.h"
#include "hw/arm7/arm7.h"
#include "hw/naomi/naomi_cart.h"
#include "reios/reios.h"
#include "reios/gdrom_hle.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"
/* What a game's own settings put in place of the options': the options
* are read again on a reset and whenever one of them is changed, and these
* are put back after them (dc_apply_game_settings()) for as long as the
* game is loaded. -1: the option's. */
static int game_cable = -1;
static int game_alpha_sort_mode = -1;
/* A broadcast standard a game is started in is written to the flash's
* factory settings for as long as it runs; what was there is put back
* before the flash is saved, for the games that come after. */
static int game_broadcast = -1;
static u8 flash_broadcast[2];
/* The same for a language, which is also in the user's settings in the
* flash: -1 there when the flash had none to put back. */
static int game_language = -1;
static u8 flash_language[2];
static int flash_user_language = -1;
static void forget_game_settings(void)
{
game_cable = -1;
game_alpha_sort_mode = -1;
game_broadcast = -1;
game_language = -1;
flash_user_language = -1;
}
void dc_apply_game_settings(void)
{
if (game_cable >= 0)
settings.dreamcast.cable = game_cable;
if (game_alpha_sort_mode >= 0)
settings.pvr.Emulation.AlphaSortMode = game_alpha_sort_mode;
}
static void LoadSpecialSettingsCPU(void)
{
#if FEAT_SHREC != DYNAREC_NONE
#if defined(HOST_APPLE_JIT) && FEAT_SHREC == DYNAREC_JIT
// (no memory for code: a frontend without the JIT entitlement)
if (settings.dynarec.Enable && !recSh4_HaveCodeCache())
settings.dynarec.Enable = 0;
#endif
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));
/* the product number without the spaces it is padded with */
std::string id(prod_id);
id.erase(id.find_last_not_of(' ') + 1);
const u32 cable_option = settings.dreamcast.cable;
const u32 alpha_sort_option = settings.pvr.Emulation.AlphaSortMode;
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;
}
// (with the RGB cable only: what VGA or TV gives such a game is its own)
if (lut_games[i].no_rgb_component == 1 && settings.dreamcast.cable == 2)
{
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()
|| id == "T-12504N" // Caesar's Palace 2000 (US)
|| id == "12502D-50")) // Caesar's Palace 2000 (EU)
{
NOTICE_LOG(BOOT, "Game doesn't support VGA. Using TV Composite instead");
settings.dreamcast.cable = 3;
}
if (settings.dreamcast.cable == 2
&& (id == "T22901N" // Roadsters (US)
|| id == "T28202M" // Shin Nihon Pro Wrestling 4: stops with RGB
|| id == "T9512N")) // The Grinch (US)
{
NOTICE_LOG(BOOT, "Game doesn't support RGB. Using TV Composite instead");
settings.dreamcast.cable = 3;
}
if (id == "T-9707N" // San Francisco Rush 2049 (US)
|| id == "MK-51146" // Sega Smash Pack - Volume 1
|| id == "T-9702D-50" // Hydro Thunder (PAL)
|| id == "T41601N" // Elemental Gimmick Gear (US)
|| id == "T-8116N" // South Park Rally (US)
|| id == "T1206N") // JoJo's Bizarre Adventure (US)
game_broadcast = 0;
else if (id == "T-9709D-50" // San Francisco Rush 2049 (EU)
|| id == "T-8112D-50" // South Park Rally (EU)
|| id == "T7014D 50" // Super Runabout (EU)
|| id == "T10001D 50" // MTV Sports - Skateboarding (PAL)
|| id == "MK-5101050" // Snow Surfers
|| id == "12502D-50" // Caesar's Palace 2000 (EU)
|| id == "T46605D 80") // Evil Twin - Cyprien's Chronicles
game_broadcast = 1;
if (game_broadcast >= 0)
{
NOTICE_LOG(BOOT, "Forcing %s broadcasting", game_broadcast == 0 ? "NTSC" : "PAL");
settings.dreamcast.broadcast = game_broadcast;
flash_broadcast[0] = sys_nvmem_flash.data[0x1a004];
flash_broadcast[1] = sys_nvmem_flash.data[0x1a0a4];
}
if (id == "T-8102N") // TrickStyle (US)
{
NOTICE_LOG(BOOT, "Forcing English language");
game_language = 1;
settings.dreamcast.language = game_language;
flash_language[0] = sys_nvmem_flash.data[0x1a003];
flash_language[1] = sys_nvmem_flash.data[0x1a0a3];
flash_syscfg_block syscfg;
if (sys_nvmem_flash.ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg))
flash_user_language = syscfg.lang;
}
if (settings.dreamcast.cable != cable_option)
game_cable = settings.dreamcast.cable;
if (settings.pvr.Emulation.AlphaSortMode != alpha_sort_option)
game_alpha_sort_mode = settings.pvr.Emulation.AlphaSortMode;
}
/* The games that do not start, or do not work, with one of the two BIOSes:
* the real one when its file is there, the HLE one. The ROM is put in place
* of the other after the reset, before anything has run. */
static void ChooseGameBios(const char *system_dir, bool hle_bios)
{
char prod_id[sizeof(ip_meta.product_number) + 1] = {0};
memcpy(prod_id, ip_meta.product_number, sizeof(ip_meta.product_number));
std::string id(prod_id);
id.erase(id.find_last_not_of(' ') + 1);
if (hle_bios
&& (id == "T7001D 50" // Jimmy White's 2: Cueball
|| id == "T40505D 50" // Railroad Tycoon II (EU)
|| id == "T18702M" // Miss Moonlight
|| id == "T0019M" // KenJu (Atomiswave conversion)
|| id == "T0020M" // Force Five (Atomiswave conversion)
|| id == "HDR-0187" // Fushigi no Dungeon: Fuurai no Shiren Gaiden
|| id == "T15104D 50" // Slave Zero (PAL)
|| id == "MK-51152" // World Series Baseball 2K2
|| ip_meta.isMILCD())) // a MIL-CD: the HLE BIOS cannot start one
{
if (LoadRomFiles(system_dir))
{
NOTICE_LOG(BOOT, "Forcing real BIOS");
settings.bios.UseReios = false;
}
else
{
/* (a file that was there and could not be read whole may have
* been read in part over the HLE BIOS) */
WARN_LOG(BOOT, "This game needs the real BIOS, which was not found: using reios");
LoadHle(system_dir);
}
}
else if (!hle_bios
&& (id == "T17708N" // Stupid Invaders (US)
|| id == "T17711D" // Stupid Invaders (EU)
|| id == "T46509M" // Suika (JP)
|| id == "T36901M")) // Cool Boarders Burrrn (JP)
{
NOTICE_LOG(BOOT, "Forcing HLE BIOS");
LoadHle(system_dir);
settings.bios.UseReios = true;
}
}
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++)
{
/* (the whole name: one that is part of another game's is not that game) */
if (!strcmp(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;
}
}
if (!strcmp(name, "MONKEY BALL JAPAN VERSION")) // the map's depth
{
NOTICE_LOG(BOOT, "[Hack]: Applying translucent polygon depth mask hack.");
settings.rend.TranslucentPolygonDepthMask = true;
}
}
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);
gdrom_hle_reset();
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
// the HLE BIOS is the one in the ROM
bool hle_bios = false;
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");
hle_bios = true;
}
}
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();
if (libGDR_GetDiscType() != NoDisk && settings.reios.ElfFile.empty())
ChooseGameBios(new_system_dir, hle_bios);
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 once a frame (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)
{
spg_frame_cut();
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);
// (where it ends: hw/pvr/spg.cpp)
spg_frame_begin();
maple_start_deferred();
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();
if (game_broadcast >= 0 && settings.System == DC_PLATFORM_DREAMCAST)
{
sys_nvmem_flash.data[0x1a004] = flash_broadcast[0];
sys_nvmem_flash.data[0x1a0a4] = flash_broadcast[1];
}
if (game_language >= 0 && settings.System == DC_PLATFORM_DREAMCAST)
{
sys_nvmem_flash.data[0x1a003] = flash_language[0];
sys_nvmem_flash.data[0x1a0a3] = flash_language[1];
flash_syscfg_block syscfg;
if (flash_user_language >= 0
&& sys_nvmem_flash.ReadBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg))
{
syscfg.lang = (u8)flash_user_language;
sys_nvmem_flash.WriteBlock(FLASH_PT_USER, FLASH_USER_SYSCFG, &syscfg);
}
}
forget_game_settings();
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;
/* what the game loaded before this one had of its own */
forget_game_settings();
settings.mapping.JammaSetup = JVS::Default;
naomi_game_inputs = NULL;
//settings.dynarec.DisableDivMatching = 0;
//disable_nvmem can't be loaded, because nvmem init is before cfg load
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;
}