mirror of
https://github.com/libretro/parallel-dc.git
synced 2026-11-12 18:44:20 -10:00
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.
561 lines
16 KiB
C++
561 lines
16 KiB
C++
#include "types.h"
|
|
#include <algorithm>
|
|
#include <string.h>
|
|
|
|
#include "maple_if.h"
|
|
|
|
#include "hw/sh4/sh4_interrupts.h"
|
|
#include "hw/sh4/sh4_sched.h"
|
|
#include "hw/sh4/sh4_mem.h"
|
|
#include "types.h"
|
|
#include "hw/holly/holly_intc.h"
|
|
#include "hw/holly/sb.h"
|
|
#include "hw/maple/maple_helper.h"
|
|
#include "maple_cfg.h"
|
|
#include "hw/pvr/spg.h"
|
|
|
|
enum MaplePattern
|
|
{
|
|
MP_Start,
|
|
MP_SDCKBOccupy = 2,
|
|
MP_Reset,
|
|
MP_SDCKBOccupyCancel,
|
|
MP_NOP = 7
|
|
};
|
|
|
|
maple_device* MapleDevices[4][6];
|
|
|
|
int maple_sched;
|
|
|
|
/*
|
|
Maple host controller
|
|
Direct processing, async interrupt handling
|
|
Device code is on maple_devs.cpp/h, config&management is on maple_cfg.cpp/h
|
|
|
|
This code is missing many of the hardware details, like proper trigger handling,
|
|
DMA continuation on suspect, etc ...
|
|
*/
|
|
|
|
static void maple_DoDma();
|
|
static void maple_handle_reconnect();
|
|
static bool IsOnSh4Ram(u32 addr);
|
|
|
|
//really hackish
|
|
//misses delay , and stop/start implementation
|
|
//ddt/etc are just hacked for wince to work
|
|
//now with proper maple delayed DMA maybe its time to look into it ?
|
|
bool maple_ddt_pending_reset;
|
|
|
|
/* What the devices have answered in the transfer under way.
|
|
*
|
|
* An answer takes time to come in over the bus, and the game is told with an
|
|
* interrupt when the whole transfer is over. Until then its memory has to be
|
|
* as it was: a game that looks before the interrupt must not find the answer
|
|
* there already. So the answers wait here, each as the address it goes to,
|
|
* its length in bytes and its words, and are put in memory by
|
|
* maple_dma_done(). An address of 0 is an answer with nowhere to go: the
|
|
* overrun interrupt, raised then too.
|
|
*
|
|
* In the save state, from V18. */
|
|
u32 maple_out[MAPLE_OUT_WORDS];
|
|
u32 maple_out_used;
|
|
|
|
static void maple_out_write(u32 dest, const u32 *data, u32 bytes)
|
|
{
|
|
if (dest == 0)
|
|
{
|
|
asic_RaiseInterrupt(holly_MAPLE_OVERRUN);
|
|
return;
|
|
}
|
|
/* An answer goes to RAM, and all of it there. The address and the length
|
|
* can be a save state's, and the game can ask for an answer at the very
|
|
* end of RAM: what would be written outside it is left out. */
|
|
if (!IsOnSh4Ram(dest) || bytes > RAM_SIZE - (dest & RAM_MASK))
|
|
return;
|
|
u32 *p = (u32 *)GetMemPtr(dest, bytes);
|
|
if (p != NULL)
|
|
memcpy(p, data, bytes);
|
|
}
|
|
|
|
static void maple_out_put(u32 dest, const u32 *data, u32 bytes)
|
|
{
|
|
const u32 words = (bytes + 3) / 4;
|
|
|
|
if (maple_out_used + 2 + words > MAPLE_OUT_WORDS)
|
|
{
|
|
// more answers than there is room to keep: this one arrives at once
|
|
maple_out_write(dest, data, bytes);
|
|
return;
|
|
}
|
|
maple_out[maple_out_used++] = dest;
|
|
maple_out[maple_out_used++] = bytes;
|
|
memcpy(&maple_out[maple_out_used], data, bytes);
|
|
maple_out_used += words;
|
|
}
|
|
|
|
// The transfer is over: the answers go where the game asked, and it is told
|
|
void maple_dma_done()
|
|
{
|
|
u32 at = 0;
|
|
|
|
while (at + 2 <= maple_out_used)
|
|
{
|
|
const u32 dest = maple_out[at], bytes = maple_out[at + 1];
|
|
// (a length from a save state can be anything: no more than is kept here)
|
|
if (bytes > (maple_out_used - at - 2) * 4)
|
|
break;
|
|
const u32 words = (bytes + 3) / 4;
|
|
maple_out_write(dest, &maple_out[at + 2], bytes);
|
|
at += 2 + words;
|
|
}
|
|
maple_out_used = 0;
|
|
SB_MDST = 0;
|
|
asic_RaiseInterrupt(holly_MAPLE_DMA);
|
|
}
|
|
void maple_vblank()
|
|
{
|
|
if (SB_MDEN & 1)
|
|
{
|
|
if (SB_MDTSEL & 1)
|
|
{
|
|
if (maple_ddt_pending_reset)
|
|
{
|
|
DEBUG_LOG(MAPLE, "DDT vblank ; reset pending");
|
|
}
|
|
else
|
|
{
|
|
DEBUG_LOG(MAPLE, "DDT vblank");
|
|
SB_MDST = 1;
|
|
maple_DoDma();
|
|
// if trigger reset is manual, mark it as pending
|
|
if ((SB_MSYS >> 12) & 1)
|
|
maple_ddt_pending_reset = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
maple_ddt_pending_reset = false;
|
|
}
|
|
}
|
|
maple_handle_reconnect();
|
|
}
|
|
|
|
static void maple_SB_MSHTCL_Write(u32 addr, u32 data)
|
|
{
|
|
if (data&1)
|
|
maple_ddt_pending_reset=false;
|
|
}
|
|
|
|
static void maple_SB_MDST_Write(u32 addr, u32 data)
|
|
{
|
|
if (data & 0x1)
|
|
{
|
|
if (SB_MDEN &1)
|
|
{
|
|
SB_MDST=1;
|
|
/* A transfer the game starts where a run ends is made at the
|
|
* start of the next run, with its pad (hw/pvr/spg.cpp). Only
|
|
* one with no other under way: a transfer started with nothing
|
|
* scheduled to end it is how a state says one is waiting. */
|
|
if (!sh4_sched_is_scheduled(maple_sched) && !spg_gun_pending() && spg_maple_defer())
|
|
return;
|
|
maple_DoDma();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* dc_run(): a run starts. A transfer the last one left for it is made now,
|
|
* where the machine stands, before it runs on. */
|
|
void maple_start_deferred()
|
|
{
|
|
if ((SB_MDEN & 1) && (SB_MDST & 1) && !sh4_sched_is_scheduled(maple_sched) && !spg_gun_pending())
|
|
maple_DoDma();
|
|
}
|
|
|
|
static void maple_SB_MDEN_Write(u32 addr, u32 data)
|
|
{
|
|
SB_MDEN=data&1;
|
|
}
|
|
|
|
#ifdef STRICT_MODE
|
|
static bool check_mdapro(u32 addr)
|
|
{
|
|
u32 area = (addr >> 26) & 7;
|
|
u32 bottom = ((((SB_MDAPRO >> 8) & 0x7f) << 20) | 0x08000000);
|
|
u32 top = (((SB_MDAPRO & 0x7f) << 20) | 0x080fffe0);
|
|
|
|
if (area != 3 || addr < bottom || addr > top)
|
|
{
|
|
INFO_LOG(MAPLE, "MAPLE ERROR : Invalid address: %08x. SB_MDAPRO: %x %x", addr, (SB_MDAPRO >> 8) & 0x7f, SB_MDAPRO & 0x7f);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void maple_SB_MDSTAR_Write(u32 addr, u32 data)
|
|
{
|
|
SB_MDSTAR = data & 0x1fffffe0;
|
|
if (!check_mdapro(SB_MDSTAR))
|
|
asic_RaiseInterrupt(holly_MAPLE_ILLADDR);
|
|
}
|
|
#endif
|
|
|
|
static bool IsOnSh4Ram(u32 addr)
|
|
{
|
|
if (((addr>>26)&0x7)==3)
|
|
{
|
|
if ((((addr>>29) &0x7)!=7))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void maple_DoDma(void)
|
|
{
|
|
verify(SB_MDEN &1)
|
|
verify(SB_MDST &1)
|
|
|
|
DEBUG_LOG(MAPLE, "Maple: DoMapleDma SB_MDSTAR=%x", SB_MDSTAR);
|
|
u32 addr = SB_MDSTAR;
|
|
#ifdef STRICT_MODE
|
|
if (!check_mdapro(addr))
|
|
{
|
|
asic_RaiseInterrupt(holly_MAPLE_ILLADDR);
|
|
SB_MDST = 0;
|
|
return;
|
|
}
|
|
#endif
|
|
/* The table of frames is in RAM, and ends where RAM does at the latest.
|
|
* (It was read from wherever the game said, and one with no entry marked
|
|
* as its last went on through all of memory.) */
|
|
if (!IsOnSh4Ram(addr))
|
|
{
|
|
INFO_LOG(MAPLE, "MAPLE ERROR : INVALID SB_MDSTAR value 0x%X", addr);
|
|
SB_MDST = 0;
|
|
return;
|
|
}
|
|
const u32 ram_end = (addr | RAM_MASK) + 1;
|
|
const bool swap_msb = (SB_MMSEL == 0);
|
|
u32 xfer_in = 0, xfer_out = 0; // bytes from the console, and from the devices
|
|
bool last = false;
|
|
bool occupy = false;
|
|
while (last != true)
|
|
{
|
|
if (addr + 4 > ram_end)
|
|
{
|
|
INFO_LOG(MAPLE, "MAPLE ERROR : the table at 0x%X has no end", SB_MDSTAR);
|
|
SB_MDST = 0;
|
|
maple_out_used = 0;
|
|
return;
|
|
}
|
|
u32 header_1 = ReadMem32_nommu(addr);
|
|
u32 header_2 = ReadMem32_nommu(addr + 4) &0x1FFFFFE0;
|
|
|
|
last = (header_1 >> 31) == 1;//is last transfer ?
|
|
u32 plen = (header_1 & 0xFF )+1;//transfer length (32-bit unit)
|
|
u32 maple_op=(header_1>>8)&7; // Pattern selection: 0 - START, 2 - SDCKB occupy permission, 3 - RESET, 4 - SDCKB occupy cancel, 7 - NOP
|
|
// The port the frame goes out on is the transfer's to say, not the frame's
|
|
const u32 bus = (header_1 >> 16) & 3;
|
|
//this is kinda wrong .. but meh
|
|
//really need to properly process the commands at some point
|
|
switch (maple_op)
|
|
{
|
|
case MP_Start:
|
|
{
|
|
#ifdef STRICT_MODE
|
|
if (!check_mdapro(header_2) || !check_mdapro(addr + 8 + plen * sizeof(u32) - 1))
|
|
{
|
|
asic_RaiseInterrupt(holly_MAPLE_OVERRUN);
|
|
SB_MDST = 0;
|
|
return;
|
|
}
|
|
#else
|
|
if (!IsOnSh4Ram(header_2))
|
|
{
|
|
// nowhere for the answer to go: the overrun interrupt, when the transfer is over
|
|
INFO_LOG(MAPLE, "MAPLE ERROR : DESTINATION NOT ON SH4 RAM 0x%X", header_2);
|
|
header_2 = 0;
|
|
}
|
|
#endif
|
|
|
|
// (a frame that starts in RAM and ends past it is not one)
|
|
u32* p_data = addr + 8 + plen * sizeof(u32) > ram_end ? NULL
|
|
: (u32*) GetMemPtr(addr + 8,(plen)*sizeof(u32));
|
|
if (p_data == NULL)
|
|
{
|
|
INFO_LOG(MAPLE, "MAPLE ERROR : INVALID SB_MDSTAR value 0x%X", addr);
|
|
SB_MDST=0;
|
|
maple_out_used = 0;
|
|
return;
|
|
}
|
|
|
|
const u32 frame_header = swap_msb ? SWAP32(p_data[0]) : p_data[0];
|
|
|
|
//Command code
|
|
u32 command = frame_header & 0xFF;
|
|
//Recipient address
|
|
u32 reci = (frame_header >> 8) & 0xFF;//0-5;
|
|
//Sender address
|
|
//u32 send=(p_data[0] >> 16) & 0xFF;
|
|
//Number of additional words in frame
|
|
u32 inlen = (frame_header >> 24) & 0xFF;
|
|
// (no more than the table says the frame has: the device reads no further)
|
|
if (inlen >= plen)
|
|
inlen = plen - 1;
|
|
|
|
u32 port=maple_GetPort(reci);
|
|
/* A device with no slots for others hears every frame sent down
|
|
* its bus, whatever the frame is addressed to. (Asked only when
|
|
* nothing is in the slot addressed, which a device with slots
|
|
* may have empty too.) */
|
|
if (port != 5 && MapleDevices[bus][port] == NULL && MapleDevices[bus][5] != NULL
|
|
&& !maple_HasSlots(MapleDevices[bus][5]->get_device_type()))
|
|
port = 5;
|
|
|
|
if (MapleDevices[bus][5] && MapleDevices[bus][port])
|
|
{
|
|
static u32 maple_out_buf[1024 / 4];
|
|
if (swap_msb)
|
|
{
|
|
static u32 maple_in_buf[1024 / 4];
|
|
maple_in_buf[0] = frame_header;
|
|
// every word of the frame: plen of them, the header included
|
|
for (u32 i = 1; i < plen; i++)
|
|
maple_in_buf[i] = SWAP32(p_data[i]);
|
|
p_data = maple_in_buf;
|
|
}
|
|
u32 outlen = MapleDevices[bus][port]->RawDma(&p_data[0], inlen * 4 + 4, maple_out_buf);
|
|
// each frame also has its start, parity and stop on the wire
|
|
xfer_in += plen * 4 + 3;
|
|
xfer_out += outlen + 3;
|
|
#ifdef STRICT_MODE
|
|
if (!check_mdapro(header_2 + outlen - 1))
|
|
{
|
|
// TODO: This isn't correct (with SB_MMSEL=1) since the interrupt
|
|
// should be raised before the memory is written to
|
|
asic_RaiseInterrupt(holly_MAPLE_OVERRUN);
|
|
SB_MDST = 0;
|
|
maple_out_used = 0;
|
|
return;
|
|
}
|
|
#endif
|
|
if (swap_msb)
|
|
for (u32 i = 0; i < outlen / 4; i++)
|
|
maple_out_buf[i] = SWAP32(maple_out_buf[i]);
|
|
maple_out_put(header_2, maple_out_buf, outlen);
|
|
}
|
|
else
|
|
{
|
|
if (port != 5 && command != 1)
|
|
INFO_LOG(MAPLE, "MAPLE: Unknown device bus %d port %d cmd %d", bus, port, command);
|
|
static const u32 nobody = 0xFFFFFFFF;
|
|
maple_out_put(header_2, &nobody, 4);
|
|
}
|
|
|
|
//goto next command
|
|
addr += 2 * 4 + plen * 4;
|
|
}
|
|
break;
|
|
|
|
case MP_SDCKBOccupy:
|
|
{
|
|
if (MapleDevices[bus][5])
|
|
{
|
|
// (a later request, for a bus with no gun, does not undo one that has)
|
|
if (MapleDevices[bus][5]->get_lightgun_pos())
|
|
occupy = true;
|
|
xfer_in++;
|
|
}
|
|
|
|
addr += 1 * 4;
|
|
}
|
|
break;
|
|
|
|
case MP_SDCKBOccupyCancel:
|
|
// (the transfer ends in its own time, and not at vblank out)
|
|
occupy = false;
|
|
spg_gun_occupy_cancel();
|
|
addr += 1 * 4;
|
|
break;
|
|
|
|
case MP_Reset:
|
|
addr += 1 * 4;
|
|
xfer_in++;
|
|
break;
|
|
|
|
case MP_NOP:
|
|
addr += 1 * 4;
|
|
break;
|
|
|
|
default:
|
|
INFO_LOG(MAPLE, "MAPLE: Unknown maple_op == %d length %d", maple_op, plen * 4);
|
|
addr += 1 * 4;
|
|
}
|
|
}
|
|
|
|
/* How long the transfer takes. The console sends at 2 Mbit/s; devices
|
|
* answer more slowly, 724 to 738 kbit/s as measured on the wire with a
|
|
* protocol analyser (the captures are in OrangeFox86's
|
|
* DreamcastControllerUsbPico, under measurements). These are upstream's
|
|
* figures, which it came to through Silent Scope, which reads its
|
|
* memory card at boot and fails if the answer comes back too soon. */
|
|
if (!occupy)
|
|
{
|
|
u64 cycles = (u64)SH4_MAIN_CLOCK * xfer_in / (2000000 / 8)
|
|
+ (u64)SH4_MAIN_CLOCK * xfer_out / (740000 / 8);
|
|
sh4_sched_request(maple_sched, (int)std::min(cycles, (u64)SH4_MAIN_CLOCK));
|
|
}
|
|
}
|
|
|
|
static int maple_schd(int tag, int c, int j)
|
|
{
|
|
if (SB_MDEN&1)
|
|
maple_dma_done();
|
|
else
|
|
{
|
|
INFO_LOG(MAPLE, "WARNING: MAPLE DMA ABORT");
|
|
SB_MDST=0; //I really wonder what this means, can the DMA be continued ?
|
|
maple_out_used = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void maple_SB_MDAPRO_Write(u32 addr, u32 data)
|
|
{
|
|
if ((data >> 16) == 0x6155)
|
|
SB_MDAPRO = data & 0x00007f7f;
|
|
}
|
|
|
|
//Init registers :)
|
|
void maple_Init()
|
|
{
|
|
sb_rio_register(SB_MDST_addr,RIO_WF,0,&maple_SB_MDST_Write);
|
|
sb_rio_register(SB_MDEN_addr,RIO_WF,0,&maple_SB_MDEN_Write);
|
|
sb_rio_register(SB_MSHTCL_addr,RIO_WF,0,&maple_SB_MSHTCL_Write);
|
|
sb_rio_register(SB_MDAPRO_addr, RIO_WO_FUNC, nullptr, &maple_SB_MDAPRO_Write);
|
|
#ifdef STRICT_MODE
|
|
sb_rio_register(SB_MDSTAR_addr, RIO_WF, 0, &maple_SB_MDSTAR_Write);
|
|
sb_rio_register(SB_MDSTAR_addr, RIO_WF, nullptr, &maple_SB_MDSTAR_Write);
|
|
#endif
|
|
|
|
maple_sched=sh4_sched_register(0,&maple_schd);
|
|
}
|
|
|
|
void maple_Reset(bool hard)
|
|
{
|
|
maple_ddt_pending_reset=false;
|
|
maple_out_used = 0;
|
|
// (and the end of the transfer those answers were for is not to come)
|
|
if (sh4_sched_is_scheduled(maple_sched))
|
|
sh4_sched_request(maple_sched, -1);
|
|
SB_MDTSEL = 0x00000000;
|
|
SB_MDEN = 0x00000000;
|
|
SB_MDST = 0x00000000;
|
|
SB_MSYS = 0x3A980000;
|
|
SB_MSHTCL = 0x00000000;
|
|
SB_MDAPRO = 0x00007F00;
|
|
SB_MMSEL = 0x00000001;
|
|
maple_jvs_firmware_reset();
|
|
}
|
|
|
|
void maple_Term()
|
|
{
|
|
|
|
}
|
|
|
|
void maple_FlushSaves()
|
|
{
|
|
int bus, port;
|
|
for (bus = 0; bus < 4; bus++)
|
|
for (port = 0; port < 6; port++)
|
|
if (MapleDevices[bus][port])
|
|
MapleDevices[bus][port]->FlushSave();
|
|
}
|
|
|
|
static u64 reconnect_time;
|
|
|
|
/* The settings have changed what is plugged in. What they no longer have is
|
|
* unplugged, and a tenth of a second later what they have is plugged in, so
|
|
* that the game sees the port empty in between. On a Dreamcast that is only
|
|
* the devices that change: a game saving to a card on another port went on
|
|
* writing to nothing for that tenth of a second, and a save cut off there
|
|
* left the card's file system half written. */
|
|
void maple_ReconnectDevices()
|
|
{
|
|
if (settings.System == DC_PLATFORM_DREAMCAST)
|
|
mcfg_ReconcileDevices(false);
|
|
else
|
|
mcfg_DestroyDevices();
|
|
reconnect_time = sh4_sched_now64() + SH4_MAIN_CLOCK / 10;
|
|
}
|
|
|
|
static void maple_handle_reconnect()
|
|
{
|
|
if (reconnect_time != 0 && reconnect_time <= sh4_sched_now64())
|
|
{
|
|
reconnect_time = 0;
|
|
// (a state loaded in the meantime has put its own devices there: what is not as the settings say goes first)
|
|
if (settings.System == DC_PLATFORM_DREAMCAST)
|
|
mcfg_ReconcileDevices(true);
|
|
else
|
|
{
|
|
mcfg_DestroyDevices();
|
|
mcfg_CreateDevices();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* After a load. A state saved before the settings changed a Dreamcast's
|
|
* devices and loaded after - run-ahead does just that, every frame - puts
|
|
* back the devices of before, with nothing waiting to bring the new ones:
|
|
* the change was lost. What is not as the settings say is unplugged then,
|
|
* and what they say is plugged in a tenth of a second later, as when they
|
|
* changed. It is the same for the same state and settings, every time. */
|
|
static void maple_devices_after_load(void)
|
|
{
|
|
if (settings.System != DC_PLATFORM_DREAMCAST || reconnect_time != 0
|
|
|| mcfg_DevicesAsSettingsSay())
|
|
return;
|
|
mcfg_ReconcileDevices(false);
|
|
reconnect_time = sh4_sched_now64() + SH4_MAIN_CLOCK / 10;
|
|
}
|
|
|
|
/* What Maple has had in a save state since V21, after everything older:
|
|
* the I/O boards' part (maple_devs.cpp), and when the devices that have
|
|
* been unplugged are to be plugged in again, 0 if they are not waiting to
|
|
* be. The size is always the same.
|
|
*
|
|
* The time was not in a state. With run-ahead, which loads a state every
|
|
* frame, the devices came back in a frame that was run ahead and went
|
|
* again with the load that followed, and by then nothing was waiting to
|
|
* bring them back: a port's device changed, and every port was empty from
|
|
* then on. */
|
|
void maple_serialize_v21(void **data, unsigned int *total_size)
|
|
{
|
|
maple_jvs_serialize_v21(data, total_size);
|
|
LIBRETRO_S(reconnect_time);
|
|
}
|
|
|
|
bool maple_unserialize_v21(void **data, unsigned int *total_size)
|
|
{
|
|
maple_jvs_unserialize_v21(data, total_size);
|
|
reconnect_time = 0;
|
|
LIBRETRO_US(reconnect_time);
|
|
// (it is never more than a tenth of a second away)
|
|
const u64 latest = sh4_sched_now64() + SH4_MAIN_CLOCK / 10;
|
|
if (reconnect_time > latest)
|
|
reconnect_time = latest;
|
|
maple_devices_after_load();
|
|
return true;
|
|
}
|
|
|
|
/* A state from before V21: the boards as maple_devs.cpp says, and devices
|
|
* waiting to be plugged in again go on waiting, as they did. */
|
|
void maple_state_before_v21(void)
|
|
{
|
|
maple_jvs_state_before_v21();
|
|
maple_devices_after_load();
|
|
}
|