150 Commits
Author SHA1 Message Date
libretroadmin 49648886d5 Log straight to the frontend's log_cb
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.
2026-11-14 02:42:03 +01:00
libretroadmin f6f0106dfe C89: the Maple host and the modem's DNS lookups
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.
2026-11-13 10:55:22 +01:00
libretroadmin 50b60907e7 C89: the cache controller and the AICA interface
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.
2026-11-12 19:20:06 +01:00
libretroadmin 79a1213f63 C89: the system bus, the GD-ROM drive and the force feedback board
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.
2026-11-12 17:43:46 +01:00
libretroadmin 6c44da308e C89: the core's headers compile as C, six files become C
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.
2026-11-11 23:19:27 +01:00
libretroadmin 7b24bec95f Replace miniupnpc, stb and xxhash with libretro-common
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.
2026-11-11 17:15:20 +01:00
libretroadmin 9d5383018b Run-ahead: no allocation for a state loaded every frame, and measured
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.
2026-11-10 19:31:38 +01:00
LibretroAdmin bf289e0e74 naomi: two I/O boards for the 4-player cabinets, as upstream
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.
2026-10-14 21:21:14 +02:00
LibretroAdmin 4f43a6ca27 naomi: the cards out of the readers in the save state (V30)
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.
2026-10-14 20:40:56 +02:00
LibretroAdmin 5a17ed6fa3 holly, maple: console ID and new cards without the C library's rand()
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.
2026-10-14 18:16:08 +02:00
LibretroAdmin 6b41116a31 pvr, rend, maple, libretro: TA limits, punch-through, contexts, guns, RTT
- 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.
2026-10-14 02:21:54 +02:00
LibretroAdmin 59220e0039 naomi: racing and other I/O boards, MIDI force feedback, F355 printer
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.
2026-10-13 23:10:28 +02:00
LibretroAdmin 87bad6bf31 naomi: the same first boot every time; I/O boards from the start
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.
2026-10-13 23:10:21 +02:00
LibretroAdmin 9787bcced0 naomi: System SP without maple devices, dinokich card reader, I/O boards
- 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.
2026-10-13 16:34:28 +02:00
LibretroAdmin 80ccf99352 maple: the NAOMI's EEPROM read from its file as the machine starts
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.
2026-10-13 16:34:21 +02:00
LibretroAdmin 97d8a8daea naomi: card readers, touchscreen, hopper, printer; the SCIF
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.
2026-10-13 14:37:49 +02:00
LibretroAdmin 5393a2828b maple, libretro: the VMU's beep; save state V24
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.
2026-10-13 14:37:45 +02:00
LibretroAdmin 5f0bc7f4b5 naomi: cartridge reset on a hard reset, LUT names, network packet number
- 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.
2026-10-13 12:26:55 +02:00
LibretroAdmin 3015196cc6 maple: a device change through a state load, the bus from the descriptor
- 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.
2026-10-13 12:26:51 +02:00
LibretroAdmin 9ef9f67ca1 serialize: V23, comm board, I/O board upload, mouse motion, held-back sound
- 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.
2026-10-13 11:08:27 +02:00
LibretroAdmin 087cc0c49c maple, mem: only changed devices unplugged, VMU clock, gun flag, fallback memory
- 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.
2026-10-13 11:08:16 +02:00
LibretroAdmin 0c96a1352b maple: memory cards stay through the loading of a state; less is written after one
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.
2026-10-13 10:21:54 +02:00
LibretroAdmin 3405e306ca maple: coins, encoders and reconnection in a state; a 32-bit build's states in a 64-bit one
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.
2026-10-10 23:23:25 +02:00
LibretroAdmin 855f6dffa1 maple: the VMU's screen read back, an empty JVS request, two answers to one question
"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.
2026-10-10 23:23:22 +02:00
LibretroAdmin ca211ee386 maple: frames are as long as their headers say; JVS and firmware bounds
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.
2026-10-10 20:09:19 +02:00
LibretroAdmin ba5d45f55a maple: JVS replies, the EEPROM kept in memory, players after a state load
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.
2026-10-10 18:04:45 +02:00
LibretroAdmin 99566a0625 maple: all arcade players without threaded rendering; mouse; coins
- 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.
2026-10-10 14:38:54 +02:00
LibretroAdmin 0c21663f5a maple: a save that cannot be written is tried less often, and said once
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.
2026-10-09 09:06:04 +02:00
LibretroAdmin 8f33290211 maple: what a game saves is written to its file by a thread of its own
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.
2026-10-09 07:16:31 +02:00
LibretroAdminandClaude Opus 5.5 4af6bb4658 naomi2: the other NAOMI 2 games get their controls
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
2026-10-09 06:00:05 +02:00
LibretroAdmin ad3ee3c7b1 naomi 2: the machine
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.
2026-10-08 03:50:06 +02:00
LibretroAdmin a9f605a1f2 maple: three fixes to the devices, from upstream
- 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.
2026-10-07 13:32:04 +02:00
LibretroAdmin f9f970c113 maple: VMU and EEPROM writes leave the emulation path
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.
2026-10-06 12:34:24 +02:00
LibretroAdmin 0468a7b56f Drop zlib
The default VMU image inflates through libretro-common's rinflate and
the BBA's receive checksum uses encoding_crc32, so core/deps/zlib goes.
2026-10-06 11:04:59 +02:00
LibretroAdmin 840219d06a vmu: the renderer draws the LCD from a published copy
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.
2026-10-06 02:34:18 +02:00
libretroadmin 63c599caa2 vfs: route all file I/O through libretro VFS
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).
2026-07-04 07:48:52 +02:00
libretroadmin 344ca696b4 paths: open files as UTF-8 on Windows (fixes accented paths)
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.
2026-07-04 06:37:01 +02:00
twinaphex cb3bd71cc5 Backport https://github.com/flyinghead/flycast/commit/e99aac357567302e1d1daf4b59ec8e4139c879da#diff-7194c04007dd96b8c496118886760725f8eb7b8bda6916d63bf6e3ea967e44a7 2021-04-12 02:32:26 +02:00
twinaphex 10732bdff0 Backport https://github.com/flyinghead/flycast/commit/f1c8679df7e480bab2529cd3b1072e7251b47cde 2021-04-11 03:48:05 +02:00
twinaphex 8fa6f79bcb Backport most of this commit - https://github.com/flyinghead/flycast/commit/ef16842074560bd0b8febcc006d31f57ae7c2914#diff-cd8013797851ba94cbf725dbb44a0cf68816b3277b36309bef4dceb39ff7272f 2021-03-05 23:22:56 +01:00
barbudreadmon aba25cd17e (Maple) Backport https://github.com/flyinghead/flycast/commit/8192eea387467b3aece1fa2aeccc75d320191749 properly 2020-11-18 18:53:41 +01:00
barbudreadmon 155ff88dc6 Revert "(Maple) Backport https://github.com/flyinghead/flycast/commit/8192eea387467b3aece1fa2aeccc75d320191749#diff-cd8013797851ba94cbf725dbb44a0cf68816b3277b36309bef4dceb39ff7272f"
This reverts commit e6dd855a05.
2020-11-18 18:12:51 +01:00
twinaphex e6dd855a05 (Maple) Backport https://github.com/flyinghead/flycast/commit/8192eea387467b3aece1fa2aeccc75d320191749#diff-cd8013797851ba94cbf725dbb44a0cf68816b3277b36309bef4dceb39ff7272f 2020-11-15 19:04:00 +01:00
Bradon Kanyid c4e5f45c0e Adds JVS IO response CRC byte, Add JVS Self Test command/response 2020-10-20 08:13:57 +02:00
barbudreadmon b799ca8ab3 rebase some code against upstream [flyinghead]
backport https://github.com/flyinghead/flycast/commit/0fd51fb52a1869749d31ce119bd2f48669fb3e67 https://github.com/flyinghead/flycast/commit/130110324519787bac1e7c542a0fe3a090e3a08f
2020-10-17 13:46:08 +02:00
Flyinghead 0f29d7e14a maple: don't overrun device strings
Issue #913
2020-06-23 18:50:07 +02:00
Flyinghead 1bf642e90c maple: create devs after loading the game. aw: better analog mapping
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
2020-06-22 13:40:41 +02:00
Flyinghead e595af5dfb maple: fix vmu reset in BIOS 2020-05-29 14:45:55 +02:00
Flyinghead a126b33e8a naomi: force lightgun for games that need it. Fix gundmct going left
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
2020-05-28 21:55:14 +02:00
Flyinghead 51661bb384 naomi: wrungp inputs. support inverted axis for naomi
fix input descriptor L2/R2 mixup
2020-04-15 18:56:03 +02:00