The log macros call log_cb, and nothing more: no log manager, no
second formatting into a buffer of the core's, no locale switched, no
file, line or part of the machine in the line. Each format ends with
its "\n". INFO_LOG and DEBUG_LOG are left out of release builds, as
they were. The tests that stood in for GenericLog() set log_cb.
tools/log/log_check.py fails on a log format without its "\n", in
headless.yml.
maple_if.c drives the devices through a small C API in maple_if.h
(maple_dev_dma, maple_dev_has_slots, maple_dev_lightgun_pos,
maple_dev_flush_save), which also declares the I/O boards' state parts
and the mcfg_* calls once, with C linkage. A device writes its answer
straight into the room maple_out keeps for it until the transfer ends;
the copy is left only for when that room has run out.
dns.c builds its query and reads the answer as bytes, with no picoTCP
headers: dns_query_build() and dns_answer_parse() are tested on their
own by tools/modem/dns_run.sh (with the sanitizers, in headless.yml),
names too long for the query included.
ccn and aica_if become C. CCN_QACR_write() takes its queue as an
argument; the G2 DMA channels' templates are one table of their
registers and interrupts, looked up by the register written. CCR's
cache invalidations go through sh4_icache_invalidate() and
sh4_ocache_invalidate().
mmu.h and blockmanager.h get C parts, with their templates, reference
parameters and the block classes behind __cplusplus; sh4_if.h declares
sh4_cpu for C. The AICA interface's state is declared in aica_if.h, where
serialize.cpp and maple_devs.cpp declared it themselves.
tools/aica/g2dma_run.sh checks that each expansion DMA channel moves its
own transfer, leaves its own registers as a finished transfer does and
raises its own interrupt, touching no other; the C++ channels pass it
too. It runs in the Headless workflow, with the sanitizers as well.
sb, gdromv3 and midiffb become C. The bus's registers that keep only
some bits of a write take their mask and set bits from a table rather
than from a template each. The drive's registers that were a struct
around a union are the union; its CD audio states are CDDA_*; its
aggregate resets are memsets, and _vmem_write_ptr() gives C what
_vmem_write_const() gives through a reference.
The headers they need compile as both: gdromv3.h, emulator.h,
netservice.h, modem.h, bba.h, and naomi_cart.h's C part (the game's
inputs and name). The pads the frontend gives are declared in
libretro/pad_state.h, where four files declared them themselves.
tools/c89/linkage_check.py also checks the other way: a variable a C
file uses and a C++ file defines has been declared in extern "C" there
first. The drive's state and the bus's FIFO count are declared in
their headers for serialize.cpp.
The way to MSVC C89, gradually (AGENTS.md). types.h and Log.h compile as
C89 and C++: the C++-only parts are behind __cplusplus, the rest is in
extern "C", the anonymous unions are marked, the comments are /* */. In
C, the log's kinds are LOG_INPUT and the like, since Windows headers
already have INPUT. JVS is a plain enum (JVS_Default, ...), and the
settings' strings are char arrays.
last_picture, descrambl, vmu_beep, gdrom_response, printer and
miniupnp are now .c files. miniupnp is a struct with init, term and
add_port_mapping instead of a class.
tools/c89/c89_check.sh compiles every .c file of the core's own as
-std=c89 -pedantic with the build's flags. CI runs it natively and for
Windows. libretro.cpp includes compat/strl.h for strlcpy, which glibc has
and Windows does not.
The modem's UPnP is net_upnp, custom textures are read with rpng and
rjpeg and dumped with rpng (core/rend/custom_image.c), and the textures'
hashes are xxh32. The libretro-common files are RetroArch's, as they are
there; xxh32 needs RetroArch's patch adding it. core/deps/miniupnpc,
stb and xxhash are gone.
Custom textures are named by their hashes, so those must not change:
tools/rend/texhash_test.sh checks them against XXH32's values. Pictures
read as stb_image read them, except 4:2:2 JPEGs' second-last column,
where stb weighted the wrong neighbour (tools/rend/custom_image_test.sh,
with files cut and spoilt, and dumps read back by a second reader). The
UPnP is checked against a gateway on the runner, TCP and UDP mapped and
deleted (tools/threads/upnp_test.sh). All in CI.
A device on a controller port that the state loaded has one of the same
type of is made anew in its own memory, its map with it (mcfg_Remake(),
maple_CreateAt()): a state loaded made and freed sixteen of them, which
with run-ahead is every frame. The NAOMI I/O board made so remembers that
the EEPROM was read, as one made new does.
The allocation measures run the plain disc and the one whose state has a
render to a texture on the card in it with a state saved and loaded every
frame, on every renderer: vk_hdr's VKH_AHEAD and d3d_hdr's D3DH_AHEAD, as
gl_frames' GLF_AHEAD, in vk_allocs.sh, gl_allocs.sh and d3d_allocs.sh,
100 frames each. headless.sh runs the NAOMI cartridge with every frame run
ahead too.
Virtua Athlete, Royal Rumble, Beach Spikers and MJ (mj1) run on a cabinet
with two 837-13551 I/O boards, World Kicks PCB with two Namco boards;
the fork gave them one. They get two now (JVS::DualIOBoards4P, by game
ID), the second board players 3 and 4 on libretro ports 3 and 4.
Upstream puts players 3/4 on the first board and 1/2 on the second,
always; with the game set for one or two players that left player 1 on
port 3. Here the order follows the player count in the game's EEPROM,
read on every JVS message: reversed for 3 or 4 players, as upstream,
straight for 1 or 2, so a 2-player setup plays from port 1, and a change
in the test menu applies at once.
Their EEPROM is set up as upstream's when it is not this game's (no
file, or another game's): the game ID, a 4-player cabinet where the
header allows it, the coin settings from the header, the CRC and the
second copy. A file of the game's own is kept, but a player count the
header does not allow becomes the highest it does. This happens as the
EEPROM is read at machine start, so the state holds the result.
The Namco board now reads every player's stick (players 3 and 4 read
0x8000 before). No state format change: the board order follows the
EEPROM, which is in the state, and the board state already has room
for two.
A card reader keeps the card out of the reader in memory, read from its
file when the machine starts, and the game's writes to it change that
copy; the IC card readers of VF4 and Mushiking the same with both cards.
None of it was in the state: a state loaded after the game had written a
card (run-ahead does that every frame) left the newer card, and the next
one put in was not the card the state had. Both are in the state from
V30: a byte saying which readers there are, then 208 bytes with a card
reader, 256 with the IC card readers. An older state keeps the cards in
memory as they are, as before. The reader's copy is zeroed before the
file is read into it, so a short file leaves no stray bytes.
A new Dreamcast console ID (made once, when the flash has none) and the
bytes of a new VF4 / Mushiking IC card came from srand() and rand(),
the process-wide generator anything else can draw from. Each has a
generator of its own now: the console ID's seeded with the clock the
flash was just given, the cards' with the host time as before. Values
already in the flash or a card file are not touched.
- A vertex with x or y over 1e25 is dropped as upstream (Manic Panic
Ghosts); one with a negative z is kept and clipped by the GPU (World
Series Baseball 2K1). The test is on the bits, so fast-math keeps it.
- Punch-through polygons are tested on the shaded alpha (texture x
vertex colour), not the texel's, in GL, GL4, Vulkan and Vulkan OIT
(Langrisser Millennium), as upstream.
- Per-strip sorting: sorted translucent polygons write no depth there
either (Sonic Adventure, which is forced to it, and LOW_END builds).
- A TA context unused for 60 renders is reused, as upstream; counted per
render started, so frame skip does not change it, and never one of
another frame size.
- NAOMI 2: the render-end interrupt always 1,500,000 cycles on.
- Light guns: at the gun's line and at vblank-in only SB_MDST is
cleared; a transfer the game started ends at vblank-out with its answer
and interrupt (RevolveR), one vblank started gets none (Virtua Cop 2).
Ninja Assault, Mazan and Gun Survivor get the raw 640x480 position.
The gun's X follows the widescreen hack when no widescreen cheat is
on. A "Pointer" controller type aims and fires with one touch and
reloads with two; the left stick aims on a gamepad in a gun game.
- Render to texture with a clip whose minimum is above 0: the target
starts at the frame's pixel 0 and the clip is the scissor, in GL and
Vulkan; only the clip goes back to VRAM, at its place, dithered by the
pixel's place in the frame. Pixels past max - min were lost, and VRAM
below the minimum was overwritten.
Save state V28: the gun's transfer owed its end, and the TA contexts'
ages (17 bytes). An older state gets ages 0.
The JVS I/O boards upstream has and the fork lacked:
- the 837-13844 racing board of F355 Challenge and Tokyo Bus Guide,
which answers for the drive board on P1/P2 BTN2-5 (Tokyo Bus sets its
power through its spring commands);
- 18 Wheeler's: the racing board and its shifter (SHIFT L/H toggles low
and high, SHIFT R is reverse; the gear on P2 UP/DOWN/LEFT), the wheel
kept within 0x1000-0xefff;
- Samba de Amigo's maraca sensors, from the analog sticks as upstream;
- Shootout Pool's cue roller (while CUE ROLLER is held, up/down moves
the roller and the aim stays where it is), Kick '4' Cash's and
Crackin'DJ's encoder boards (its turntable motor turns them).
They are chosen by game ID. Lamps are dropped; the drive board's force
goes to rumble. A board's own wiring applies after the game's button
mapping, as upstream; JVS command 0x33 (analog output) no longer goes
to the switched outputs. F355's and 18 Wheeler's input descriptors are
upstream's, for these boards (F355's assists on the directions, gears
on P2; the Deluxe clutch has no second pair of triggers to go on).
MIDI force feedback (midiffb.cpp): the steering board of Initial D (1,
2, 3, Cycraft), The King of Route 66 and Club Kart, on the AICA's MIDI
out and in. Its replies come in at the sound chip's next step, and its
input holds 64 bytes. The option "NAOMI Wheel Force Feedback"
(reicast_naomi_wheel_ffb, off by default) turns the wheel's pull and
shake into rumble on port 1, handed to the frontend from one atomic word
when it changes.
F355 Challenge's printer on the G2 bus: its status reads 0x0f, bit 0 set
after each read and cleared when the game writes it, as upstream; what
it prints is dropped. These addresses read 0 before.
BIOSes naomi-ko-h and the two multi-region ones (epr-21576h_multi,
epr-23605c_multi), which take their region from motherboard DIP
switches: with one of those loaded the JVS reply sets them to Korea
(0xf9, 0x08 at 15 kHz); every other BIOS sees what it saw.
Save state V27: the boards' outputs, power and gear (12 bytes), the MIDI
board (79 bytes on its games) and the printer's status (1 byte on a
NAOMI). An older state gets them idle.
With the real BIOS, Dead or Alive 2 went one of two ways from run to
run. A NAOMI's battery-backed SRAM has no file on a first boot and
started as whatever the heap had there, which the BIOS reads. Chip
memory starts zeroed now, as upstream's: a first boot with no .nvmem
file (or a Dreamcast's or Atomiswave's flash with none) finds zeros.
The JVS I/O boards were made the first time the game spoke to them,
around frame 45, and each added 2 bytes to the state: its size changed
while the game ran, so run-ahead started before that failed, and a state
saved before them, loaded after, kept the later boards. They are made
with the NAOMI's I/O device now; they act only when spoken to, so the
game sees the same, and the state's size is fixed from the first frame.
- A System SP has no JVS I/O board or controller (MAME's segasp has
none, nor does upstream); its inputs are read from the same per-frame
input words. The Insert Card button is read when it reads IN_PORT0.
- The Hirocon IC card reader of dinokich and loveber3cn (dinokich.cpp,
dinokich_card.cpp), on UART1 with its two slots. Cards are 1 KB, kept
as <game>-<region>.rfid1 and .rfid2 and written when the game writes
them, including its check value. A command shorter than it needs is
refused, and what comes in and goes out has a fixed room.
- Mushiking's I/O board (837-13551 with the card dispenser): the
dispenser reported ready unless TEST is down, P1 RIGHT and P2 START
clear, TEST seen one read late, by the game IDs upstream uses.
- Touch de Uno 1 and 2: the touch board reports the raw 640x480 position,
ORed with 0xf000 while the screen is touched; it sent Ninja Assault's
scaled one, which a touch never reached. Touch de Uno 2 and Mirai Yosou
Studio get the 837-13844 touch board (MAME and upstream agree), and both
Touch de Uno get upstream's calibrated default EEPROM where there is no
file yet.
- anpanman2 gets its 837-13844 board.
Save state V25: the dinokich reader's cards and messages (2828 bytes, on
those two games only) and a byte per I/O board for Mushiking's late TEST.
It was read the first time the game asked for it. A state saved before
that said "not read yet"; run-ahead then ran on, the game wrote its EEPROM
and the file got it, and once the state was loaded the game's first read
took the file with what the game had not yet written in that run. Virtua
Fighter 4 on its first boot (no .eeprom file yet) went another way at
frame 66: it found its EEPROM set up and did not write it. It is read now
when the I/O board is made, before any state can be saved; an older
state saved before the read still reads it at the first access.
The NAOMI's arcade peripherals:
- The SCIF serial port gets its FIFOs, status and interrupts while a
device is plugged into it; with none it is the stub it was, and its
scheduler event runs only while something is on its way.
- Card readers (card_reader.cpp): Initial D and Club Kart on the SCIF,
Derby Owners Club (both kinds) on the I/O board's RS-422 commands, the
barcode reader (the barcode is the first line of <game>.barcode), and
the IC card readers of VF4 and Mushiking on maple buses 2 and 3 (a new
device type, numbered after the others). Cards are kept in the save
directory: <game>.card, <game>-p1.card, <game>-p2.card. With a reader,
L is "Insert Card". The option "NAOMI Card Readers"
(reicast_naomi_card_readers) turns them on; off by default.
- The touchscreen of Manic Panic Ghosts, Pokasuka Ghost and Touch de
Zunou, from where each player's light gun points.
- The hopper of the Club Kart Prize, Shootout Pool and Kick '4' Cash
sets; its settings and credits in <game>-hopper.bin.
- The printer of Touch de Uno 1 and 2 and Mirai Yosou Studio: answered,
what it prints dropped.
A message from the guest longer or shorter than the device takes is
dropped where it could read or write past its buffer.
New sets that need them: mushikk, mushi2k3, mushik4t, mushi2k61,
mushi2k62, drbyocwt.
In the save state (V24): which devices the game has and what each holds,
nothing on a Dreamcast or Atomiswave, 1 byte on a NAOMI without any.
A game can make a VMU beep, through the clock function's SetCondition:
the period and the time on, the tone 1 MHz over the period (Chao in
Sonic Adventure, alarms in Skies of Arcadia and others). It is played
from a short recording of a VMU's beep, faded in and out over 128 samples,
and added to the sound at the point the libretro layer sends a frame's
samples: a test per frame, nothing per sample while no VMU beeps, and the
console's master volume does not apply, the VMU having its own speaker.
Samples held back for the next frame get it when they are sent, so the
held-back sound in a save state never has it in.
The option "VMU Sounds" (reicast_vmu_sound) turns it on; off by default.
A device reset or the VMU's removal silences it, as does a reset of the
core.
The save state goes to V24 for it (13 bytes); an older state is silent.
- A hard reset now resets the cartridge's registers - PIO and DMA
offsets, M1 decompression and decryption state, M4 and Atomiswave
device state, the GD-ROM cartridge's DIMM address - without reloading
ROMs, and any reset clears the I/O board program buffer: an upload cut
off by a reset is not taken for part of the next one.
- The NAOMI per-game table is matched on the whole name: a name that
was part of another entry's (an empty one: Samurai Spirits 6's) got
that entry's settings.
- NaomiNetwork::receive waits for both bytes of the packet number, and
does not report a half-received packet as received when stopping.
- A state saved before the frontend changed a Dreamcast port's device
and loaded after (run-ahead does this every frame) brought back the
old devices with nothing to put the new ones in: the change was lost.
After a load, what is not as the settings say is unplugged and the
right devices plugged in a tenth of a second later, as for a live
change - the same for the same state and settings every time.
- The bus a frame goes out on comes from the DMA descriptor, not from
the frame's address, and a device without expansion slots answers the
frames addressed to its slots (upstream a429fba21, MAME agrees). A
device answers with its own bus's attached-device bits.
- The VMU screen after a load: a controller's second card, never drawn
on, no longer puts its blank screen over the first card's picture.
- NAOMI: the comm board's registers and its two 128 KB memories, and the
64 KB I/O board program on its way (Crazy Taxi and HOTD2 boards lost
their mode when a state was loaded mid-upload). The upload frame is
bounded by its length.
- NAOMI 2: the geometry processor's near and far planes.
- libretro: mouse motion not yet read and the samples held back at the
end of a frame, which a load dropped (a gap of up to a tick).
States before V23 load as before.
- When the frontend changes a port's device, a Dreamcast unplugs only
what changes (the bus whose device changes, a slot whose device or
card file changes) and plugs in the rest a tenth of a second later.
Everything was unplugged: a memory-card write in that window was lost
(the threaded/non-threaded state comparison in live.sh failed on it).
- The VMU clock reads the console's RTC, which is in a save state, not
the host's clock: a frame run again from a state got another date.
- A later light-gun occupy request for a bus with no gun no longer
clears the flag a gun had set.
- Without the fast-memory layout: each memory has a zeroed spare page
(unaligned accesses at the end went past the heap block); Windows frees
_aligned_malloc memory with _aligned_free; video memory fault offsets
drop the Android ARM64 pointer tag.
Every load deleted all the Maple devices and made them again. For a
memory card that is: wait until everything on its way to the card's
file has been written, close the file, unpack an empty card, open the
file, read 128K - and then take the card's contents from the state.
Four cards, or eight, sixty times a second with run-ahead: "Loaded VMU
from file" 1188 times in a 300-frame run. A card that the state has in
the same slot now stays and is given the state's contents. The other
devices are made anew as before.
After a load the card may not be what its file has, and the file is
not written until the game next writes to the card. Every load counted
as one that changed the card, and that next write then wrote the whole
card. Now the card is compared with the state's first, by the block:
nothing differing, nothing is noted; otherwise the range that differs
is, and goes to the file with the game's next write. While a game
saves with run-ahead on - a block written in a frame, taken back by the
state loaded after it, written again - that is a block or two a frame
where it was 128K.
A state loaded on the test disc with four cards: 9.2 ms to 8.8.
The I/O boards' coin counters were not in a state: a load made the
boards anew with nothing counted, and the game, which goes by how far a
count has moved since it last looked, saw coins come or go - on every
frame with run-ahead. They are in maple_serialize_v21(), with the coin
switches (a coin held across a load was otherwise counted twice), Wave
Runner GP's drive board byte, the rotary encoders' positions, and the
time at which unplugged devices are plugged in again: without that, a
change of device under run-ahead left every port empty from then on.
84 bytes, whatever the machine has.
How many boards there are was written as a size_t: eight bytes by a
64-bit build and four by a 32-bit one, and each took the other's state
for its own, with everything after it read four bytes out. What is
written is unchanged; the reader tells the two apart (the four bytes
more are zeroes, and what follows a 32-bit count never starts with four
of them).
Devices that came back after the wait replaced the ones a state loaded
in the meantime had put there without taking those away first.
"Read LCD" from a VMU gave the first 192 bytes of the card's memory. It
gives the screen's.
A JVS request with nothing in it was answered "unknown command". There
is nothing wrong with it: status normal and nothing to report (MAME and
OpenJVS agree).
The I/O board's answer to "all status" ran on into the next case, and
both answers were sent.
The microphone answered "get condition" with six bytes that were the
controller's answer with most of it taken out. A microphone has no such
request (upstream has none, KallistiOS never sends it): it is one the
device does not know.
A Maple frame was read by what each device expected to find in it. A VMU
block write with a short frame made the count wrap and copied some 4 GB
into the card's memory. A word is now read only when the frame has one
left; the VMU's screen and the rumble pack act only on a frame that is
all there.
The table of frames and each frame have to lie in RAM, and an answer goes
to RAM, all of it: the addresses are the game's, or a state's. What a
device is given is no longer than the table says the frame is.
JVS: lengths, channel numbers and counts in the cabinet's messages are
held to the frame and to the reply (255 bytes); the encoder count to the
eight there are; a request kept for later is dropped if it and the new
one do not fit. The I/O board's firmware upload is cut at the end of its
64 KB. From a state: no more boards than the cabinet has, and a pending
answer length that cannot be one is none.
The end of a firmware upload to a NAOMI's I/O board answered with nothing
written and the reply's code returned as its length. Command 0x86 found
its channel, node and length by an old guess at the layout; it goes by
the subcommand, as upstream does. The House of the Dead 2 prototype's
firmware is known by its hash and gets the reply it expects.
King of Route 66 takes a pedal at exactly half way for the race won. An
axis of that kind at exactly half is sent one step on, as upstream does.
The EEPROM was read from its file every time the game asked for it, while
what the game writes goes to the file later, from another thread: asked
for in between, the game got the old contents, and those were then what
was written. It is read once and kept. The address and size of a read
or a write were the game's, unchecked, into an array of 256 bytes.
A state does not say whose inputs an Atomiswave's device on the third or
fourth bus reads - the first and second player's analog axes, guns or
trackball are there - and a device made from a state read the third and
fourth player's. After any load, which run-ahead and rewind do all the
time, those inputs were dead. It is worked out from the game's setup; the
state's format is as it was.
Zombie Revenge: the second player's stick starts at the fifth axis.
- A NAOMI's I/O board is one device on the first port and answers for
every player. With threaded rendering off only the port a device is
on is read when it asks - since "read only the requested port on
NAOMI/Atomiswave" (a129477c) - so the second to fourth players'
buttons, sticks and coins never changed. All four are read when the
board asks. With threaded rendering on they always were.
- The mouse's reply starts with one byte of buttons and three that
are 0 (options, the axes that overflowed, one kept for later). It
was sent as a word of buttons with every bit above the three set:
every axis always overflowing. Upstream 9d4711a95 (Silent Scope,
Dreamkey). And the middle button is bit 3, not bit 0.
- The I/O board counted coins for two chutes: a third and fourth
player's were never counted. Four. "Subtract coins" took the
chute's number from the game unchecked.
- What is left of the board's reply after the message, up to a whole
word, is zeroes and not what the buffer had (upstream 58f05f74c, an
SH4 crash while a NAOMI starts).
Read, not run with a second player: the test frontend has one pad.
Since 8f332902 a memory card or an EEPROM whose write fails is written again
about a second later, for as long as the write goes on failing, with a
line in the log each time. Where the file cannot be written at all - no
such directory, a full disk - that was a write attempt and a log line
every second for the rest of the session.
The wait before the next try doubles each time, from a second up to a
minute, and the failure is logged the first time only. When a device goes
(the content is closed) it still tries once more, whatever wait was left.
A NAOMI game with its save directory somewhere that does not exist: one
"Cannot save EEPROM" line in 1500 frames, where there was one a second.
A game saves to a memory card in the middle of a frame; the bytes were
written to the card's file between two frames, by the thread that runs
them, which waited for the write and the flush. Next to nothing on a disk
with the system's cache in front of it; a frame or several where a write
goes through to a memory card, a network share or another process, and
the sound then stutters while a game saves. A NAOMI cabinet's EEPROM the
same, with the file made anew each time. (The audit's throughput item 6.)
core/savewriter.c: the bytes are copied and handed to a writer thread - a
list pushed onto with one compare-and-swap, the writer woken through an
eventcount; no lock, and no system call unless the writer is asleep - and
the writer does the waiting. Writes reach the file in the order they
were handed over. No more than 4 MB waits at once: past that the hand-over
is refused, the device keeps what it had to save and tries at the next
frame, by which time it is one write. A device waits for the writer
before it closes its file or goes; the thread ends with the content.
Without threads (TARGET_NO_THREADS) the write is made on the spot, as it
was.
And a write that fails is no longer forgotten. The flush used to count
what was to be written as written whatever seek, write and flush said, so
a card whose file could not be written for a moment stayed different from
its file for good. The writer tells the device, and the device writes the
whole card again (the EEPROM again) about a second later, for as long as
it goes on failing, and says so in the log.
tools/threads:
- savewriter_test.c, run by run.sh under ThreadSanitizer and
AddressSanitizer: the writer over a file system that can be made slow
and made to fail. 2000 writes over one another leave the file as
writing straight to it would; 40 writes of 5 ms each are handed over
in 0.05 ms and written 206 ms later; a failed write sets the caller's
flag and the card written again is in the file; more than may wait is
refused and what was taken is written; stop and start.
- the test disc's program writes a quarter of a block to the memory
card in the controller, and headless.sh looks for it in the card's
file after each of its runs (a new card each run).
The EEPROM files Virtua Fighter 4 and Dead or Alive 2 leave after 900
frames are the same files as before, with threaded rendering on and off.
What a failed write leads to in the devices has been run in the writer's
test, not in the core: the files here do not fail.
Of the NAOMI 2 sets only the Virtua Fighter 4 ones had an input map. The
rest ran with none: every switch under its JVS number, and the analogue
channels read as the two sticks' directions one after the other - so a
driving game's pedal was a stick's direction, half way down when let go -
and the frontend's list of controls named nothing.
Maps for 33 more sets, after upstream flycast's:
Initial D (13 sets) wheel on the left stick, accelerator and
brake on the triggers, VIEW, gear up and down
Club Kart (12 sets) wheel, accelerator, brake, VIEW (BET in the
prize versions)
King of Route 66 (2) wheel, accelerator, brake, HORN, WIPER, VIEW,
the three gear positions, the microphone
switch
Wild Riders handlebar, accelerator, front brake, push
and pull
Soul Surfer the board's swing, roll and pitch on the two
sticks
Virtua Striker 3 (3) long pass, shoot, short pass
Beach Spikers A, B
Sega Driving Simulator is left as it was: its wheel is read through a
force-feedback board the core does not have.
Three things these cabinets do that a button descriptor could not say,
and now can:
- a control wired to another of the same player's switches (Wild
Riders' push and pull are the lever's left and right; Soul Surfer's
left and right are buttons 1 and 2; King of Route 66's microphone
switch). ButtonDescriptor has a fourth member for the switch it is
read as; the second player's, which the gear levers use, was there;
- a switch that is always on, with no button for it: Soul Surfer's
floor mat, which says somebody is standing on the board;
- a pedal nothing is mapped to: Wild Riders' rear brake. A half axis
given a source the pad does not have reads as let go, where it read
as half way down.
Nothing changes for a set that had a map: a descriptor's new member is 0
unless given.
Not run: none of these sets is at hand, only Virtua Fighter 4's, which
starts and runs as before. The maps are upstream's, read against its
table set by set. Upstream also gives Initial D, Club Kart and King of
Route 66 a card reader or a force-feedback board, which this core does not
have; what those games make of their absence has not been seen.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QGA2kQLpYnygaJBecryMPV
A flat NAOMI image whose header says "Naomi2" is run as a NAOMI 2, with
the BIOS from naomi2.zip (or .7z). Until now every such image was taken
for a NAOMI 1, and the BIOS answered "this game is not acceptable by
main board" - which is what upstream flycast does with one as well.
What a NAOMI 2 has that a NAOMI does not:
- The geometry processor of the last patch, in area 2: its registers
at 08800000, its command port at 09000000, its 32 MB at 0a000000,
and at 085f6800 and 085f8000 the holly's and the PowerVR's registers
written to both PowerVRs at once (core/hw/pvr/elan_host.cpp). What
it sends goes into the tile accelerator the way a store queue write
does.
- A second PowerVR. The two are one here, as in upstream: one set of
registers, one video memory. What the second has of its own is its
normal interrupt status, at 025f6900: the end of a list and the end
of a render are raised in both, and the game clears each.
- The second PowerVR's video memory is the mirror 32 MB above the
first's, and games write every texture there too. The texture
cache did not watch the mirror ("the mirrors don't seem to be
used"), so a texture changed through it kept its old picture. On a
NAOMI 2 it is watched.
- Frames several times the size of anything a NAOMI sends: the tile
accelerator's buffer and the lists a frame is parsed into are four
times as big on a NAOMI 2, and a buffer kept from another machine
is not reused.
Save states: version V20 adds, on a NAOMI 2 only, the second interrupt
status, the chip's state and memory, and the time its texture transfer
ends. Older states load as before.
"Is this a NAOMI" is asked in a dozen places: SYSTEM_IS_NAOMI() answers
for both.
Tested with a flat image of Virtua Fighter 4 and naomi2.7z, in the
RetroArch nightly of 7 October under Xvfb (Mesa llvmpipe):
- The BIOS starts the game; the attract mode's temple scene and the
demonstration fight are drawn - letterbox, coin text, the stage, the
fighters and their shadows - by the OpenGL and the Vulkan renderer,
per triangle and per pixel, and look alike in all four.
- Against upstream flycast (which lights in vertex shaders), made to
run the same image as a NAOMI 2 by a change of three lines: the same
scenes, the same picture to the eye. The two do not reach a scene on
the same frame, so they were not compared pixel by pixel.
- What the chip sends the tile accelerator over 1500 frames is the
same, byte for byte, from run to run.
- Threaded rendering on and off: 138 pixels of a 1440x1080 frame
differ, where two runs with the same setting differ in 54. Those
are Mesa's: a texture with mirrored repeat comes out differently
from run to run on llvmpipe, and identically with plain repeat.
- A state saved, the game run 50 frames on, the state loaded and the
game run on: 100 frames later its state differs from a straight
run's in no more bytes than two straight runs differ from each
other (about 900 of 92 MB).
- tools/threads/headless.sh, the Dreamcast test disc: passes.
Not done here: the chip's memory is not in the recompiler's direct
mapping, so its accesses go by the handlers; NAOMI 2 games in MAME's
zip form are not in the game table; starting without a BIOS.
- A light gun answered that its two trigger axes were fully pulled. It
has none, and they read as at rest. Upstream: Death Crimson 2 took
the pulled trigger for "skip", and its story mode ran past.
- Asked for all of its status, a controller or a memory card answered
with the same 28 words as for the short request. Each now adds the
80 bytes the real one does, its maker's notes.
- After a state is loaded the memory card in memory is the state's and
its file is still what it was. The next block the game wrote was
patched into the file all the same, leaving a card that was part one
and part the other. The whole card is now written at that first
write, and anything waiting to be written when the state is loaded
goes out before it. Loading a state still does not touch the file by
itself. Upstream: "a partial save might corrupt the vmu file system".
The disc's program asks its controller for all of its status and
expects 48 words that go on with "Version"; on the old code it gets 28
(bad00904). The gun and the card after a state load are not covered by
a test here.
A VMU block write and a JVS EEPROM write only update the device's
memory and mark it dirty; maple_FlushSaves() writes the dirty span out
once per frame from retro_run, while the machine is asleep, and the
device destructors flush what is left. A save that used to cost a
seek, a write and an fflush per 128-byte phase on the emulation thread
is one write of its span per frame instead.
The VMU's LCD pixels live in the maple device, which the emulation
thread writes when the game draws to the screen. The renderers read that
same buffer when drawing the VMU overlay, and with Threaded Rendering on
they draw on the frontend's thread while the machine runs on: a picture
could be read half written. vmu_screen_needs_update was set by one
thread and cleared by the other the same way.
The emulation thread now publishes a whole picture through a triple
buffer each time the game changes one (and when a VMU is set up or a
state is loaded), and the renderer takes the latest: it only ever sees
a whole picture, and taking one tells it there is something new to
upload. vmu_screen_needs_update is left for what the frontend's thread
sets itself, a change of the overlay's options. The GL, GL per-pixel
and Vulkan renderers all go through the same two calls.
tools/threads/live.sh turns the first VMU's screen on, and
live_shot.py requires the top left corner of the screenshot to be the
LCD and not the background, so the overlay is drawn in every run of the
test, on gl, glcore and vulkan, threaded and not.
Vendor the current libretro-common VFS stack -- streams/file_stream.c,
file_stream_transforms.c, vfs/vfs_implementation.c and their headers -- and
wire RETRO_ENVIRONMENT_GET_VFS_INTERFACE in retro_set_environment via
filestream_vfs_init, so the frontend's VFS handles every file operation when
provided, with libretro-common's local implementation (UTF-8-safe on every
platform) as the fallback. This supersedes the fopen_utf8 spot-fix from the
previous commit with the proper single I/O layer.
Converted to filestream: coreio (all disc content: cue/gdi/cdi tracks and the
gdi/lst text parsing), the mds/mdf reader, libchdr via its coretypes.h porting
shim (core_file becomes RFILE; SEEK_* and RETRO_VFS_SEEK_POSITION_* share
values so whence passes through; chd.cpp owns the handle since chd_open_file
does not), BIOS/flash (flashrom.h), VMU/eeprom/JVS dump (maple_devs), the REIOS
font, the Vulkan pipeline cache, the m3u parser, and the NAOMI .lst/.bin
probes. The two stat() checks in retro_load_game were already replaced with
path_is_directory.
Bug found and fixed by the conversion: mds.cpp passed its raw FILE* as a
core_file* to RawTrackFile -- a type pun that made core_fread interpret stdio
internals as coreio's wrapper struct, i.e. mds/nrg image reading has been
broken; it now opens the mdf through core_fopen like every other format.
Version-skew fallout from syncing libretro-common: file_path.c split
(file_path_io.c added, with rtime.c and current stdstring), libretro.h updated
to current (additive; provides the retro_vfs_*_t typedefs incl. stat_64), and
the removed fill_short_pathname_representation replaced by a local helper over
fill_pathname_base + path_remove_extension.
Exceptions, by design: the NAOMI ROM cache maps files with mmap through native
fds (no VFS equivalent); files opened by name inside vendored libzip/lzma
(NAOMI zip/7z) keep their internal opens; debug-only dumps (modem, pcapng,
track dumper) unchanged.
Verified: clean Linux build; VF3tb from an accented path and DOA2 load and run
(content, BIOS, tracks via filestream); VMU create/write and flash save
exercised (deleted VMU recreated correctly).
RetroArch hands the core UTF-8 paths, but every file open in the core went
through plain fopen(), which on Windows interprets the ANSI codepage -- so any
path with non-ASCII characters (C:/Emulación/...) failed to open: content load
crashed and the BIOS fell back or failed, exactly as reported in
libretro/flycast#108 back in 2018 and still present until now.
libretro-common's compat/fopen_utf8 (already compiled into the core, just never
used) converts UTF-8 to UTF-16 and opens via _wfopen on Windows and passes
through to fopen elsewhere. Route every user-path open through it:
- content: core_fopen (cue/gdi/cdi/track files), the mds/mdf reader, and CHD --
the latter switched from chd_open(filename) to fopen_utf8 + chd_open_file
(libchdr's core_file is a plain FILE* and chd_open_file does not take
ownership, so CHDDisc now owns and closes the handle)
- BIOS and flash (flashrom.h Load/Save)
- VMU, eeprom and the NAOMI fw dump (maple_devs)
- the REIOS font, the Vulkan pipeline cache, and the m3u parser
- the two stat() existence checks in retro_load_game are replaced with
libretro-common's path_is_directory, which is VFS-routed and UTF-8-safe on
Windows (path_mkdir already was)
Not covered: files opened inside vendored libzip/lzma by name (NAOMI zip/7z
archives) still use their internal opens; converting those means patching the
vendored libraries and is left for a follow-up if reports warrant it. Debug-only
dumps (modem/pcapng) are unchanged.
Verified: Linux build clean; content in an accented path
(/home/claude/Emulación/Ségà/) loads and runs 400 frames through the new path;
the Windows branch of fopen_utf8 compile+link checked with mingw-w64. Runtime
validation on a real Windows accented path needs a Windows machine.
Create maple devices after loading the game so the required arcade
controls are known (AW).
Map ftspeed and maxspeed accel and brake to triggers.
Issue #865
Don't rely on having a lightgun controller at boot for hotd2 and others
make sure naomi VMU controller don't report inputs
Fix wrungp drive board error
JVS config for Guilty Gear, Maze of the King, Confidential Mission