top of page
loading_reduced.jpg

2 / 07-10-2026

average rating is 2 out of 5

Performance

average rating is 2.7 out of 5

Features

average rating is 2 out of 5

Pureikyubu

Accuracy

competition

Pureikyubu (formally Dolwin) is an open-source Nintendo GameCube emulator which initially started development in 2004. It went into hiatus but is now active. With that in mind, it's not the most advanced but has interesting features and is able to boot and run some commercial games and demos.


  • Emulation requires DSP IROM / DROM dumps

  • A BIOS image dump is not required, but if you want to experiment with it, you can also add it in the settings. The BIOS is launched through the menu File -> Run Bootrom. Then you need to wait a bit and open the drive cover (File -> Swap Disk -> Open Cover). After that, IPL Menu will start :p

--

Build using Visual Studio 2019.  To build, open Dolwin.sln and click Build.

The executable will be at the root (Dolwin.exe)

Most Recent Changes

--2.0--

Version 2.0 is the peripheral release the 1.9.1 notes promised. The console's peripherals are one subsystem now: every device — a controller, a memory card, a network adapter, a Game Boy Player, a Game Boy Advance on a link cable — is a model of its own, with its own settings, its own place in the configuration and one window that edits them all. The Broadband Adapter and the Modem Adapter are emulated end to end from the console's side, the Game Boy Player is reached through the ARAM window it really lives in, and the memory card finally mounts: the command decoder, the EXI identity, the address wrapping and the streaming read were all wrong, and the card's own unlock challenge had to be implemented before a title could use one. Save states arrive for the GameCube side, with the portable machines keeping theirs and the front end offering ten slots of both. The software GFX pipeline leaves its experimental status: it draws the boot ROM's splash and the movie players, and the titles it runs agree with the shader backend (the black sky of The Wind Waker was the CP's BP
write mask). The interface gets eight themes, the Japanese titles of the selector, text that is one texel to one pixel at any display scale, a video output window that follows the window it is in, and
a status bar that reports the two clocks of the emulation. Need for Speed: Carbon, whose VM abort started this line of work, boots.


Highlights

  • One pool of peripheral devices. The emulation of a peripheral used to be wherever its consumer
    happened to be: si.cpp knew the controller's answer bytes, padsdl.cpp knew both the SDL side and
    the way a pad is polled, memcard.cpp mixed the card format with the EXI lifecycle, and a device's
    settings were kept by whoever used the device. A peripheral is a device of its own now
    (src/peripherals.cpp): a model registered by its module (cont.cpp, memcard.cpp,
    gbplayer.cpp, gbalink.cpp, bba.cpp, mdm.cpp), a port of the console's SI, EXI or
    Hi-Speed bus, a pool that is the Devices list of the configuration, and a property grid the
    settings window draws from what the device publishes. A build that does not compile a module simply
    has no such devices, and a new model appears in the settings window without a line of front end
    code.

  • The two network adapters. The Broadband Adapter (DOL-015) and the Modem Adapter (DOL-012) share
    serial port 1 (EXI channel 0, chip select 2) and are told apart by their EXI device ID. Both
    speak the console's own command word, then data protocol — an EXI shim in front of an
    off-the-shelf Ethernet controller (bba.cpp), with the address walking forward with every byte —
    and both answer the bring-up the SDK's ethernet library performs. The Broadband Adapter's frames go
    through a BbaBackend, and the backend the builds ship puts them on UDP (bbaudp.cpp), so two
    emulators play on one emulated segment. The network support is still a work in progress: no
    title in the SDK speaks IP, so the top half of the stack (ARP, IP, the sockets) is unproven, the
    challenge/response handshake and the split length field of a received packet are models rather than
    reconstructions, and with an empty peer the adapter is a console whose cable is unplugged.

  • The memory card mounts. Five things were wrong before a title could use a card, and each was
    invisible until a real program tried: the command decoder came up un-parked, the EXI device ID was
    shifted into the wrong byte, "erase card" was missing its two trailing bytes, an address past the
    part's capacity was refused instead of wrapping, and a read restarted from the command's address on
    every transfer instead of streaming on from where the last one stopped. With those and the card's
    unlock challenge (CARDUnlock, the bit-serial mixer the library hides in a read's spare
    fields), all six capacities were verified end to end — 4, 8, 16, 32, 64 and 128 Mbit — with
    CARDFormat, CARDMount and CARDCheck returning what the hardware returns.

  • The Game Boy Player is reached where it really is. The Player does not appear on EXI or SI: it
    hangs off the ARAM (SDRAM) controller, and the Start-up Disc talks to it by claiming an
    expansion window at 0x0100_0000 and then running a four-round complement handshake that a
    plain memory window cannot pass. gbplayer.cpp implements the window, the AMCR expansion size,
    the 32-byte command channels and PI interrupt bit 13, and once the channels hold their writes (a
    constant answer can never be acknowledged) the disc stops spinning and reaches
    ***Creating GbpSystemObject. The GBA support is still a work in progress: the Player's own
    firmware
    is not implemented, so the disc stops there, no 3840-byte frame block is ever read, and
    the link cable speaks only the JOY protocol a Game Pak runs from its own cartridge — not the
    multi-boot upload that puts a program into a Game Boy Advance.

  • Save states. The GameCube side gets the format the portable machines have had since
    src/gba/gba_savestate.*: the whole machine in one file, F5 to write and F7 to read the current
    slot, Shift+F5/F7 to step it, the same items in the File menu, and savestate, loadstate
    and states in the debug interface. A state is a header, a checksum and one section per subsystem,
    about 45 MB (51 MB with the software pipeline, which also carries its EFB). The round trip was
    checked byte for byte, and so was the property that matters: a state loaded into a machine that has
    run on resumes the run that never stopped.

  • The software GFX pipeline is no longer experimental. It renders the boot ROM's splash (the
    592-pixel picture the VI fetches with VI_PICTURE_CFG's own width and the horizontal scaler) and
    the movie players — the Wind Waker title screen above is its work — and the DolphinSDK demo
    sweep agrees with the shader backend. The shader backend remains the default. The fix that let the
    picture out was the CP's BP write mask (0xFE): every bypass register now merges the mask into
    its own value for the very next write, which is what keeps the GX library's shared payloads from
    clobbering each other — without it the library's blend write took the colour-update bits away and
    the sky of The Wind Waker's title screen came out black.

  • The GFX command dump. gfxdump records what the CP pushes into the XF, keeps the last frames
    of it as a ring, and shows the stream in three ways: the decoded commands, the raw big-endian words
    and the guest-RAM slices of the textures the frame sampled. It is a debugger tab of its own
    (GFX Dump), it is available as gfxdump [...] over JDI and MCP, and it is self-contained: the
    bytes a draw read from a texture or a TLUT travel in the frame that needed them.

  • The interface has a look of its own. Eight themes drawn from the site's palette (the default
    "Flipper", the six hues next to it, and "Daylight" for the light one), the Japanese titles of
    the selector merged into the font so that a Japanese disc's kana and kanji are drawn instead of
    blanks, text baked at the display scale so that a glyph lands on one pixel at any scaling, the
    video output window resized and titled (Video Output | shader | 59.9 fps) with the picture scaled
    into the largest rectangle of its own shape, and a status bar that reports the two clocks of the
    emulation
    : the console's time base and the host's wall clock.

  • Need for Speed: Carbon boots. Its VM abort was a TLB that was not flushed when a DSI/ISI page
    fault was taken, so the guest kept executing through a stale translation; the PTE's Changed bit
    is now set through the translation cache as the hardware sets it.


Added

The peripheral pool

  • What a device is, in one number. DeviceID names a model: 0x00010001 is the standard
    controller (DOL-003), 0x00020001 a memory card, 0x00030001 the Game Boy Player. A module
    registers its models (Peripherals::RegisterFactory), so the subsystem itself does not know what a
    pad or a card is, and a port belongs to a bus (SI, EXI or HSP) — a device can only be plugged
    into a socket of its own bus. The ports are the four SI channels, the two card slots, serial port 1
    (the two network adapters) and the Hi-Speed Port (the Game Boy Player).

  • A device publishes its controls and its properties. An actuator is one control
    (PAD_ACT_* for a pad, GBP_ACT_* for a Game Pak), a binding is what drives it (a keyboard key
    and a host controller button or axis), and properties are everything else a device shows about
    itself: a checkbox, a read-only line, a file the device owns (a card image, PERIPH_PROP_FILE) or
    one it only reads (a Game Pak or a BIOS, PERIPH_PROP_ROM). The kind picks the editor and the
    browser, so a device does not know how the dialog draws it.

  • The host side is a back end, not a part of a device. HostInput is implemented by the front
    end (padsdl.cpp over SDL2, padnull.cpp for the headless build) and by the unit tests; the
    rumble motor of a pad reaches the host controller through it. A device never makes an SDL call and
    the front end never knows what a pad is.

  • The pool is a list, not a table with holes. A device keeps its entry of the list — a handle of
    its own object it reads and writes its settings through — so taking a device out of the pool takes
    its settings with it and the entries that follow are not renumbered. The list is the content; there
    is no fallback in the code for "a slot the configuration does not mention"."peripherals":
    {
       "Devices":
       [
           { "Type": 65537, "Name": "Controller 1", "Port": 0, "VKEY_FOR_A": 27, "GCKEY_FOR_A": 65536 },
           { "Type": 131073, "Name": "Memory Card A", "File": "Data/card.mci" }
       ]
    }
    The settings of the builds before the pool (controllers with PluggedIn_<n>/VKEY_FOR_*_<n>,
    memcards with MemcardA_*, and the Device3_Name variables) are dropped from the document when
    the settings are read, so a configuration this build writes does not carry them along.

  • The lifetime. The pool is opened with the emulator, not with the machine — the settings
    window edits it with no game loaded — and the machine only matters to a device that talks to it:
    MachineOpened puts the cards into their slots and MachineClosed takes them out and flushes the
    files they were writing. A device is never destroyed while the emulator runs: one that is removed is
    detached, taken out of the list, and kept alive until the pool itself is closed in one piece.

  • testing/peripherals_test.cpp drives a pad the way a user does (bind a control, say the host
    reports it, poll the channel, read the SI registers) and covers the pool, the buses, the motor, the
    origins, and that a device added with its name and bindings is still there after a restart.


The two network adapters

  • The transport. Both adapters front an off-the-shelf chip with an EXI shim, and a selection
    is a command word followed by the data, most significant byte first, with the address walking
    forward with every byte — which is how the driver reads six MAC bytes or a packet in one burst:accesscommand wordthencontroller readfour bytes: 0x8000 and the address in bits 15:8an n-byte readcontroller writefour bytes: 0xC000 and the addressan n-byte writeshim readtwo bytes: reg << 8an n-byte readshim writetwo bytes: 0x4000 | reg << 8an n-byte writeThe shim's register is six bits and the controller's address sixteen; the register file is page 0
    and the packet memory is the pages above it. The device is told which transfer begins a sequence
    (Peripherals::TransferEXI passes the channel's firstImm flag), because a two-byte write can be
    either a command or a byte of data.

  • The Broadband Adapter (DOL-015). The controller's register file, its identification, its MAC
    address and its receive/transmit ring are emulated; the network behind it is a BbaBackend the
    front end installs. bbaudp.cpp puts frames on UDP: the "GCN Hardware" page names the other
    end (BBA_PEER, a host:port) and the port this emulator listens on (BBA_PORT), and the network
    is opened with the emulator. An empty peer is the default and means "no network" — the adapter then
    identifies itself, reports the link down and no frame arrives.

  • The Modem Adapter (DOL-012). The same command-then-data protocol with a six-bit register number
    of its own page: 0x01 is the status the driver polls, 0x0D the error status, 0x12 the raw
    status, and 0x06/0x07, 0x0E/0x0F, 0x10/0x11 are the divisor and threshold pairs. The
    register file, the identification, the acknowledge commands and the AT interface are emulated; the
    telephone line is not — the setup commands are answered OK, which is what the driver's
    bring-up waits for, and a dial is answered NO DIALTONE.

  • The command word is keyed on the assertion, not on a change. The flag that marks the first
    transfer after a chip select used to be set only when the selected device changed, and a driver
    handed a bus on which the socket is already selected begins its sequence with a select that changes
    nothing — its command word was taken for data. The flag is now set by every write that asserts a
    select, which is what the hardware keys on; three runs out of three of the modem replay driver now
    read the identification back.

  • The driver readsBroadband AdapterModem Adapterthe EXI device type0x040202000x02020000the controller id at 0x444D 58 ("MX")0x00020000the MAC bursta MAC (00 09 BF 00 00 01)the adapter's own idthe link state at 0x310300MISC2 (0x50) written 0x80, read back8000the shim revision written 0xD107, read backD1D1the challenge burst5A 5A A5 A500 00 00 00How the two are checked without a game. No SDK demo speaks IP, so what is checked is the half of
    the driver that lives on the console's side — the bring-up the ethernet library performs before any
    packet moves (bbadrv.py / mdmdrv.py load that sequence into a booted demo through the debug
    interface). Both adapters answer it, and they answer differently, which is the point:


The Game Boy Player and the link cable

  • The Player is an ARAM expansion, not a device on a bus. AMCR carries both the internal and
    the expansion size; the driver claims the largest window (0x0063), probes it with a four-round
    handshake that requires byte 1 of the answer to be the complement of the byte written, and
    settles on 16 MB (0x005B). The window's base is the internal ARAM size — all Player traffic is at
    ARAM 0x0100_0000 and upwards — and it is a window of registers and mailboxes, not of memory.

  • The channels hold their writes. The console's command routine is a read-modify-write: it reads
    a byte, clears the bits it is acknowledging and sets the bits of the command it sends, and writes
    the result back. A device that answered a constant could never be acknowledged, and the disc spent a
    20-second run on ~107 000 reads of +0x400000 and ~53 000 of +0xD00000. With the channels holding
    their writes and the interrupt raised only when the device changes state, the same run makes 526
    and 3 reads, and the disc gets on with its bring-up.

  • What the disc does now. With the port brought up to that point the Start-up Disc prints
    ***Creating GbpSystemObject, ***Allocating GbpSystemObject, ***Creating Display object and
    GBAPadInit : ClientImageSize = 19464 — the port bring-up is past the point it used to be stuck at.

  • The Game Pak is a property of the device. The cartridge file is kept in the Player's own entry
    of the pool ("GamePak"), the settings window draws a "Game Pak" row with a file browser on it, and
    the device carries the "BIOS" row as well (a real 16 KB GBA BIOS can be installed). A "Run Game
    Pak"
    switch decides whether the machine inside the Player costs anything at all: a Game Pak that
    is running costs about as much host time as the console it is attached to.

  • Watching the Game Pak run. A GameCube session publishes no GBA debug interface of its own, so
    the device publishes gbpshot <file.png>: the 240x160 LCD of the machine in the Player (or of the
    one on the link cable), written to a file, with the device's name, the size and the machine's frame
    counter in the answer.

  • The link cable (gbalink.cpp) is the same machine behind the DOL-011 JOY command set, and it
    runs only when it has something to do: a Game Pak in the bay, or the console driving the port
    as a link — the second case is what a multi-boot needs, because a title that streams its program
    into the partner's RAM does it with an empty bay. The device says so in the log once:
    GBA Link: the console drives the port as a link (command 15). It does not speak the transfer
    that uploads a program into a Game Boy Advance; that gap is what the Game Boy Player Start-up Disc
    runs into.


Save states

  • The keys and the menu. F5 writes the state of the current slot and F7 reads it back;
    Shift+F5/F7 step the slot (0..9, wrapping, shown in the status bar). The File menu carries
    Quick Save/Load State, a submenu per slot with the current one marked, and
    Next/Previous State Slot.

  • The commands. The debug interface has savestate [slot], loadstate [slot] and states, each
    answering Markdown next to the fields a client can use (file, slot, result, error/size).
    The machine is stopped for the duration of a save or a load and started again the way it was.

  • The files. A state lives next to the image and is named after it: Data/game.iso gives
    Data/game.st0...st9, pong.dol gives pong.st0.., the boot ROM gives Bootrom.st0...

  • The format. A header (the magic PSAVEST, the format version, the payload size and its FNV-1a
    64 checksum) and then one section per subsystem: META, FLPR, CPU , MEM , PI  , VI  ,
    AI  , DI  , SI  , EXI , CP  , DSP , DVD , GX  , PE  , XF  , SU  , RAS ,
    TEV , TX  , BP   and HLE . A section is a tag, a length and the bytes; the reader walks them
    in order and steps over a tag it does not know, and every section has to be consumed exactly to its
    end.

  • What a state does not carry, and each has its reason. The disc image, the executables, the boot
    ROM and the DSP ROMs (they belong to the settings); the memory cards and the rest of the pool (a
    card lives in its own file, the controllers are the user's hands); the samples already handed to the
    audio device; the EFB of the shader pipeline (it lives in an OpenGL target); the GL objects, the
    window and the host threads; the JIT's compiled blocks; the debugger's breakpoints; and the caches
    (written back and dropped, except the locked L1 data cache, which is scratch-pad memory and travels
    in the state).

  • A state that does not belong is refused by name — not a state, an unknown format version, a
    checksum that does not match, another game, another amount of memory, another build's section
    layout, the other machine's state, or an empty slot.

  • The checks it was written from. A headless build driven through the MCP transport wrote a state,
    read it back and wrote it again — byte for byte identical, on pong.dol and on the boot ROM with
    the software pipeline live. A machine stepped 20 000 instructions after loading a state produces the
    same bytes as one that never stopped, and the same holds when the state is loaded into a machine
    that ran free for five seconds. The tests found two real asymmetries (the memory interface's
    counters and the DSP recompiler's generation) and one cache problem (a load that left the caches
    holding the run in between) before they passed.

  • The portable machines keep theirs. Both GBA and Game Boy machines write and read a state with
    F3/F4 and Shift+F3/F4 (the slot is shown in the window title), the cartridge's .sav is
    still F5, and the debug interface has gbasavestate/gbaloadstate/gbastates and the Game Boy's
    gbsavestate/gbloadstate/gbstates. A state is a picture of a machine running a particular
    cartridge
    , so it carries the cartridge's identity and is refused by a machine running another one —
    or by the other machine entirely.


The GFX command dump

  • src/gfxdump.cpp records the graphics stream where the XF starts (the CP's own VCD/VAT state is
    not part of it), as four kinds of record: an XF register block load, an XF register read, a bypass
    register word with the BP write mask that preceded it, and a draw command with the vertex rows that
    followed it.

  • The dump is cut into frames by the frame counter and kept as a ring (GfxDump::MaxFrames); a frame
    past the byte limit is cut off and marked truncated. The stream is a self-describing sequence of
    big-endian 32-bit words, so a saved file does not depend on the host's word order or on a struct
    layout.

  • The RAM slices make a dump self-contained. A draw samples a texture (and, for a paletted one, a
    TLUT) programmed long before it, and the guest overwrites that texture in the next frame; the
    texture engine hands every such read to the dump and the bytes are copied into the frame that needed
    them.

  • The commands are gfxdump [frames], gfxdump commands [id], gfxdump hex [id],
    gfxdump capture [on|off|toggle], gfxdump select <id>, gfxdump clear and gfxdump save [id].
    The debugger has a GFX Dump tab next to the whole debugger, with a tab per answer ("Frames",
    "Commands", "Hex"), a capture button and a per-frame listing where every field is printed.

  • The CP also handles the 0x69 bypass command now, which is one of the commands a bypass stream can
    carry.


The interface

  • Eight themes. The look is a module of its own (uitheme.cpp): a palette of ten colours turned
    into the ImGui style, with the hovered and held states derived from the resting one. The default
    theme, "Flipper", is the dark ink of the project's site with the blue of the cube of its logo for
    everything that is selected and the violet of its shaded face as the second accent; six themes next
    to it are hues of the same recipe (Nebula, Emerald, Crimson, Amber, Graphite, Sakura) and "Daylight"
    is the recipe over a light background. A theme is picked in Options → Settings… → Interface,
    shown before it is applied, and kept in ui.THEME.

  • The interface mark. The pureikyubu cube of the project's logo is drawn from the palette
    (UiThemeDrawCube), so the interface is marked by the project and not only coloured like it.

  • Japanese titles. The selector's Title and Comment columns came out blank for every Japanese
    disc, because neither ImGui's built-in font nor the proportional faces of the systems have a kana or
    a kanji in them. Japanese is merged into the interface font from the CJK face each system has
    (Meiryo, Yu Gothic, MS Gothic, BIZ UDGothic and Arial Unicode on Windows; Noto CJK, VL Gothic, IPA,
    Takao and Droid Sans Fallback on Linux) over the 2999 ideographs of the Joyo and Jinmeiyo sets; a
    system without such a face shows the titles the way it did before.

  • Sharp text at any scale. The font atlas is baked at the display's own scale (1.0, 1.25, 1.5)
    while the interface keeps laying itself out at the logical one, so a glyph lands on the framebuffer
    one texel to one pixel instead of being a smaller texture stretched by the renderer.

  • The video output window (issue #458). It can be resized and maximized now: both presenters
    scale the picture into the largest rectangle of the window that has the picture's shape, centred,
    with the bands around it black — a maximised 16:9 window no longer turns the cubes of a game into
    bricks. The software back end scales on the CPU, one pixel at a time; the GL back end blits with
    the linear filter and clears the bands with the whole colour mask. The software path used to stop
    updating altogether on a resize, because SDL throws the window surface away and it was never asked
    for again.

  • The window titles. The video output window says which backend draws the picture and how many
    frames a second the emulator outputs (Video Output | shader | 59.9 fps); the main window keeps the
    plain name of what runs. Exactly one of the two counts a frame — the GL backend counts full-frame
    EFB→XFB copies, the video back end counts the XFB outputs it presents.

  • The two clocks (issue #458). The status bar shows the emulated Gekko and DSP throughput and the
    two clocks of the emulation measured from the moment the image was loaded: the console's time base
    (TBR 5m 09s) and the host's own clock (wall 1h 02m 03s). The emulated clock is a new OSSeconds
    command, because OSTime/OSDateTime roll their hours over at 24 and carry milliseconds.

  • The settings window. Every setting of the emulator is in one window with a strip of tabs on the
    left — General (the game selector), GCN Hardware (the console version and the firmware),
    Controllers, Memory Cards, Network, High-Speed Port and Interface — and a property grid on the
    right.

  • The stand-alone GBA settings. The portable machine is configured in a window of its own,
    Options → Stand-alone GBA… (uisettingsgba.cpp), because the machine it configures is a
    different one from the console the rest of the menu belongs to. It edits the file the first time it
    is opened, writes it back when "Save" is asked for, and edits the keys as well.

  • The selector's menu is disabled, and veiled with the reason, while the emulation runs, so a stray
    key or click can no longer load another image over the running one. Hardware → Graphics backend
    picks the shader or the software pipeline (the same switch as gxpipeline), and
    Debug → HW Profiler Overlay switches the profiler's overlay, which now follows the picture into
    the rectangle the picture took in the window.


Changed

The memory card, reviewed

  • The command decoder has to start idle (0xFF). The device came up with the field zeroed, read
    every byte of its first write as an extra, never decoded a command and answered nothing — which the
    console's library sees as "no card in this slot". MCConnect now parks the decoder.

  • The EXI device ID is not the card ID. The 32-bit type (EXI_MEMORY_CARD_59 = 0x00000004 and so
    on) is read with a four-byte immediate transfer, most significant byte first, so the capacity code
    lives in the low byte; the module shifted it into the top byte. The two-byte card ID and the
    one-byte status are shifted, which is what hid the mistake.

  • "Erase card" is three bytes (0xF4 0x00 0x00). The table gave it no trailing bytes, so the two
    zeros were refused as extras — and the refusal used Halt, which raised a front-end report on every
    retry and cost about 80x on the demo that does it.

  • The card wraps its addresses. A flash part is smaller than the address its controller can put on
    the bus, so a transfer at an address the part does not have is the same bytes as the wrapped
    address. The library reads at addresses that only exist on larger parts while it identifies the
    card. Reads, writes and sector erases now wrap.

  • A read streams from an internal address. The four offset bytes a read carries are where the
    block starts, and every transfer continues where the last one stopped; only a new command word
    resets the counter. The module re-read the command's address on every transfer, so the library's
    mount read the card's first four bytes nine times.

  • The unlock challenge. An original card does not hand its code over to anyone who asks: the
    library hides a challenge in the read command's spare fields (a 20-bit value and a length of 4..32
    from a generator seeded by the tick counter), works out the answer with a bit-serial mixing step,
    reads the card's code with the answer walking on between the words, and requires bit 6 of the status
    byte. memcard.cpp implements the whole exchange; the four details that make it work are that the
    code read comes from the controller and not the flash, that bit 0x40 is set only by a challenge,
    that a challenge is an immediate transfer and never a page read, and that a challenge can be exactly
    four bytes long.


The settings window is one window

  • The dialogs the pool replaced — Options → Controllers → Port n, Options → Memcards → Slot A and
    the settings that had no dialog at all — are gone from the menu, which carries a single
    Options → Settings… item.

  • A page follows the device, not the bus it hangs from. The cards and the two network adapters are
    all EXI devices of the pool, so a page that took its devices from the bus showed the adapters under
    "Memory Cards".

  • A setting takes effect as it is edited (a binding is used by the next poll, a card file is opened at
    once) and the pool writes the device back itself, so the window has neither an OK nor a Cancel.

  • The GBA's own page is separate: Options → Stand-alone GBA… edits GBASettings.json, including
    the eleven bindings of the machine. The default keys follow the hardware: a GBA has A to the right
    of B
    , so A is X and B is Z.


The software pipeline

  • The boot ROM's splash is drawn. The number of active lines comes from VI_VERT_TIMING and the
    geometry of a line from VI_PICTURE_CFG — its WPL field is the width of the picture in 16-pixel
    words and its STD field is the stride a field's lines are fetched with. The splash is 592 pixels
    wide (37 words, 1184 bytes to a line) and the picture came out skewed while the scanout assumed the
    640 pixels of the render target. VI_HSCALE then decides whether the horizontal scaler spreads the
    picture over the whole line (the boot ROM enables it) or leaves it one pixel to one pixel.

  • The movie players run. The software pipeline draws the movie players' output, which is what put
    the Wind Waker title screen on the screen above.

  • The BP write mask (0xFE) limits which bits of the very next BP register write are updated and
    then clears itself. Every bypass register honours it now, and the CP hands it down the bypass chain
    with the write. Without it the library's blend write took the colour and alpha update bits of
    CMODE0 away, and everything drawn after it wrote no colour at all — the black sky of The Wind
    Waker
    .

  • The software EFB is cleared with the first primitive of its frame, which is what a frame that
    begins with a full-screen primitive needs. A side effect of the same work is that a bypass register
    that honours the write mask no longer clobbers the bits another feature owns.


Gekko, the command processor and the serial interface

  • TLB flush on a page fault. The cache-management instructions that fill the TLB can leave a stale
    translation behind, and the guest kept executing through it after a DSI/ISI page fault —
    Need for Speed: Carbon's VM abort. The TLB is flushed when such a fault is taken.

  • The PTE's Changed bit is set through the translation cache, as the hardware sets it. Before,
    the bit was only written when the TLB entry was filled, so a page that was written through a cached
    translation never reported that it had been changed.

  • WPAR[BNE] is reported only when half of the write gather buffer is occupied, which is the
    condition the hardware reports it under (an experiment that the benchmarks now measure).

  • The CP handles the 0x69 bypass command.

  • Issue #416: deterministic stepping. The step command, the IPL's "no disc" path (--no-disc)
    and the display XFB reporting were made deterministic and repeatable, so a run can be stopped at an
    exact instruction and compared.

  • The serial interface answers a COM transfer to an empty socket the way the hardware does, and
    latches NOREP — a channel that answered a communication transfer with no reply must keep saying
    so.


The portable core

  • A parked DMA keeps its place in the block it is repeating. GBATEK reloads CNT_L and
    optionally DAD on a repeat, not SAD, so the source pointer carries on; a sound DMA is always
    repeating, and the old code restarted the stream at the same address on every FIFO refill.

  • A byte store to DISPSTAT keeps the half it does not address, which is what a game that writes
    one byte of the register expects.

  • The GBA settings get a window of their own (see above), the BIOS row's buttons have their room and
    the file is a user file rather than a shipped one.

  • The two Game Boy devices share the controls and the settings and differ only in which bus the
    console reaches them by.


Fixed

  • The black sky of The Legend of Zelda: The Wind Waker's title screen — the CP's BP write mask
    (see Changed), which is also what made the picture of a title that begins with a blend-mode write
    go dark.

  • Need for Speed: Carbon — the stale TLB translation after a DSI/ISI page fault and the PTE
    Changed bit (see Changed). The title boots and runs.

  • The memory card could not be mounted — five separate defects (see Changed), each of which made
    the console's own card library refuse the card.

  • A JSON list is replaced as a whole instead of merged member by member; the elements of a list
    have no name, so a merge could only go by position and folded every entry of the file into the
    first one. The bug was reachable as soon as the pool became a list.

  • The Game Boy Player's status byte had no return on its default path, so every status read the
    Start-up Disc made was whatever the return register happened to hold; the whole status block is
    decided now.

  • The ARAM DMA engine logged every block it moved, unconditionally. A Player disc that polls its
    window moves tens of thousands of blocks a second and every one of them cost two lines of text; the
    block honours an AR_LOG setting now, off by default. That one change is worth about 4x on a run of
    the Start-up Disc.

  • The GFX dump's frame counter and the presented-frame counter of the copy engine are pinned by
    tests: a texture copy and a partial display copy do not count, a full-frame display copy does.


Known issues

  • The network support is a work in progress. The adapter's own half is emulated (the transport,
    the registers, the identification, the MAC and the rings), but the network behind it is a
    backend
    , and no title in the SDK speaks IP: ARP, IP and the sockets the SDK's library builds on
    them are unproven. The challenge/response handshake and the split length field of a received
    packet's descriptor are models rather than reconstructions, and they are marked as such in the
    source. The Modem Adapter's telephone line is not emulated — the setup commands are answered
    OK and a dial is answered NO DIALTONE, which is the result code a modem with nothing in its
    socket reports. With an empty BBA_PEER the adapter is a console whose cable is unplugged.

  • The GBA support is a work in progress. The Game Boy Player's own firmware is not
    implemented
    : the port's window, the handshake, the channels and the interrupt are there and the
    Start-up Disc is past its bring-up, but every command the console sends is answered by the Player's
    firmware and nothing implements it, so no frame stream is ever read and the frame buffer the VI
    scans out stays empty. The link cable speaks only the JOY protocol a Game Pak runs from its own
    cartridge; it does not speak the multi-boot transfer that uploads a program into a Game Boy
    Advance, which is what the Start-up Disc's own 19 464-byte client image needs. A Game Pak running
    inside a Player or a link device is not part of a GameCube save state either: a state loaded
    with a Game Boy game in progress resumes with that machine at its reset.

  • The portable machines keep the issues of 1.9.1. The Game Boy Color's picture is still work in
    progress (a line is composed at once rather than dot by dot); the GBA's per-line OBJ cycle budget
    and DISPCNT bit 5 are not modelled and the video-memory contention cycle is missing; the sound
    output stage (SOUNDBIAS, the PWM amplitude) is not modelled; the HLE sound driver's reverb is not
    modelled; Nintendo's aging cartridge is not fully green (the remaining checks are in the
    waitstate/prefetch area); and rewind (running the machine backwards through a ring of states)
    is not implemented.

  • Save states. There is no unit test for the GameCube side yet — it was checked with a headless
    build driven through the MCP transport, and the portable harness covers the GBA and Game Boy
    states. The EFB of the shader pipeline does not travel, so a state of a machine running the shader
    pipeline resumes with whatever the title draws next; the JIT's compiled blocks are dropped; and a
    state that fails half way leaves the machine half loaded (the checks that can be made without
    touching the machine are all made first). Nothing is compressed: a state is 45 MB or so.

  • The software pipeline still does not do the per-vertex "emboss" bump texgen, the anisotropic
    filtering and the diag_lod/lodclamp refinements, the round/field_predict motion-compensation
    modes, the 16-bit R5G6B5 colour and Z formats of the anti-aliased EFB, the PE_COPY_VFILTER
    coefficients, the YUV/4:2:0 copy modes, the EFB CPU window (Cpu2Efb) and the Rev-B PE_CHICKEN
    behaviours. The shader backend remains the default, and a title that needs one of those is the
    reason to switch back to it.

  • The dump holds the last frames of the stream, not a whole session, and a frame past its byte
    limit is truncated; a texture read that the texturing unit does not perform (an unused stage, for
    instance) is not in the frame.

  • The Linux build still has no sound and no input for the GameCube side, and the Linux headless
    target has no OpenGL offscreen backend (the Visual Studio one does). The Settings tests that
    compare build/Data/GBASettings.json with the defaults byte for byte need that file to have LF
    endings.

  • The emulator's own unit suite is at 531 tests with three failures (Pe_ZWriteMaskControlsTheDepthUpdate,
    Pe_ZFreezeDisablesTheDepthWrites and Report_TevFog), and the same three fail on a build made
    before this work — they are the harness' own, and the two depth ones pass when they are run on
    their own.


Up next

The peripherals are not finished, and the next release is where they are finished. The two network adapters need the top half of the stack (a title that speaks IP, or a host-side bridge), the Game Boy Player needs the Player's own firmware — the command set, the video and audio format of the frame stream — and the link cable needs the multi-boot transfer. The Game Boy Advance a Player or a link device owns should also travel in a GameCube save state, which is what would close the one
hole in the state format that is machine state rather than settings. With that, the project moves into its steady state: planned, methodical improvement — performance work and bug fixing release after release, rather than another feature push. The architecture is in place; what remains is making what is there faster, more exact and more complete.



--1.9.1--

Version 1.9.1 is the maintenance release that finishes what 1.9 started. The integrated Game Boy Advance and Game Boy now sound and time the way the hardware does: the Game Boy's LCD runs on its own 4.194304 MHz clock, the Game Boy APU was rewritten against the manuals and has tests for the first time, the GBA's sound controller and its host-side BIOS sound driver match the official IPL, and the aging cartridge's timing checks pass. On the GameCube side the release is about the interface and the build: the legacy debug console and the Win32 front end are gone in favour of the single SDL2 one, the SDL port gains the settings and memory-card dialogs it was missing, the TEV gets its swap tables, and the recompilers run on 32-bit hosts and Windows 7. The portable suite closes at 362 of 362 tests.


Highlights

  • The portable machines sound right. The Game Boy's APU was wrong in almost every clock it has — the frame sequencer ran sixteen times too fast, the length counters counted backwards, the pulse and wave dividers were half their rate (every note an octave high), the duty phases were wrong and NR51 was swapped, which mirrored the whole stereo image. The suite that now pins it down (GbApu, fifteen tests) is new; the module had no tests at all. On the GBA, channel 3 was clocked by a timer, the mixer's levels were half of what the hardware gives the direct-sound FIFOs, the
    waveform RAM lost its second bank, and a decreasing sweep that underflowed stopped the channel.

  • The portable machines time right. The Game Boy's LCD advanced one dot per four clocks — a GBA rule — so a frame was 280896 clocks instead of 70224 and every device that counts clocks got four times its rate per frame of picture. The dot clock is now the machine's own 4.194304 MHz clock, one clock per dot. The GBA's memory model caught up with GBATEK: the internal memories charge their documented access cycles (including the undocumented 4000800h waitstate register), the Game Pak uses the first/second access times of WAITCNT with the 128 KByte non-sequential rule, a DMA transfer spends its cycles while it runs, and a transfer no longer rewrites the
    SAD/DAD registers.

  • The HLE BIOS is the official BIOS. The host-side BIOS calls have the official SWI numbers now (the sound driver is 1Ah..1Fh/28h/29h, not 28h..2Fh), the four decompressors were read out of the official image and fixed (HuffUnComp's bitstream starts at source + 4 + (treeSize + 1) * 2 and is read as a rotated word; RL's flag byte is one token, not eight), the sound driver mixes its twelve virtual channels, and the IRQ contract is the hardware's: acknowledging IF is the handler's job. Metroid Fusion on the high-level BIOS now draws the same frames as on the real image.

  • Nintendo's own aging cartridge runs. The AGB aging cartridge (AGB_CHECKER_TCHK10) is the release's oracle, exactly as the real BIOS image is for the HLE. Eleven of its checks were answered by the timing work — five MEMORY sub-tests, the H-Blank status and interrupt flags, the video capture window, KEY INTR, the DMA priority preemption and the address-control registers.

  • One front end and one debugger. The Win32 front end (ui.cpp, 4940 lines, with its DirectSound, GDI and GetAsyncKeyState back ends) and the legacy debug console are gone; the SDL2 front end is the only build, and the Debug menu has one item that opens the new debugger. The SDL
    port gains the two dialogs it was missing — Options → Settings… and Options → Memcards.

  • 32-bit and Windows 7. The recompilers exist as x86 modules next to the x86-64 ones: the 32-bit host no longer falls back to the interpreter, and the same sources build a 32-bit emulator.
    The Windows projects target Windows 7 (_WIN32_WINNT=0x0601), the oldest Windows SDL2 2.28 and
    the emulator run on.

  • The Flipper's TEV gets its swap tables. The two 2-bit fields of TEV_ALPHA_ENV are the raster and texel swap-table selects, not a mode and a fixed component pick; the four tables are the low four bits of TEV_KSEL_2k. The CP's BP write mask (0xFE) limits the very next register write, which is what keeps the library's shared payloads from clobbering each other. The demo tev-swap's doughnuts come out grey (0.0% saturated pixels, was 69%).


Added

The Game Boy side of the portable core

  • A GbApu suite (fifteen tests) and a GbPpu suite for the Game Boy machine, which had none: the
    divider rates of the four channels against the manuals' frequency formulas, the duty waveform phases, the length timer, the 512 Hz frame sequencer with the envelope at 64 Hz and the sweep at 128 Hz, the wave RAM access order and volume shifts, the noise LFSR sequence, NR50/NR51 mixing,
    the exact sample clock, PCM12/PCM34 (the CGB's view of the four generation circuits), the CGB's own high pass filter and the setting that turns it off. On the LCD side: the mode timings in the machine's own clocks (one clock to a dot, 456 to a line), the STAT/LYC interrupts, the CGB attribute map, the DMG compatibility rules and OPRI, the HDMA destination bank, and the LCD-off blank.

  • The two Acid2 pictures are pixel for pixel identical to Matt Currie's reference images —
    dmg-acid2 on the DMG, dmg-acid2 on a CGB in DMG mode (shade for shade through the emulator's own grey ramp) and cgb-acid2 on the CGB. They are what found the lost line-start STAT interrupts, and they are the end-to-end check of the one-line-at-a-time renderer.

  • A way to listen and to compare: --wav <file> records the mix in both harnesses, --rate N changes the sample rate and --no-hpf turns the DMG/CGB high pass filter off
    (audio.highPassFilter), which is what a recording that will be filtered later wants. The GBA harness also got --log (the core's own warnings, e.g. an unimplemented SWI, used to be thrown away) and --bios.

  • The Game Boy frontend blends each shown frame with the one before it — dmgemu's lcd_effect, the trail an LCD leaves while the picture moves. It lives in Host::Present, so a .gba and a .gb/.gbc run get it from the same code, and video.lcdEffect turns it off (on by default).
    The cores are not touched: the frame the screenshots and the debug interface read stays clean.

  • --keys is pushed through to the Game Boy machine, which is how the Metroid II run below was
    checked.


Build and tooling

  • The recompilers on 32-bit hosts (issue #417). gekkojit_x64.cpp, gekkojit_ps_x64.cpp,
    dspjit_x64.cpp and gekkojit_layout_x64.h are marked x86-64-only and each got a 32-bit sibling

  • (jit_x86.h, gekkojit_layout_x86.h, gekkojit_x86.cpp, gekkojit_ps_x86.cpp,dspjit_x86.cpp). The translator is the x86-64 one adapted to eight general purpose registers,
    four of them callee-saved, with the layout written down in gekkojit_layout_x86.h, and the state
    hash of a workload is identical on all four JIT builds — MSVC and GCC, x86-64 and i386.

  • Windows 7. The Visual Studio projects and CMakeLists.txt carry both module sets and let the
    preprocessor pick; the emulator projects define _WIN32_WINNT=0x0601 and WINVER=0x0601, and no
    emulator source calls an API newer than that, so the Windows 7 target of SDL2 2.28 is the oldest
    the emulator runs on.

  • SDL2 is linked as a static library from the bundled tree.

  • testing/gba_bench grew the Bus, HleBios, GbBus, GbApu, GbPpu, Audio and Settings suites; the Visual Studio project and the Readme follow, so the Windows build of the harness
    compiles the tests that compare the host BIOS with the official image.


Changed

The SDL front end is the only one (issues #421, #422, #420)

  • The Win32 front end and its back ends are deleted: ui.cpp (the main window, the menu, the game selector, the settings property sheet, the controller and memory-card dialogs), audio.cpp/audio.h (DirectSound), video.cpp (GDI) and pad.cpp (GetAsyncKeyState),
    together with the dialog, menu, accelerator and bitmap templates only they used. The interfaces the rest of the emulator speaks stay where they were; the application icon stays in the resource
    script.

  • The settings of the two ports are one pair of files again (Data/DefaultSettings.json and
    Data/Settings.json); the Windows/SDL split is gone, and the DOLDEBUG key, unread since the
    debug console was removed, is gone with it.

  • The legacy debug console is gone (cui.*, cuinull.cpp, cuisdl.cpp, debugui.*). The Debug
    menu of both ports carries a single item, Open Debugger…, which opens or closes a DebugUI2
    session; the crash report reaches the front end with the same UIReport command it used when no
    console was open.

  • The SDL port can mount a DolphinSDK folder again — File → Mount DolphinSDK as DVD… was a stub
    there and now opens the directory browser and calls DvdMountSDK.

  • The settings dialog and the memory cards (issue #420). Options → Settings… is one window
    with a tab per Win32 property-sheet page: the directories the selector scans and the file filter (GUI/Selector), and the emulated console version plus the three firmware images (GCN Hardware). The version combo carries a "User defined" entry so a hand-edited configuration is shown as it is; Apply writes the configuration the way SaveSettings does, with the paths going back through AddSelectorPath, and Cancel drops the copy. Options → Memcards is a window per
    slot: the connection flag, the save policy (SyncSave) and the card file, with Create New…
    asking for one of the six sizes the hardware has. Unlike the Win32 dialog, which left the running card alone until the next boot, OK re-points (flushing the card it replaces) or connects a card
    that is already open.


The GBA/Game Boy core

  • The sound controller against GBATEK and the AGB manual. Channel 3 is clocked by NR33 and
    not by a timer — SOUND3CNT_X bit 14 is the length flag and nothing else, and only the two direct-sound FIFOs are timer driven; the waveform RAM is two banks of 16 bytes with the CPU reaching the one that is not playing; the mixer follows GBATEK's "Max Output Levels" (a PSG channel spans a quarter of the output range, a FIFO the whole of it, and SOUNDCNT_L's level has "no effect on direct sound"); a decreasing sweep that would underflow keeps the frequency instead of stopping the channel; and the sum is clipped by the 10-bit output stage as the hardware clips
    it.

  • The device is the clock of the machine. The machine pushes every sample it mixes into the mixer buffer and the device plays it from its own audio callback. The buffer keeps a cushion of three video frames, throws away what does not fit, fills a period it cannot fill with silence instead of stale samples, and reports its delay, gaps and drops in the window title. The frame loop holds the machine back only when the buffer is genuinely ahead, and fast forward throws its sound away. The rate correction is a slow PI controller on a filtered level, gains worked out from the loop: the old one-liner answered a sawtooth with an undamped integrator whose period
    worked out at half a frame, which swung the full ±1% between neighbouring frames and threw away
    9450 frames a minute.

  • A repeating DMA streams its source. GBATEK reloads CNT_L and optionally DAD on a repeat — not SAD, so the source pointer carries on. A sound DMA is always repeating (the FIFO asks for 16 bytes at a time), so the old code restarted the stream at the same address on every refill and the FIFO played one 16-byte block over and over — a high buzzy squeak that vaguely followed the tune. A transfer also does not change the SAD/DAD registers, which hold the values the CPU wrote; the running pointers live in the engine.

  • The video capture runs on the lines GBATEK gives it — started at VCOUNT=2, repeated each scanline, stopped at VCOUNT=162 with the channel's enable bit self-cleared — and a
    higher-priority DMA request that arrives while a transfer is running is serviced at the next unit boundary instead of being lost until the channel's next trigger.

  • The EEPROM answers only in its own window. GBATEK puts the chip at D000000h-DFFFFFFh on a cartridge of 16 MByte or less (or in the last 256 bytes of a full 32 MByte image); everywhere else the ROM still drives the bus. That is what lets a game run its own EEPROM routine from the cartridge — Minish Cap fetches instructions between the read request and the data transfer. The chip's idle read also drives bit 0, the ready line the games poll after a write, instead of returning the mirrored ROM.

  • The host-side sound driver's channel array is twelve entries of 40h bytes at
    50h + n * 40h (GBATEK: 1-12 channels), not sixteen of 30h; a DMA's unit size follows CNT_H bit 10 on every channel, not just DMA3; the DMA's per-unit cost follows the bus width of the
    source and destination; and the internal memories charge their documented cycles, with the on-board 256K WRAM's waitstates taken from the undocumented 4000800h.

  • An idle WAITCNT bit 15 (the read-only Game Pak Type Flag) can no longer be set by a write, and DISPCNT bit 5 — the "H-Blank Interval Free" flag — no longer silently means something else to the renderer.


The GBA HLE BIOS

  • The SWI numbers are the official ones: the sound driver at 1Ah..1Fh, with VSyncOff/VSyncOn at 28h/29h. Before, the whole block was at 28h..2Fh and 1Ah was called DivArm2, so a game's SoundDriverInit was answered with a division and its music never
    started.

  • HuffUnComp, BitUnPack, LZ77 (both write variants), RL and the two delta filters were re-read out of the official image's own code and are now covered by a differential test that
    encodes random data into valid streams and compares the bytes both implementations wrote. MidiKey2Freq uses the BIOS's own fixed point — a 16.16 semitone table, the octave as a shift, the product truncated, the fine value as a slope — instead of a double-precision guess.

  • The sound driver's entry points are implemented from what a probe can measure out of the real BIOS: the work area's identifier 68736D53h and its 0FB0h size, the pcmbuf layout (two
    0630h byte halves at +0350h and +0980h), the two FIFO DMAs (B600h: repeating, 32-bit, with
    an incrementing source), SOUNDCNT_H 210Eh, the timer 0 reload for every playback frequency index, and SoundDriverMain, which mixes the twelve virtual channels with the driver's own
    envelope state machine and fixed-point levels. The reverb is not modelled, and the sample stepping is a 16.16 accumulator rather than the driver's exact arithmetic.

  • The IRQ contract is the hardware's: the custom boot ROM no longer acknowledges IF before dispatching, because the official BIOS does not either. Metroid Fusion's handler reads IF to see what happened, so a pre-acknowledged IF left it doing nothing every frame and the game froze on
    its title screen. The SIO link driver now installs its own acknowledge-and-return handler, which
    is what a program enabling that interrupt has to do.


Gekko, the TEV and the build

  • TEV swap tables and the compare operation. The two 2-bit fields of TEV_ALPHA_ENV are the
    swap-table selects (GXSetTevSwapMode's raster and texel selects) and the four tables are the low four bits of TEV_KSEL_2k/2k+1; TEV_KSEL resets to the tables GX initialisation programs
    (RGBA/RRRA/GGGA/BBBA). The compare operation shares the same bits, so it is decoded the way the
    driver encodes it — the bias field's fourth value marks it, the sub bit picks the comparison — and
    the mask tests the pre-shift stage value.

  • The CP's BP write mask (0xFE). A write to it limits which bits of the very next BP register
    write are updated and then clears itself. The library uses it for the registers whose payload two
    features share — the K constant selects and the swap tables of TEV_KSEL, the cull mode of
    GEN_MODE, the blend mode of PE_CMODE1 — so without it the swap tables would be clobbered by
    the first GXSetTevKColorSel. The mask travels down the CP chain with the write and each block
    merges it into its own register value.

  • Two TEV arithmetic details. The interpolated blend factor is a u0.8 fraction normalised over
    256, so 255 is exactly 1.0 and 128 is 129/256 (all three copies of the model divided by 255, half
    an LSB away), and DIVIDE_2 truncates without rounding. A bit-exact model of the design's stage
    then matches the demo on all 64 argument-sweep states, all 24 arithmetic states and the 11 states
    that used to separate the two models.

  • The disassembler. The halfword transfer's bit 22 selects the offset form and was inverted, so
    every immediate offset printed as a register and every register offset as an immediate — the
    official BIOS's own decompressors were unreadable. A register offset's shift is printed now, and a
    pre-indexed writeback is outside the bracket ([r1, #4]!, not [r1, #4!]). Both are what made
    the HLE BIOS work above possible.

  • The GBA module uses the stdint types (issue #419) instead of the project's u8/s8 aliases;
    json.cpp is self-contained (the standard library, verify.h and its own UTF-8 codec), so the
    portable core compiles it next to its own sources without SDL, OpenGL or ImGui, and the GBA
    settings reader uses the shared Json engine (issue #423) instead of its hand-written parser.

  • The debugger window is opt-in in --gba/--gb: emulation.debugger (false by default)
    decides whether it opens with the machine; the JDI node and the MCP transport come up either way,
    and F2 opens and closes the window as before.


Fixed

The portable machines

  • The Game Boy's clock (found by capturing zelda.gb and comparing it with another emulator).
    The LCD advanced one dot per four clocks, so a frame was 280896 clocks instead of 70224: the APU
    mixed 2940 samples a frame where the device plays 738.35, so three quarters of the music was
    thrown away in bursts (the rattle that would not go away no matter how the buffer was steered);
    DIV ran at 64 kHz instead of 16384 Hz and the serial port at four times its baud rate; and the CPU
    had four frames of work to get through in one. The emitter now runs on one clock per dot.

  • The Game Boy APU's every rate. The frame sequencer stepped every 512 system clocks instead of
    8192, so the length, the envelope and the sweep all ran sixteen times too fast, and it clocked the
    length on step 7 as well (320 Hz with jitter instead of 256). The length counters loaded
    64 - NRx1 and counted up to 64 — backwards. The pulse and wave dividers ran at
    (2048 - x) * 2 and (2048 - x) instead of * 4 and * 2. The four duty waveforms had the
    right ratios but the wrong phases. NR50's master volume was never applied to the mix, and NR51's
    halves
    were swapped, which mirrored the whole stereo image. The sample clock now keeps the fraction of a
    clock it used to round away.

  • The line-start STAT interrupts. BeginVisibleLine called EnterMode(2) and threw its return
    value away, so the STAT request a line start produces — the mode 2 source turning on, and an LYC
    that matches the new LY — never reached IF. Every visible line's mode 2 and LYC interrupt was
    lost (the ones inside VBlank still worked, which is why it hid), and the raster effects driven by
    LY=LYC that draw the Acid2 hair, eye, mouth and footer never ran. A register write can raise the
    STAT line now (enabling a source whose condition is already true, or a LYC write that makes it
    match "constantly"), and STAT bit 2 stays live while the LCD is off.

  • A CGB running a monochrome cartridge (the frontend's default for a DMG game) ignored the
    DMG's own display rules: LCDC bit 0 was the CGB's master priority instead of blanking the
    background and the window, the window bit was not overridden by it, objects were prioritised by
    OAM position instead of X (OPRI was stored but never reached the PPU), the bank 1 attribute map
    was read although the manual says that bank "is not present in this mode", and
    BGP/OBP0/OBP1 were ignored instead of indexing the CGB palettes.

  • The CGB's VRAM DMA always wrote bank 0, while the manual is explicit that the destination is
    VBK, and reading HDMA5 during an active HBlank transfer returned bit 7 set, which Pan Docs
    defines as "Not Active". The mode 3 OBJ penalty 11 - (X mod 8) dropped to 4 or 5 on the last two
    columns of a tile where Pan Docs floors it at 6. Turning the LCD off left the last picture in the
    frame buffer instead of the blank white of the disabled screen.

  • The GBA LCD audited against the manuals. BG2 is affine in mode 1 (Final Fantasy V Advance's
    "SQUARE ENIX PRESENTS" screen was a field of noise); the bitmap modes sample through the BG2
    matrix (the boot ROM and the no-BIOS start leave the identity matrix, as the real BIOS programs
    it); mode 5's bitmap line is 320 bytes and not mode 3's 480; a window's garbage dimensions reach
    the screen edge; the OBJ window needs DISPCNT bits 12 and 15; the Green Swap exchanges the
    green of each pair of dots instead of byte-swapping every pixel; a semi-transparent OBJ needs a
    2nd target selected in BLDCNT and the window's effect bit gates the alpha blend as well as the
    brightness; an OBJ whose 8-bit Y range runs past line 255 wraps to the top; and the 28-bit affine
    reference point is kept sign-extended.

  • The text layers' scroll offsets. BGxHOFS/BGxVOFS hold a nine-bit offset (0-511) but were
    sampled through the register's readable form, which the PPU trimmed to eight bits. Castlevania:
    Circle of the Moon's attract demo scrolled a room to dot 296, the renderer used 40, and the top of
    the screen showed whatever the map's other half happens to hold — a band of green "corrupt
    background" — while the camera appeared frozen relative to the player.

  • A double-sized affine OBJ is anchored in the middle of its doubled area. The X/Y attributes
    are the upper-left corner of the display area and the rotation/scaling centre is the middle of
    that area — half a base size right and below the reference point normally, a whole one when the
    double-size flag is set. Every doubled OBJ sat half a base size too high and too far left; the
    real BIOS's boot animation is what pinned it (the 64x64 letters snapped down by 32 pixels on their
    landing frame). Final Fantasy V Advance's letterbox also pins the OBJ Y wrap: its rows of 16x16
    OBJs at Y=240..243 fill the gaps the wrapped lines leave.

  • The V-Counter match interrupt is an edge, not a level. UpdateVCountMatch requested it
    whenever the match condition was true on an evaluation, and a DISPSTAT write evaluates it too,
    so a program that wrote the register back while VCOUNT still equalled its setting took two match
    interrupts for one line. Minish Cap writes DISPSTAT at line 80 of every frame — in the handler
    of the match it has just taken — and its own copy of the BIOS sound driver advances its sequencer
    once per match, so the intro music ticked twice per frame: the notes came twice as fast and the
    track ran out of sequence at 11 s. It is now the edge of the gated (match AND enable) condition,
    which is what GBATEK's "requested when the flag becomes set" means; a program that enables the
    interrupt while the counter already matches still gets it.

  • The Save file of a fresh Minish Cap cartridge came up corrupted. The EEPROM's ready line
    (bit 0 of the ROM bus in the chip's window) was never driven, so the save library's poll after a
    write always saw a busy chip, every write timed out, and after three attempts the library stamped
    its DAMEDAME failure marker over the block it was writing — file headers included. With the
    ready line on the bus every write is verified on the first attempt.

  • The HLE sound driver's channel array was misaligned. Sixteen entries of 30h bytes spans the
    same 300h bytes as the real twelve of 40h, which hid the mistake: only channel 0 landed where the
    official driver looks, so channels 1..11 were mixed from the next channel's envelope and volume
    bytes — heard as wrong notes, crackle and an apparent speed change.

  • The halfword transfer offset form in the disassembler (see Changed), which is what made the
    decompressors readable.


The core, the harness and the shell

  • Thumb LDMIA/STMIA skipped an instruction. ThumbMultipleTransfer treated bit 7 of the
    register list as "r15 in the list", but a Thumb register list is eight bits wide (r0-r7) and can
    never name r15. The BIOS loads its BitUnPack parameter block with LDMIA r1!,{r5,r7}, so the
    instruction that followed was skipped, the block stayed zero, BitUnPack read a zero item count
    and bailed out: the seven letter sprites had no glyph data and the boot animation was invisible.
    This is why the emulator's own boot ROM did not draw.

  • SVBK maps a written 0 to WRAM bank 1. A DMG-only cartridge runs on the emulated CGB in
    compatibility mode, and the register mapped a written 0 to bank 0. With the power-up value 0xF8
    that aliased 0xC000-0xCFFF and 0xD000-0xDFFF onto one 4 KByte page, so a game whose variables
    or stack live in the upper bank had its own low-RAM scratch overwrite them: Metroid II kept a
    return address at 0xDFFB, read back 0x0000 and restarted from the reset vector for ever.

  • Json's one-byte UTF-8 sequences. The local decoder's shortest-form table had entries for two,
    three and four bytes only, so every ASCII character came out as U+FFFD. Since AddUtf8String is
    how the whole debug interface builds its Json, the regression corrupted every answer made that way
    — the Markdown panels of the debugger, the disassembly and the symbol names, the reports, the MCP
    tool answers and the OS time of the status bar (which showed a row of question marks). Of the 16
    unit-test failures at the parent commit, 15 were this.

  • The Apu::ReadSamples call site in the tests handed a maxFrames count to an
    int16_t buffer[128] while the call writes two samples per frame: the 256-byte stack buffer was
    overflowed and the run died later, in the middle of another suite. Found with AddressSanitizer;
    the buffers are sized for stereo frames now.

  • The DMA transfer's unit size follows CNT_H bit 10 on every channel (see Changed), which is
    why the Minish Cap engine's per-frame 32-bit DMA0 block was half copied before.


Known issues

  • The official GBA BIOS runs its boot and its animation is now drawn: the mode 2 BG3 that is
    only visible inside the OBJ window, with the nine 32x64/64x64 window sprites, WINOUT = 0x3F27
    and alpha blending around it. Both the emulator's own boot ROM and the real image hand over to a
    cartridge. What is left to model on the cartridge side is the EEPROM window of a full 32 MByte
    image (the last 256 bytes); the harness exercises the 16 MByte layout, where the chip answers
    anywhere at D000000h-DFFFFFFh.

  • A real Game Boy Color cartridge's picture is still work in progress: the machine boots, runs
    its own boot ROM and passes its tests, but at some moments its frame does not match VRAM. The
    remaining difference is read as the mode 3 line length (a line is composed at once rather than dot
    by dot), the HBlank/HDMA timing and the PPU-internal VRAM read-block windows. DMG and CGB in DMG
    mode are exact against the Acid2 references.

  • The GBA's per-line OBJ cycle budget and DISPCNT bit 5 are not modelled, and the
    VRAM/OAM/Palette contention cycle ("+1 cycle if the GBA accesses video memory at the same time")
    is not: the accesses take their documented 1/1/2 cycles but never the extra one. The cartridge
    prefetch buffer is modelled as a waitstate rule rather than as a real 8-halfword buffer.

  • A preempted DMA transfer runs to its end before the preempted one resumes instead of the two
    interleaving a unit at a time. Nintendo's aging cartridge's remaining failures (the
    waitstate/prefetch checks, KEY INPUT SIMPLE) are in this area, and are recorded in
    testing/gba_bench/Readme.md with the addresses of its test table.

  • The GBA's sound output stage is not modelled: SOUNDBIAS is stored and read back, but neither its
    bias level nor the PWM amplitude resolution changes the mix, which is produced as clean 16-bit
    samples. The wave RAM is plain memory rather than the hardware's shift register.

  • The sound driver's reverb is not modelled on the HLE path (the mode's reverb bits are stored, the
    driver's delay line is not), and the pitch stepping is a 16.16 accumulator rather than the
    driver's exact fixed point.

  • Save states, rewind, the EEPROM's "last byte is the AND of the old and new value" quirk and the
    RTC's per-minute interrupt register are not implemented on the portable machines.

  • The serial port's JOY bus mode (RCNT bit 15, a GameCube controller on the link port) is decoded
    but not driven; UART mode works register-wise but has no peer. The emulator-to-emulator cable
    (normal and multiplayer) is implemented and tested.

  • The Linux build still has no sound and no input for the GameCube side, and the Linux headless
    target has no OpenGL offscreen backend (the Visual Studio one does). The two Settings tests that
    compare build/Data/GBASettings.json with the defaults byte for byte need that file to have LF
    endings; a Windows checkout with core.autocrlf reports the \rs as a difference.

  • Nintendo's aging cartridge is not fully green: ten checks still fail, all of them in the waitstate/prefetch area above. The GBA's Ppu test RegisterReadBack and the memory test ROM
    pin the data rules where the timing is approximated.



--1.9--

The emulator gains a second machine — a from-scratch Game Boy Advance and Game Boy core — a second rendering path — a complete software implementation of the Flipper graphics blocks — and a new debugger, a local MCP server for LLM agents, a hardware-interface profiler, a headless build and a DSP recompiler. The whole source tree was audited for the data it
loads and the debug interface speaks UTF-8. On the compatibility side the Metroid Prime intro movie runs several times faster and the release reaches the game's title screen, Super Mario Sunshine boots and plays its intro, and titles such as Star Wars Rogue Squadron III, Super Monkey Ball 2, Prince of
Persia and Soul Calibur II get their pictures.


Highlights

  • An integrated Game Boy Advance and Game Boy. src/gba is a second machine written from the
    public hardware specifications (the ARM Architecture Reference Manual for the ARM7TDMI, GBATEK for the GBA peripherals, the
    Pan Docs for the Game Boy). It has its own free boot ROM whose animation draws the pureikyubu
    logo, its own SDL2 front end and its own test harness; pureikyubu --gba game.gba runs a
    cartridge and pureikyubu game.gbc runs the Game Boy machine inside it. It exists to be a
    GBA Link peer and a Game Boy Player stand-in for the GameCube side.

  • A software GFX pipeline. The Flipper graphics blocks (XF, SU, RAS, TX, TEV and PE) now have a
    CPU implementation next to the OpenGL one. It draws into a real EFB memory array, a real TMEM, and
    the copy engine turns the finished frame into the XFB the video interface scans out — no OpenGL
    context at all. hardware.GFX_PIPELINE and the gxpipeline command switch pipelines on the fly.

  • A new debugger (DebugUI2). Its own thread, its own OpenGL 3.3 window and a session folder per
    run; every panel is the Markdown of a JDI command, so the whole Flipper — thirteen tabs from Gekko
    to the profiler — and both portable machines are inspectable in place. Debug → Test New Debugger
    opens it, F2 starts and stops a session.

  • An MCP server. pureikyubu --mcp runs the emulator as a local Model Context Protocol server:
    every command of the debug interface becomes a tool an LLM agent can call (~150 before a machine is
    loaded, ~170 after), over stdin/stdout JSON-RPC.

  • A hardware-interface profiler. The status bar could say how fast the CPU runs; the profiler
    says whether the data moves at all, and where: the 60x bus, the Flipper/Splash bus, the PI and CP
    FIFOs, the write-gather buffer, every DMA engine, the audio mixer input, the GFX primitives, the VI
    frames — per emulated second.

  • A headless build. uinull.cpp and a null GFX layer make the emulator a windowless console
    application for benchmarks, test sweeps and the MCP server; the Visual Studio headless target also
    draws with a real OpenGL context on a hidden window, so frames can still be read back.

  • A DSP recompiler. The DSP core got a basic-block compiler that shares the common x86-64
    emitter with the Gekko one; it is 1.5–1.8x faster than the interpreter and off by default.

  • A security review and a UTF-8 pass. About ninety defects reachable from the artifacts the
    emulator loads were found and fixed behind a small verifier layer, --selftest reports a startup
    failure instead of disappearing, and the debug interface, the console and both front ends now
    speak UTF-8.

  • Compatibility. Metroid Prime's intro FMV went from ~3.5 to ~9–11 frames a second and the run
    now reaches the title screen inside 100 seconds; Super Mario Sunshine boots, plays its intro and
    reaches the scene after it; Super Monkey Ball 2 stops spinning before its first frame; Star Wars
    Rogue Squadron III no longer faults while its MMU handler maps the page; Prince of Persia's memory
    card dialog is readable.


Added

The integrated Game Boy Advance and Game Boy (issue #388)

  • A second machine in src/gba, written from the public specifications and independent of the
    GameCube side. The Game Boy Advance core implements the ARM7TDMI (ARM and Thumb, all seven modes,
    the exceptions and HALT), the memory map with the open bus and the WAITCNT waitstates, the LCD
    (tile modes 0–2, bitmap modes 3–5, sprites, windows, mosaic, blending, forced blank), the four
    timers, the four DMA channels including the sound-FIFO and video-capture timings, the keypad, the
    interrupt controller, the sound (the four legacy channels and the two direct-sound FIFOs), the
    serial port with an emulated link cable, the cartridge (ROM plus SRAM, Flash and EEPROM saves and
    the GPIO/RTC port) and the BIOS service functions in the host.

  • A free boot ROM, because the official IPL is copyrighted: the boot animation is emulated ARM code
    assembled at run time by a small emitter (src/gba/gba_armasm.cpp). A wireframe hypercube
    collapses into the pureikyubu cube mark while the wordmark scrolls in, and the boot ROM then hands
    the machine to the cartridge the way the real BIOS does. gba_bench --dump-bootrom bootrom.bin
    writes the image and its assembly listing out for review.

  • The Game Boy machine the GBA carries inside it (src/gba/gb_*.cpp): the LR35902, the DMG/CGB LCD
    with the CGB palettes, VRAM banks and attribute map, the four sound channels and the MBC1/2/3/5
    mappers with battery saves, with its own 256-byte boot ROM.

  • The modes: pureikyubu --gba game.gba, pureikyubu --gba (no cartridge — the boot ROM's SIO link
    driver, i.e. the "GBA Link" state), pureikyubu game.gbc, --gba-bios bios.bin for a real BIOS
    image and --no-gba-bootrom to skip the BIOS. The window, the sound and the input come from SDL2,
    and the bindings live in Data/GBASettings.json.

  • A harness of its own (testing/gba_bench), because the core has no SDL, OpenGL or ImGui
    dependency: unit tests, a headless ROM runner with per-frame hashes and PNG dumps, a speed
    measurement and a link test that plugs two instances into each other. The public MIT test ROMs
    (jsmolka/gba-tests) run through it — the memory, ARM and Thumb suites report "All tests passed",
    and they are what found the VRAM mirroring, the byte-store rules of the video memory and the
    register-shift-by-zero carry rule. The suite is at 263 tests and all of them pass.

  • The GBA and GB cores are debugged from the host like everything else: gba, gbaregs, gbacpu,
    gbamem, gbappu, gbadma, gbtimers, gbsio, gbcart and the gb* equivalents are JDI
    commands, and DebugUI2 builds its panels for both machines.


The software GFX pipeline (issue #384)

  • A complete CPU implementation of the Flipper graphics blocks in the same modules as the shader
    backend, with the software methods carrying a Soft prefix: the transform unit (the matrix
    multiplies, the projection combine, per-vertex lighting, texture-coordinate generation, the
    guard-band clipper and the divide plus viewport mapping into window space), the setup unit
    (primitive assembly, edge coefficients, interpolation planes, culling, zfreeze), the rasterizers
    (the 2×2-pixel quad grid with a 12-bit coverage mask per quad, the top-left rule, the scissor and
    perspective-correct interpolation), the texture unit (a real TMEM, the load commands, a tag cache,
    the LOD computation and the point, bilinear and trilinear filters over every texel format), the
    TEV (16 combine stages, the Rev-B K constants, alpha compare, the Z-texture environment, fog and
    the final alpha function) and the pixel engine (a real EFB memory array, the Z test, blending,
    logic ops, write masks and the copy engine).

  • The pipeline is a configuration variable (hardware.GFX_PIPELINE, 0 = shader, 1 = software) and
    can be switched at run time with gxpipeline soft / gxpipeline shader; the choice is stored in
    the settings. The two paths share no rendering state.

  • Indirect (bump) texturing is implemented in the software TEV with the same arithmetic as the
    shader backend, so the two pipelines produce the same picture.

  • testing/gfx_soft_test.cpp drives the pipeline through the hardware API only — the XF and BP
    register spaces and object-space vertices — and reads the result out of the EFB memory and the
    XFB. The DolphinSDK sweep renders the same 87 demos through it and report.py puts the two
    pipelines side by side. The pipeline stays experimental: a few titles still have picture
    defects (the anti-aliased framebuffer demos among them), and the default remains the shader
    backend.


The new debugger - DebugUI2 (issues #371, #397, #407)

  • A debugger that runs in its own thread and talks to the emulated machine only through JDI and the
    Debug API; the window is a separate SDL window drawn with OpenGL 3.3 and a glyph atlas rasterized
    out of Data/DebugUiMono.ttf.

  • A session per run (Data/Sessions/<image>_<ordinal>, a JDI entity of its own). Every panel is
    the Markdown answer of a command, so the message history, the command line and a snapshot of the
    emulated state can be reviewed offline.

  • The GameCube session has thirteen tabs: Gekko, the three processor panels (registers,
    disassembly, memory), the nine Flipper subsystems and the profiler. The subsystem reports are one
    command per block — airegs, viregs, piregs, miregs, diregs, siregs, exiregs,
    cpregs and dspstate — each answering the decoded state of its block.

  • A portable session (GBA and Game Boy) is its own front end: F2 starts and stops it, the panels
    come from the gba* / gb* commands, and an MCP client that launched pureikyubu --gba gets the
    portable commands rather than the GameCube ones.

  • Focus, scrollbars and tabs: the panel under the pointer takes the focus, wheels and scrolls, a
    panel whose content does not fit gets a draggable scrollbar, and the tab strip selects between
    stacked sub-panels. Fixed along the way: DrawText placed every line an ascent too high, the
    performance counters crashed the debugger when it was opened with no machine loaded, and the
    scroll and tab state now survives the snapshots.


The MCP server (issue #383)

  • pureikyubu --mcp publishes the whole debug interface as MCP tools over stdin/stdout (one
    JSON-RPC 2.0 message per line). The client starts the emulator as its own child process, so there
    is no port, no listener and no authentication.

  • The tools are not a second interface: src/mcp.cpp builds the tool table from the can records of
    the registered nodes, so a command added to the emulator appears in the tool list by itself. Every
    tool carries the help text, the usage and the arguments of its command; a failing command is
    reported as a failed tool so the model can correct itself.

  • mcp 0 / mcp 1 start and stop the server from the debugger console, and McpRequest <json>
    drives the whole protocol as one command, which is how it is tested without a transport. The
    answers are capped at 4 MB so a command like FileLoad cannot push a binary into the model's
    context.


The hardware-interface profiler (issue #394)

  • src/hwprof.* watches the console's information-exchange channels and reports what each one
    carried per emulated second: the 60x bus, the Flipper/Splash bus, the PI interrupt assertions, the
    write-gather buffer, the PI→CP command FIFO, the audio mixer input, the EXI/DI/DSP/AI/ARAM DMA
    engines, the GFX primitives and vertices, the VI frames and the instructions the two cores retired.

  • The window is one emulated second rather than a wall-clock one, so a channel that moves 4 MB per
    console frame reports the same rate whether the host runs at 0.2x or at 5x. The instruction
    counters are the ones the cores already keep, so nothing is added to the Gekko hot path; a DSP
    paired instruction counts as one.

  • Three presentations: the hwprofile Markdown table, the hwprofile image PNG next to the session
    and the hwprofile osd overlay in the emulated picture (hwsod 1, stored as HW_OSD, off by
    default). The overlay asks the debug interface for the report and hands the rasterized picture to
    whichever back end presents the frame — the OpenGL pipeline draws it after the frame dump, and the
    software pipeline's video blitter puts it into the RGB output buffer.

  • DebugUI2 gets a Profiler tab next to Registers, Disassembly and Memory.


The headless build (issue #382)

  • uinull.cpp (the repurposed uisimple.cpp) is the entry point and the whole UI of a console build
    with no window: a bare image or --ipl runs until Ctrl+C, --bench <file> [sec] is a first-class
    headless mode, and the reports are echoed to the console.

  • The measurement loop moved out of the SDL front end into bench.cpp/bench.h, so both front ends
    measure with the same code and the wall clock is std::chrono.

  • gfxnull.* runs the GFX pipeline against a null GL layer under GFX_NULL, so the emulation is
    untouched while nothing is drawn and no driver is needed. The Visual Studio headless target defines
    GFX_OFFSCREEN instead and draws with a real OpenGL context on a window that is never shown, into
    a framebuffer of its own, so GFX_DUMP / GFX_EFB_DUMP / gxshot still produce frames; the Linux
    CMake HEADLESS target keeps the null backend.

  • scripts/VS2026/pureikyubu_headless.vcxproj is part of the solution but is not built by "Build
    Solution"; cmake -DHEADLESS=ON .. builds the same target on Linux. The null back ends
    (audionull.cpp, cuinull.cpp, padnull.cpp) were brought back to the current interfaces.


The DSP recompiler (issue #375)

  • src/dspjit.* compiles straight-line runs of DSP instruction words into x86-64. The semantics are
    not reimplemented: a word becomes one direct call (two for a parallel word) to the very handler the
    interpreter's Dispatch calls, so the decoder and the dispatch switch run once per block, at
    compile time. The emitter is shared with the Gekko recompiler (src/jit_x64.h).

  • Blocks re-check the halfwords they were built from on every entry and leave as soon as the code
    generation changes, an interrupt is pending or a breakpoint is armed; invalidation hooks cover
    HardReset, LoadIrom/LoadDrom and the DSP-DMA paths. Dsp16::DoDma copied a 16-bit block size
    blindly and overran the DSP memory arrays (AddressSanitizer found it); the transfer is clipped now.

  • The recompiler is 1.5–1.8x faster than the interpreter and off by default: --dspjit, or
    dspjit 1 in the debugger. testing/dsp_jit_test.cpp checks it differentially against the
    interpreter, including an exhaustive sweep of all 65536 instruction words (twice), the DSP-DMA
    microcode upload, an interrupt round trip and the block cache being dropped when the stream
    changes.


The UTF-8 pass (issue #372)

  • The narrow string of the project is UTF-8 — the JDI command line and its arguments, the Json
    documents, the reports and both front ends — while the emulator's own text stays wchar_t.
    Util::WstringToString and Util::StringToWstring are real converters now, including the
    surrogate pairs of the code points outside the BMP, and a byte sequence that is not valid UTF-8 is
    carried through instead of being dropped.

  • The tokenizer skips the byte order mark a script may start with; Json keeps member names as UTF-8
    (a non-ASCII key used to be truncated to one byte per character); Util::FileOpen is _wfopen_s
    on Windows and fopen of the UTF-8 name elsewhere, so a file name outside the ANSI code page
    travels through the interface in one piece.

  • The console reads and writes wide characters (ReadConsoleInputW / WriteConsoleOutputW on
    Win32, SDL_TEXTINPUT in the SDL build), and the command line moves the cursor, the deletion and
    the word search by code point. DebugUI2 decodes a panel's text and the command line into code
    points as well. The sources are UTF-8 without a BOM and the MSVC projects pass /utf-8.


Input verification and the security review (issue #381)

  • Every artifact the emulator reads from the outside world was audited — the settings JSON, the
    Bootrom and DSP ROM images, the .dol/.elf executables, the .iso/.gcm/.rvz disc images,
    the memory card saves, the command line, the guest's device registers and DMA engines, the console
    scripts and the .map symbol files — and about ninety defects were found and fixed. The recurring
    shape was a bound that existed only as assert(), which the Release configurations compile out.

  • The checks that repeat are one layer now (src/verify.h): the overflow-safe range test and the
    rules for a main-memory window, an image section, a disc read, an FST entry, a memory card transfer
    and a console script line. The memory interface gained length-aware accessors next to the
    start-only ones, so a block copy can no longer begin inside RAM and end outside it.

  • --selftest runs the whole startup sequence without a window and exits with the number of failed
    steps, and testing/startup_cases.sh runs it against deliberately broken settings files and random
    files renamed to .dol and .rvz. testing/security_test.cpp pins every rule down, including two
    property tests. The full report is in wiki/security.md and on the site (docs/security.html).


Tooling: the DolphinSDK demo sweep (issue #385)

  • DVD::MountSdk builds a whole GameCube disc image in memory from a DolphinSDK folder, so the
    SDK's demos can be read through an FST as if they were on a disc (File → Mount DolphinSDK as
    DVD…
    , or MountSDK <path> in autoexec.cmd).

  • testing/dolphinsdk holds the sweep methodology and its scripts: sweep.ps1 runs a list of demos
    and captures the video window, summarize.py turns a sweep folder into a table and report.py
    builds an HTML report with both GFX pipelines side by side. The 87 GX demos were run and the bugs
    they exposed were fixed.


Changed

Gekko and the command processor

  • The recompiler translates the halfword loads (lhz/lhzu/lhzx/lhzux and lha/lhau/lhax/
    lhaux) through a JitReadHalf trampoline. Falling back to the interpreter drops from 13.11% to
    3.89% of the instructions and the same 100-second Metroid Prime run goes from 289 to ~301 seconds
    of emulated time (103.1 to ~107 MIPS). Corrected on the way: the effective address of every indexed
    form is (RA|0), not RA.

  • A branch condition is emitted inline for the forms a compiler actually emits (a CR test or a CTR
    decrement), and a conditional branch no longer ends the block: the taken path leaves through the
    branch epilogue and the fall-through is compiled with the block, so a block covers the whole loop
    body. Blocks run per second more than halve and the same run reaches 131.8 MIPS and 11.64
    instructions per block (was 103.1 and 4.47).

  • Address-translation events no longer drop the block cache. A block bakes in the instruction stream
    it was compiled from, and the lookup already compares the physical address it was compiled for with
    the one the current MSR/BAT/SDR1/TLB state produces, so Exception, rfi, mtmsr and the
    mtspr of SDR1/the BATs/HID0 no longer call InvalidateAll. Metroid Prime's intro goes from
    131.8 to 181–191 MIPS, from 26.8M translations in 100 seconds to 17K, and from ~5.7 to ~9–11 frames
    a second; the run now reaches the title screen inside the 100 seconds.

  • The CP's read unit follows the emulated time base instead of guessing at the host scheduler's
    progress, and the WPAR timeout it never had on the real Gekko is gone. A FIFO repoint
    (GXSetGPFifo) keeps the entries the emulated reader has not fetched yet, which removes the
    intermittent boot failure of Metroid Prime's intro. The read unit reports its idle state from the
    ring's current state rather than as a side effect of the last fetch, which is what Super Monkey
    Ball 2 was spinning on. A draw command waits for all of its vertex data (the 16-bit vertex count
    was truncated to eight bits), the stream buffer is sized for the largest command a title can issue,
    and the new cpfifo command dumps the CP FIFO registers.


Timing units

  • The time base counts the Gekko clock (486 MHz), so a tick is one instruction and the decrementer
    steps by one; the old two-tick counter was a slow-down hack from the interpreter days.

  • The DSP runs at 81 MHz — one instruction per six time base ticks — so its anchor is a plain tick
    count and its batch is measured in instructions. Fixing the units is what removed the Star Wars
    Rogue Squadron III instruction-storage fault raised while its MMU handler was still mapping the
    page.


Build and layout

  • The GBA is a project of its own: a GBA static library the emulator links the way it links SDL2,
    plus the gba_bench console target. The portable debug interface (gba_debug.cpp) sits next to
    the SDL front end, which is the only file of the module that speaks JDI.

  • scripts/VS2026/pureikyubu_headless.vcxproj and the CMake HEADLESS option build the windowless
    target; src/jdispecs.cpp holds the JSON specifications of every JDI node in one place.


Fixed

Graphics

  • The EFB texture copy (issue #378). The copy engine's texture copy was missing entirely, so
    every title that renders through the EFB drew from textures that stayed zeroed. The rectangle is
    re-packed into the destination format, the tile rows follow the destination stride, and the copy
    destination's own 4-bit format set (and the alpha plane the single-channel formats need) is used.

  • A copy's clear belongs to the copy. A texture copy clears the rectangle it read; a display copy
    clears the whole colour buffer at the frame begin. A title that renders its frame in several passes
    through the copy engine (Metroid Prime) used to lose every pass drawn before a clear.

  • TEV texture bindings. A RAS1_TREF word binds a pair of TEV stages and its halves are consumed
    by the enabled stages in order, not by a fixed half per stage parity. This is what turned every
    string of Prince of Persia's memory card dialog into a white bar.

  • Indexed XF block loads. GXLoadPosMtxIndx and friends read guest (big-endian) words out of an
    index array; the bytes were handed to the XF as they sat in memory, so every matrix loaded that way
    was garbage. The cartoon-outline demo's whole model was invisible.

  • Shader pipeline depth. The GL backend's own frame-begin clear took its depth from
    PE_COPY_CLEAR_Z, which is still 0 on the first frame, so the whole first frame failed the depth
    compare and the indirect bump demos lost their light map. The GX clip-space z is now mapped onto
    GL's clip volume (z' = 2z + w) instead of being handed over raw, so the programmed viewport depth
    range matches GL and geometry on the near plane is not dropped.

  • Lighting and fog. The vertex shader negated the light direction in the spotlight attenuation,
    so every lit spot surface came out black (the software pipeline had the same bug). The perspective
    fog now looks up 2^24 / (b_mag - (z >> b_shf)), the reciprocal of a fraction of the depth range
    rather than 1 / b, so exponential and linear fog actually fade.

  • PE bounding box (issue #385). PE_XBOUND/PE_YBOUND and the CPU-visible read-backs were never
    extended from the drawn geometry; the box is extended once per primitive with the window extent of
    the vertices.

  • Video interface. The blitter assumed a fixed 640×480 picture instead of decoding
    VI_VERT_TIMING, so a title that copies 448 visible lines showed stale memory below them; the
    active lines are scaled over the window now, and interlaced modes carry half the height per field.
    The YUV→RGB conversion put red and blue in the wrong bytes of the output word (the RGB the VI
    produces is in (Blue, Green, Red) order, and the XFB chroma of a pixel pair is Y0 U0 Y1 V0).

  • The software pipeline's primitives. The line and point emitters built their quad by moving the
    window X/Y, which the clipper recomputes from the clip position, so lines and points could not be
    drawn at all; the offsets are formed in window space and carried into the clip position, and the
    point size is floored at one pixel. The clipper cut a hair in front of the near plane and threw
    away every primitive that lies exactly on it — the plane is inclusive on the hardware.

  • Other GFX fixes. The light-map render's scissor rectangle is handled; a draw-sync token is a
    synchronisation marker rather than a frame boundary; GXSetGPFifo is atomic with respect to the
    reader so a repoint cannot tear a command; the TEV register file keeps the 11-bit signed stage
    result (the wide alpha); the pixel engine's display copy clears the whole colour buffer while a
    texture copy clears its own rectangle.


CPU, DSP and interrupts

  • Cache management on direct-store segments (issue #300). dcbf, dcbst and dcbz took the
    DSI of a direct-store segment, which is for loads and stores, not for cache maintenance. Super
    Mario Sunshine hung in the SDK's __OSInitMemoryProtection inside DCFlushRange; with the fix it
    boots, plays its intro FMV and reaches the scene after it (~870 finished GX frames and 160M Gekko
    instructions in the first 45 emulated seconds, where the machine used to stop 0.3 s in).

  • The DSP wait state. wait left the program counter on the instruction, so an interrupt
    handler's reti returned to the same wait and the code after it never ran. A wait now ends when
    the interrupt it waited for arrives. Found with Super Mario Sunshine, whose audio microcode parked
    on a wait after answering the CPU: with the fix the phase after the intro runs at ~0.8x real time
    instead of ~0.1x.

  • The Linux thread port. Thread::Suspend() called from the worker itself deadlocked on the
    non-recursive pthread mutex (the AI thread parks itself exactly that way), DduCore's DVD-audio
    thread looped inside itself and could never be suspended, and the ringleader called
    pthread_yield() once per basic block. Super Mario Sunshine ran at 3.7 MIPS with it and 81.9 MIPS
    without; a thread that has to wait now parks on the condition variable or blocks in its device.

  • The DSP-DMA copy is clipped to the region it starts in, and the AX DirectSound backend runs
    without a sound device instead of asserting.


Games

  • Metroid Prime. The intro FMV runs about three times faster than in 1.8 and the run reaches the
    title screen. The remaining gap to full speed is the game's AX audio driver, which is a large
    share of the retired instructions.

  • Super Mario Sunshine. Boots, plays its intro FMV and reaches the scene after it. The title
    screen that follows is still not drawn; that hang is a separate finding.

  • Super Monkey Ball 2. Showed no picture at all because it waited for the CP's FIFO read-idle
    bit, which the emulator never set; it now reaches ~371 finished frames in 15 seconds.

  • Star Wars Rogue Squadron III. No longer faults on an instruction page while its MMU handler
    maps the page.

  • Soul Calibur II. Renders its scene through the shader pipeline.


Known issues

  • The integrated GBA is not complete: the JOY bus (a GameCube controller on the link port) and the
    Game Boy Player's own boot protocol are not implemented, the official BIOS runs its boot but its
    picture does not appear (the emulator's own boot ROM is what boots cartridges globally), and a real
    Game Boy Color cartridge's picture is still work in progress. The full list is in
    testing/gba_bench/Readme.md.

  • The software GFX pipeline is experimental: anisotropic filtering, the round/field_predict
    motion-compensation modes, the anti-aliased EFB, EFB pixel types other than RGB8, the display
    copy's vertical filter and the YUV/4:2:0 modes, and the EFB CPU window (Cpu2Efb) are not
    implemented; the default remains the shader backend.

  • Bump mapping on the shader backend, the Z-texture environment in some paths, PE dither and a few
    SU flag fields are still deliberately unimplemented and documented in wiki/gfx.md.

  • Full JAudio microcode support (issue #71) is still in progress; the remaining DSP divergences from
    the hardware are recorded in testing/Readme.md.

  • The DSP recompiler is experimental and off by default. The Gekko recompiler needs a 64-bit x86
    host; 32-bit and non-x86 builds fall back to the interpreter.

  • The Linux build still has no sound and no input, and the Linux headless target has no OpenGL
    offscreen backend (the Visual Studio one does).

  • RVZ support is read-only: bzip2/LZMA groups and Wii images are not implemented.

  • The CpSpec group of the native test suite hangs when two of its cases run in one process (each
    passes alone); the native suite is otherwise at 510 tests.

  • Some titles still fail to render or hang; compatibility is a work in progress.


Up next

Release 2.0 will be the next major version, the peripheral release: it is aimed at the emulation of the console's peripheral devices — the EXI bus and the devices on it (memory cards, the Broadband Adapter, the RTC), the serial interface and the pads, the disk interface and the drive, and the link port the GBA side already has — and it should close out many of the emulator's features, so that the list of "not implemented yet" above shrinks to what genuinely does not matter.

After that the project moves into its steady state: planned, methodical improvement - performance work and bug fixing release after release, rather than another feature push. The
architecture is in place; what remains is making what is there faster, more exact and more complete.



--1.8--

Version 1.8 is the performance release, and most of the speed came from removing work rather than adding it. Gekko gained an x86-64 recompiler that lifts the emulated CPU to over 150 MIPS, the periodic device threads stopped polling the time base in tight loops, and the interrupt lines of the DSP, the disk interface and the serial interface were re-derived from the hardware documentation.
The last part matters more than the numbers: with the interrupt storms gone, Zelda: The Wind Waker boots to its title screen and Animal Crossing finally leaves its exception handler. The release also adds compressed RVZ disc images, a full controller-settings dialog for the SDL build, and a new application icon.


Highlights

  • Gekko recompiler. A new x86-64 basic-block compiler (src/gekkojit.*) keeps the guest GPRs in
    host registers and shares the interpreter's decode cache for everything it does not translate.
    Measured on the differential harness: 39.7 MIPS for the original interpreter, 58.6 for the inlined
    interpreter and 155.9 with the recompiler; ALU-heavy code gains 8.6x. jit 0 / jit 1 in the
    debugger switches engines at run time.

  • Paired-Single in SSE. Every arithmetic PS instruction and both quantised loads and stores
    (psq_l / psq_st) are translated to SSE/SSE2. The PS-heavy workload goes from 59 MIPS
    (interpreter) to 435 MIPS, the quantised-load workload from 57 to 191.

  • No more busy-waiting. The VI scan-out and the serial poll now run on the CPU thread from the
    tick it already advances, and the CP, AI DMA and DSP threads block on events instead of spinning on
    Core->GetTicks(). pong.dol went from 1.47x to 3.66x real time, Ikaruga from 2.16x to 4.58x and
    Luigi's Mansion from 0.62x (stalled) to 4.26x.

  • The aggregate interrupt lines now work. The DSP and disk-interface Processor-Interface lines
    ANDed the cause bits with their own mask bits, which is always zero; both now follow the OR of the
    latched, unmasked causes. The serial interface polls on the video line schedule instead of a fixed
    tick, which removes an interrupt storm that pinned titles inside their handlers.

  • Games. Zelda: The Wind Waker boots to its title screen and Metroid Prime presents its title
    screen; Animal Crossing gets past the audio ARQ handshake, reads its disc and displays the Nintendo
    logo.

  • RVZ disc images. Compressed GameCube images in Dolphin's RVZ container (Zstandard) can be
    mounted read-only, the same way an ISO or GCM is.

  • SDL controller settings. The SDL build has a pad backend of its own and the full
    "Configure Controller N" dialog: keyboard and game controller bindings side by side, analog sticks
    and triggers, Clear / Default / OK / Cancel.

  • New icon. The cube icon was recoloured with the blue palette of the logo and regenerated as
    pureikyubu.ico (16–256 px, 32 bpp); the SDL builds embed the same pixels and call
    SDL_SetWindowIcon.


Added

Gekko

  • x86-64 recompiler for Gekko basic blocks (src/gekkojit.h, src/gekkojit.cpp). Every block
    records the translation state (generation counter) it was compiled under, so a write to a BAT,
    SDR1, HID0/HID2 or MSR, or an rfi, tlbie, icbi, dcbst or cache flush, drops all
    blocks in O(1). Untranslated instructions run through Interpreter::ExecuteDecoded and share the
    interpreter's decode cache, so both engines decode identically.

  • 8192-entry decode cache keyed on the pc and the fetched instruction word, and a direct-mapped TLB
    of 2048 page translations instead of an unordered_map that allocated a TLBEntry per page.

  • Paired-Single translation (src/gekkojit_ps.cpp, -DGEKKO_JIT_PS=0 builds it out): add, sub,
    mul, div, res, rsqrte, madd, msub, nmadd, nmsub, muls0/1, madds0/1, sum0/1,
    sel, mr, neg, abs, nabs, merge00/01/10/11 and the _d recording forms. The x86-64
    encoder, host register roles and GekkoRegs offsets moved to src/gekkojit_x64.h.

  • Quantised paired loads and stores (psq_l / psq_st): all eight indexed/non-indexed,
    update/non-update and W = 0/1 forms, with HID2[PSE], HID2[LSQE] and the RA != 0 update rule
    decided at compile time. The GQR is read at run time by Jit::PsqLoad / Jit::PsqStore, so
    writing one invalidates nothing.

  • Debugger command jit 0 / jit 1; 32-bit and non-x86 hosts, and -DGEKKO_JIT_DISABLED builds,
    fall back to the interpreter.


Disc images

  • Read-only support for Dolphin's RVZ container (src/rvz.cpp): the container header and its SHA-1
    checksums, the data-entry and group tables, the None and Zstandard compression methods and the
    "packed" representation that regenerates runs of pseudo-random disc junk from an LFG seed.
    bzip2/LZMA groups and Wii images are reported as unsupported. Zstandard is bundled under
    thirdparty/zstd.

  • .rvz is accepted everywhere a disc image was accepted: the command line, the game selector and
    the file dialogs.


Input and user interface

  • SDL pad backend (src/padsdl.cpp): a pad can be driven by the keyboard (SDL_GetKeyboardState,
    SDL scancodes in VKEY_FOR_*) and by an SDL game controller (GCKEY_FOR_* button and axis
    identifiers) at the same time; the Nth connected controller drives the Nth port. The left stick
    drives the main stick, the right stick the C stick and the triggers the analog L/R (digital L/R
    fire at 50%); the keyboard 50%/100% keys still work, and both sources are summed and clamped
    instead of overflowing int8_t.

  • The controller-settings dialog in the SDL UI (uisdl.cpp, ported from the Win32
    PADConfigDialogProc): Options → Controllers → Port 1..4 opens "Configure Controller N" with the
    Buttons table on the left and the Control Stick / C Stick tables on the right, each with its own
    Control | Keyboard | Gamepad columns. Clicking a binding arms capture, the next key, controller
    button or stick/trigger deflection becomes the binding and the event is swallowed; Esc cancels and
    modifier keys and F1–F12 are skipped for the keyboard.

  • Game controllers are opened and closed by the thread that pumps SDL events
    (PADUpdateControllers) and read by the emulation thread through a spin-locked snapshot;
    PADLoadConfig is public so the dialog can apply the bindings without restarting emulation.

  • New application icon: the cube artwork recoloured to the logo palette
    (src/res/pureikyubu_icon.svg) and embedded in src/res/pureikyubu_icon.h for SDL_SetWindowIcon
    (the SDL port has no .rc resources); pureikyubu.ico regenerated at 16–256 px, 32 bpp.


Command line and tooling

  • pureikyubu <file> (or --image <file>) loads and runs the image immediately instead of stopping
    in the game selector, and --help prints the accepted options to the console and to the report
    log, so EMU_LOG=<file> pureikyubu --help works too.

  • --bench <file> [seconds] runs the emulator unattended and reports throughput plus
    Gekko::CpuStats (translated vs interpreted instructions, basic-block length, re-translations and
    their causes, cache-line fills and every access that leaves the cache for the PI/MEM);
    BENCH_PROFILE=1 adds a host cycle breakdown and BENCH_STATS=1 a guest instruction histogram.

  • The Gekko differential harness gained BENCH_SPIN* modes (which reproduce and dissect the thread
    interference on the time base), BENCH_PS_TEST, BENCH_FUZZ_PS and the psq workload mix;
    build_win.bat builds the harness with MSVC so the two compilers can be compared on one workload.


Testing

  • Serial-interface spec tests (testing/): 17 new cases covering the SIPOLL round trip and its
    independent halves, per-channel output buffers, read-only input buffers, COMCSR bit positions and
    TSTART, the poll interval / budget / vertical-blank anchor, per-channel enables and the read-status
    interrupt with its mask.

  • Command Processor spec tests (testing/gfx_cp_spec_test.cpp, 19 cases) written from
    specs/architecture/command-processor.md: the VCD/VAT field positions, the VAT component and
    colour byte counts, the vertex attribute order and gx_vtxsize.

  • Five aggregation tests for the DSP interrupt line (each cause raises the line only through its own
    mask, a real ARAM DMA completion reaches the PI, a masked cause latches without a line, an
    acknowledgement drops it, and the mailbox interrupt reaches the PI), plus the corrected DSP vector
    test and the latched CPU→DSP request test. The suite is at 367 tests.


Changed

Performance and threading

  • The VI scan-out and the serial poll are executed by the CPU thread from the tick it already
    advances (GekkoCore::Tick / TickN call Flipper::Update), so VI timing is exact instead of
    sampled by a thread.

  • The Command Processor thread blocks on a new portable Event (utils.h) that the CPU thread
    signals when a batch of FIFO entries is due (CommandProcessor::TickSync); the drain stays at the
    emulated tickPerFifo rate.

  • The AI DMA thread is woken by DSP::AITickSync (about every 6750 ticks at 48 kHz) and parks itself
    with Suspend() when no DMA is armed. The DSP thread is woken by DspCore::TickSync once per
    DspWakeTicks and drains a batch, so the emulated DSP no longer self-throttles by re-anchoring its
    deadline on every poll; the mailbox hold-off is now a tick deadline
    (DspCore::MailboxHoldTicks).

  • mtmsr and rfi drop the compiled blocks only when MSR[IR]/[DR] actually change; the OS
    toggles MSR[EE] around every critical section and dropping the whole cache for it cost ~85,000
    needless re-translations per second.

  • Address translation, the tick and the branch-condition helpers are really inlined now
    (GEKKO_INLINE).


Behaviour and configuration

  • The Windows and SDL builds keep separate settings: DefaultSettingsWin.json / SettingsWin.json
    and DefaultSettingsSdl.json / SettingsSdl.json. The two ports run from the same directory and
    used to overwrite each other's configuration; each executable now embeds its own file names.

  • The serial interface follows the documented register layout: SICnOUTBUF is the CPU-visible
    register with the shadow read by the transfer engine, SICOMCSR latches CHANNEL and INLNGTH on an
    ordinary write and TSTART reads back as a pending bit, and the poll register drives SIPoll(line)
    directly.

  • The Flipper device threads and the emulator use the same Event abstraction, so the timing code is
    portable between Win32 and SDL.


Fixed

Interrupts and timers

  • DSP/DI aggregate interrupt lines. Every cause in CDCR and DI_SR has its own mask bit, and the
    pairs are not adjacent; both re-evaluation helpers ANDed the group of causes with the group of
    masks, so the lines were never asserted at all. Animal Crossing hung in its ARQ wait because the
    ARAM DMA completion never reached the CPU. Both lines now follow the OR of (cause && its own mask) and are re-evaluated from DSPUpdateInt() / DIUpdateInt() whenever a cause or a mask
    changes.

  • A cleared cause can drop the line. The lines were only ever dropped inside one write_cdcr /
    write_sr branch; clearing one cause while another was still latched left the line high with
    nothing left to clear it, so the CPU re-entered the external-interrupt handler on every rfi.
    Animal Crossing was executing ~11,500 exceptions a second.

  • Serial-interface poll storm. The emulator polled on a fixed 0x10000-tick interval (about ten
    times the hardware frame rate) instead of the SIPOLL video-line schedule, so RDSTINT was
    re-raised continuously. On Animal Crossing the emulated time for a fixed wall clock went from 3.8 s
    to 74 s, exception entries from 165,000 to 2,213 and VI interrupts from 0 to 2,178; Zelda's
    exception count dropped from 49,635 to 2,157.

  • DSP microcode vectors. The interrupt vector base was taken from the live CDCR use-rom bit
    instead of the program that is running; Zelda's microcode installs a CPU→DSP vector at 0x000E in
    IRAM and the loader's vectors made it bounce the command. CDCR bit 1 is now latched as a request
    and re-tried until the core accepts it through the TE3/ET gate, so a request that arrives while
    te3 is closed is no longer lost.

  • DSP/AI threads. DspAIOpen cleared its state with memset, which also wiped the Event the AI
    thread waits on (a wait on a zeroed handle returns immediately, so the thread silently went back to
    spinning); the clear is now an explicit DspAIControl::Reset.


Graphics

  • Presentation (issue #361). pe.cpp switched the VI output off after the first
    GXDrawDone/PE token, which stopped scan-out for the rest of the run in titles that keep
    presenting; the gate had no hardware meaning and is gone. In the SDL build the VI picture was
    written into the same window that carries the OpenGL context, so the two presenters alternated; the
    GL backend owns the window while it runs and the VI still decodes the XFB and counts frames.

  • Command Processor. CP_FIFO_COUNT was a stale software register and is now the live FIFO
    occupancy ("the distance between the write and the read pointer"). The break-point status bit was
    gated on CP_ENABLE[FIFOBRKINT] instead of [FIFOBRK], so a title that armed the break point
    without the interrupt never saw the flag. CommandProcessor::ResetFifoProcessor() drops the
    CP-side command buffer so a new stream starts clean.

  • VI. VI_DTV_STATUS (0xCC00206E) is implemented instead of falling through to the default case,
    where it returned 0 and made the SDK's VIGetTvFormat() always report NTSC.

  • Texture decoder (issue #359). IA4 walked the tiles column first while the tiles of an image are
    stored row by row, so a map wider than one tile came out transposed (and the report encoder mirrored
    the same walk, which is why the gallery never showed it); the debugger's gxtexdump read the colour
    fields after the decoder had serialised the texels into the R,G,B,A byte order GL wants, which
    rotated the channels of every non-grey format; C14X2 masked its palette index with 0x3ff instead of
    0x3fff; and the transparent fourth colour of the CMPR three-colour mode lost the RGB of the
    endpoint average.

  • Texture uploads. DecodeTexture compares the description and an FNV-1a hash of the raw texture
    bytes (plus a TLUT generation for the palette) and keeps the GL image when nothing changed; the
    upload reuses the image storage with glTexSubImage2D, the sampler parameters and the mip chain
    are only re-applied when TX_SETMODE0/1 really change, and TX_INVTAGS (GXInvalidateTexAll)
    marks every map for a decode again.


Interface and platform

  • File → Reopen, a dead menu entry, is wired to the last loaded image (the LASTFILE setting the
    selector already maintains) with F3 as its accelerator.

  • Writes to the SI input buffer registers no longer halt the emulation.

  • The Gekko sources are valid UTF-8 (two CP1252 comment bytes in gekko.cpp / gekkoc.cpp were
    normalised).

  • The DSP unit tests link a counter double (testing/dsp_test_support.cpp) now that the DSP sources
    are built without gekko.cpp, and the mailbox hold-off tolerates the null Core the tests use.


Known issues

  • Bump mapping, indirect texturing, the Z-texture environment and Cpu2Efb are not emulated yet.

  • PE dither and a few SU flag fields are deliberately unimplemented and documented in
    wiki/gfx.md.

  • Full JAudio microcode support (issue #71) is still in progress; the remaining DSP divergences from
    the hardware are recorded in testing/Readme.md.

  • RVZ support is read-only: bzip2/LZMA groups and Wii images are not implemented.

  • The recompiler needs a 64-bit x86 host; 32-bit and non-x86 builds, and GEKKO_JIT_DISABLED, fall
    back to the interpreter.

  • The Linux build still has no sound and no input.

  • Some titles still fail to render or hang; compatibility is a work in progress.



--1.7--

Version 1.7 is the largest update since the project started. The graphics backend was rebuilt around the two programmable stages of Flipper, the emulator finally has automated tests for its two most complex subsystems, the SDL build became a first-class front end with sound and a game selector, and a set of CPU and memory bugs was cleared that lets a Linux kernel boot on the emulated console.


Highlights

  • Shader-based graphics. The Flipper Transform Unit and Texture Environment Unit are emulated by
    GLSL 3.30 vertex and fragment shaders on the OpenGL 3.3 core profile; the fixed-function pipeline is
    gone. The Command Processor now feeds the XF instead of reaching into the graphics blocks.

  • The GFX pipeline is tested. 340 unit tests run the real CP → XF → SU → RAS → TX → TEV → PE
    pipeline against an OpenGL context and publish an HTML report with 262 rendered reference pictures.
    The suite found and fixed a large batch of long-standing rendering bugs.

  • The DSP core is tested against the hardware. 177 tests, 1822 golden ALU vectors and 435
    circular-buffer vectors; the DSP IROM is fully disassembled and analysed.

  • GC-Linux boots. Fixed GQR/paired-single decoding, the cache-coherent MMU hash-table walk, the
    decrementer model, mtcrf, lswi/lswx and the SI registers. A 2004 kernel reaches the gcnfb
    console with the Broadband Adapter driver loaded.

  • Games. The Metroid Prime intro movie plays and the game reaches its title screen; Zelda: The
    Wind Waker gets past its boot; Ikaruga's THP title movie has correct colours; PONG runs again.

  • SDL front end. Sound, an integrated file browser, message boxes and the full game selector
    (banners, Game IDs, sorting, filters, jump-to-letter) are available in the cross-platform build.


Added

Graphics

  • GLSL 3.30 vertex shader emulating the XF: geometry and texture matrix multiplies, the projection
    combine, per-vertex lighting for both colour channels and all texture coordinate generation modes.

  • GLSL 3.30 fragment shader emulating the TEV: up to 16 combine stages, the full
    operand/bias/sub/clamp/shift datapath, Rev-B K constants, fog and the final alpha function. Both
    shaders are static and receive the register state as uniforms.

  • Independent decoding and binding of all eight texture maps, mip-level generation and the corrected
    sampler wrap/filter modes.

  • Full XF register read-back over CP_XF_ADDR / CP_XF_DATAL / CP_XF_DATAH, and the
    CP → XF → SU → RAS command path.

  • GFX_DUMP dumps every n-th rendered frame to a BMP; EMU_LOG=<file> writes Debug::Report output
    to a file.


Audio

  • SDL audio backend (audiosdl.cpp) for the cross-platform build.

  • ARAM and AI behaviour brought in line with the hardware documentation (ACRS/ACWE register names,
    the 32-byte block transfer, address masks, CDCR, the AIVR streaming volume, the ARAM DMA performed
    to completion inside the AMBL write).


CPU and memory

  • GQR and paired-single extensions decoded with the ISA-manual bit numbering, with the conversion math
    factored into src/gqr.h.

  • Cache-coherent MMU hash-table access (ReadHashPte / WriteHashPte).

  • Hardware-accurate decrementer: one exception per underflow, the request latched across MSR[EE], a
    positive value clearing a pending request, a negative value at reset.

  • Command-line options --ipl (boot the IPL menu without a disc) and --no-disc (start with the DVD
    lid open).

  • Typed register views for every Flipper block; the Command Processor is a standalone Flipper entity;
    memory reset (MemRst).


User interface and debugger

  • Game selector in the SDL build: DOL/ELF/ISO discovery from PATH, banner decoding (RGB5A3 with
    alpha, SJIS titles), Game IDs, the Icon/Title/Size/Game ID/Comment columns, all Win32 sort rules,
    small icons, file filters and Add Directory.

  • Jump-to-letter navigation in the selector (issue #92).

  • Independent video-output window with filtered events so that a covered selector ignores clicks meant
    for the game.

  • JDI (JSON Debugger Interface) server, its UI view and the Flipper instance passed through the debug
    tree.

  • ImGui file browser and message boxes in the SDL UI.


Testing and tooling

  • GFX unit-test rig with a real OpenGL context, transform-feedback probing and an HTML report
    (gfx_report.html) with rendered pictures; 340 tests.

  • DSP unit tests: instruction coverage, an independent flag reference model, 1822 golden ALU vectors,
    435 circular-buffer vectors, the mailbox protocol and the IROM boot path; 177 tests.

  • GQR tests (17 cases) and the published IROM disassembly in testing/DspIrom.md.

  • Visual Studio 2026 projects (MSVC v145); the test project references the emulator sources instead of
    copying them.

  • JSON tables embedded into the source, so no external data files are required (PR #332).


Changed

  • The graphics backend no longer uses the fixed-function OpenGL pipeline; the immediate-mode draw
    paths were removed and quads are expanded into triangles.

  • Vertices are accumulated into a VBO and drawn with glDrawArrays / glDrawElements.

  • The CP pushes command words into the XF and polls it for readiness instead of driving the rasterizers
    itself; register loads are word-wise, so a partial load no longer halts.

  • The Flipper register access was refactored so every block owns a typed register view; the PI FIFO
    moved into pi.cpp; 8-bit hardware access was removed.

  • VI_DISP_POS is read-only; the PI CONFIG register reflects the memory and DVD reset state
    correctly.

  • XF matrix and colour registers start from the state GXInit establishes (verified against a real
    bootrom run).

  • A whole-frame display copy is a frame boundary of its own; partial copies and texture copies are
    not.

  • The imgstore folder moved into wiki; the wiki was extended and reorganised.

  • The project adopted Karpathy-style behavioural guidelines in .clinerules.


Fixed

Graphics

  • Black bootrom screen: the copy-engine clear erased the frame that was still to be displayed.

  • Metroid Prime: the intro FMV stayed black because movie frames were never presented (the display
    copy is now a frame boundary); whole frames stayed black because the frame-begin clear used the live
    PE_COPY_CLEAR_* registers (now captured when the copy command is issued) and because
    GX_IDENTITY / GX_DTTIDENTITY were never initialised.

  • Ikaruga THP movie: garbled colours from the wrong GQR field numbering, the shared
    K-constant/colour register storage and the swapped channel/register fields of the single-channel
    K selectors.

  • CMPR decoding (uninitialised colour table, endpoint alpha, the transparent fourth colour of the
    3-colour mode).

  • Texture maps 4–7 could never be programmed (the I4–I7 register block was decoded as I0–I3), and
    multi-texture applied each map to the previous texture unit.

  • The SU scissor was compiled out; TexMode0.lodbias, TexMode1.minlod/maxlod, SU_LPSIZE,
    SU_SSIZE/SU_TSIZE, RAS1_SS0/SS1 and GEN_MODE.flat_en are now applied; TEV fog F-select 1 and 3
    gained their law; the PE colour/alpha update bits are applied as documented.

  • R and B are swapped correctly when dumping a frame to BMP.

  • A draw command is no longer stalled by a partial register load.


CPU

  • mtcrf used the RS field as the CR mask and the CRM field as the register number (out-of-bounds
    gpr[128] access), which crashed the Linux kernel in ip_auto_config_setup.

  • The MMU hash-table walk was incoherent with the write-back data cache, so freshly created PTEs were
    invisible and the kernel looped on the same DSI fault.

  • The decrementer request was edge-triggered and lost with interrupts disabled; a fully
    level-sensitive variant livelocked the Bootrom. It is now a latched per-underflow request.

  • lswi / lswx dropped the last accumulated word when the byte count was a multiple of four,
    corrupting GCC struct copies.

  • Every non-zero GQR was decoded with the wrong bit numbering; the paired-single conversion rules and
    the HID2 gating were corrected.


Audio / DSP

  • The sliced ARAM DMA dropped Metroid Prime's audio DMA requests and hung the game right after the
    intro movie; the block is now performed to completion inside the AMBL low-word write.

  • div lost the low quotient bit; norm and div were stubs.

  • lsf / asf shifted in the wrong direction in every register form.

  • ModifyFlags did not mask to 40 bits; neg p, unsigned multiplies, the logic-family flags,
    accumulator/immediate rules, clr, neg/negc, addp, lsl16, tst p and the
    multiply/accumulate family were corrected.

  • trap did not advance the PC (reti re-trapped forever) and the interrupt vectors ignored the
    program base.

  • Circular addressing wrapped at the modifier length instead of the 2^n aligned block; the decoder
    accepted reserved words; the mailbox could tear a message pair; packed memory accesses latched the
    wrong operand.


Interface and platform

  • Writes to the SI input buffer registers no longer halt the emulation (the Linux gcn-si driver
    resets them during initialisation).

  • The DOL/ELF loader was fixed; PONG runs again.

  • The PI FIFO wrap bit, the pad stick, the DVD banner loading on Linux and the Linux build were
    fixed.

  • Project files migrated to Visual Studio 2026; warnings cleaned up; a conflicting copy of fmt
    removed.


Known issues

  • Bump mapping, indirect texturing, the Z-texture environment and Cpu2Efb are not emulated yet.

  • PE dither and a few SU flag fields are deliberately unimplemented and documented in
    wiki/gfx.md.

  • Full JAudio microcode support (issue #71) is still in progress; the remaining DSP divergences from
    the hardware are recorded in testing/Readme.md.

  • The Linux build has no sound and no input yet.

  • Some titles still fail to render or hang; compatibility is a work in progress.



--1.6--

Fixed major cache crashes, resulting in more games launching.

  • Fixed DSI/ISI crashes

  • Disabled mailbox DSP delay

  • Fixed mtsr instruction decoding

  • PE Regs



--1.5--

Fixed CPU-DSP interrupt handling causing some games to load

Also some work has been done to improve the GFX engine (experimental shader support, disabled for now).

  • Fixed debugger script command

  • Wiki main.md

  • DMEM/IMEM moved to DspCore as it should

  • gfx refactoring 

  • DI log commands 

  • Fixed CPU->DSP interrupt



--1.4--

All Gekko caches are enabled by default, so there may be issues in games that used to work. To be fixed.

  • Fixed step-over in Gekko debug (F10)

  • Fixed locked cache

  • User-friendly Debug::Hal

  • DSP debugger is intergrated into common system-wide debugger

  • Added imgui+SDL2 as submodules

  • Cui + imgui

  • Simplified uisimple.cpp

  • The debug console is ported for Linux using imgui (used by uisimple.cpp by default)



--1.3--

  • TEV regs

  • Simplified ras.cpp

  • Fixed Luigi's model spikes

  • Cache hack

  • Fixed errordemo

  • PI unmapped access debug messages + DecrementerStep

  • Fixed Gekko reset + GEKKOCORE_CACHE_DISABLE_HACK macro



--1.2--

The emulator goes through a complete boot cycle from the reset vector (BS1), IPL (BS2) and loading the game from the DVD (Apploader) without applying HLE.

  • Fixed short ARAM DMA issue

  • BS2 PAL Revision boot

  • wiki stub

  • UI cleanup

  • Next VI / Next PE debug commands

  • Gekko cache support

  • Fixed Linux build



--1.1--

  • Removed old GX code (integrated in core)

  • DSP small refactoring and notes

  • Portable debugui.cpp

  • GFX refactoring

  • Fifo fixes after refactoring



--1.0--

We've done a little rebranding. Dolwin is now called pureikyubu (PlayCube in Japanese).

By this event the code base was cleaned up and to somehow get out of the black hole of 0.xxx versions I made version 1.0 (About dialog updated).


Show Previous Changes


--0.1545--

Fixed a nasty bug with restarting.



--0.15--

The release work was evenly spread over previous releases, but the bulk of the new DSP core was implemented in this version.

For example, Luigi's Mansion now comes to the forest scene.


PS. Also keep in mind that there is a nasty bug exists - after starting the game, you need to restart the emulator every time --(Dolwin.exe)


--0.14--

This release is the beginning of work on improving the graphics system.


A new component has been added to the emulator - GX, which contains the general code for emulating the command processor (CP)

Old DolwinVideo code has been cleaned up from old stuff. It is still used as a reserve parachute until a more advanced backend appears.


In parallel, improvements have been made in DSP emulation, but there is still a lot of work to be done in this area

Fixed minor bugs that got out after splitting the emulator core and user interface.


Some games show slight progress: Luigi's Mansion now shows 1 screen further from the main menu, and Wario Ware even reaches the game, but with big slowdowns and graphical bugs. You can even hear how Wario is unhappy with this circumstance (there is sound in the game).


--0.132--

This is the first attempt at porting Dolwin to Linux-like operating systems.


Let me remind you that Playground is a "smaller" version of Dolwin, without a graphical interface and with Null backends (that is, no graphics, sound, etc.). All that is visible to the user is the emulator debug messages.


Porting was done in WSL2 environment on Windows, Ubuntu version 18.04.


--0.131--

Intermediate release related to source code refactoring. It was decided to make the emulator cross-platform.


Now the whole core is in a separate executable module (for Windows it is DolwinEmu.dll), and the user interface and debugger in the main executable file.


From the user side no special improvements are noticeable, games still show only splash screens at best :-)


There may be various bugs in the interface after refactoring, they will be fixed over time.


--0.13--

The release with unlucky number 13 was split into two releases: 0.130 and 0.131. These superstitious programmers..

  • MMU support

  • Support cache emulation

  • Dynamic recompiler (JITC)

  • Improved emulation of graphics FIFO

  • Many other minor improvements


All these things were added experimentally and at the moment the cache and recompiler are temporarily disabled. If you are a developer, you can rebuild Dolwin with the cache and recompiler turned on.


The cache is enabled with the command CacheDebugDisable 0.


The recompiler is turned on in SRC\Core\Gekko.cpp, line 20 (but the interpreter must be disabled).


Between 0.130 and 0.131, I will try to fix all incomprehensible bugs with cache and recompiler so that they are included in the next release.

bottom of page