Skip to content

add mapper 199 support#466

Open
tchaikov wants to merge 4 commits into
lukexor:mainfrom
tchaikov:mapper-199
Open

add mapper 199 support#466
tchaikov wants to merge 4 commits into
lukexor:mainfrom
tchaikov:mapper-199

Conversation

@tchaikov
Copy link
Copy Markdown
Contributor

@tchaikov tchaikov commented May 4, 2026

I ran into a ROM that tetanes couldn't run correctly: "San Guo Zhi 2 - Ba Wang de Da Lu" which is a Chinese translation of Namco's Sangokushi II. Its iNES header claims mapper 074, but the actual board is Waixing TypeG = mapper 199, and the CRC matches what Mesen2 / FCEUX / Nestopia all attribute to 199. Hence this patchset.

Tested

  • San Guo Zhi 2 (Chinese enhanced edition) : runs just fine.
  • sangoku2.nes (Japanese mapper-4 original) : unaffected; sanity-checked manually.

tchaikov added 3 commits May 4, 2026 23:50
Match real 6502 behavior: a JAM/KIL opcode keeps the CPU cycling at
the same address rather than marking the CPU as corrupted. IRQ/NMI
are suppressed while jammed. When the JAM address falls in PPU
register space ($2000-$3FFF), each fetch auto-increments PPUDATA,
so the CPU eventually reads a non-JAM byte and resumes -- useful
for ROMs that trip a JAM as a synchronization tool.

Cross-verified against Mesen2 (NesCpu::HLT, NesCpu.cpp:571), FCEUX
(KIL handler in ops.inc:402), and Nestopia (Cpu::Jam, NstCpu.cpp:1819):
all three rewind PC by 1 and suppress NMI/IRQ via a jammed flag.
tetanes' implementation matches the same pattern.

Signed-off-by: Kefu Chai <[email protected]>
Adds support for the Waixing Type G board (mapper 199), an MMC3 derivative
used by several Chinese-translation hacks. Adds:

  - 4-bit bank-select field ($8000 ctrl0 & 0x0F selects 12 registers vs
    standard MMC3's 3-bit/8 registers).
  - Two extra PRG slots: ctrl0=8/9 -> $C000/$E000 8KB banks (TypeG runs
    all four PRG slots through exPrg, MMC3's fixed slots are gone).
  - Two extra CHR slots: ctrl0=A/B -> 1KB banks at the half-tile-row
    boundaries inside $0000-$0FFF.
  - 8KB extra VRAM: CHR bank values < 8 redirect to this RAM region for
    runtime tile uploads (used here for Chinese font rendering); values
    >= 8 hit CHR-ROM as usual.
  - 4KB WRAM at $5000-$5FFF: TypeI inheritance per Nestopia's class
    hierarchy, used by games for state variables.
  - 4-mode mirroring via $A000 & 0x03: Vertical/Horizontal/SingleScreenA/
    SingleScreenB (vs standard MMC3's 2 modes on bit 0). Sangokushi II
    Chinese hack (CRC 44C20420) writes mode 3 (SingleScreenB) at the
    screen-split to map all four logical NTs onto the same physical
    page for its bottom-panel UI.

Mapper 199 is dispatched through the existing Txrom path in cart.rs.
A CRC override in generate_db.rs (and the regenerated game_db.dat)
forces 0x44C20420 onto mapper 199 -- the iNES header on that ROM says
mapper 074, but Nestopia/FCEUX/Mesen2 game databases all classify it
as 199 (a different physical board than the canonical FS306 Sangokushi
II Chinese on NES 2.0 mapper 544; this dump is the "中文加强版" hack).

** BREAKING CHANGE **: this introduces new fields on the Txrom struct
(bank_values_ext, ext_vram, wram_5000) that serialize natively rather
than being #[serde(skip)]. The bincode payload layout for ALL
Txrom-family mappers (4, 76, 88, 95, 154, 199, 206) therefore changes.
SAVE_VERSION is bumped from "1" to "2" so existing save states for
those games (SMB3, Mega Man 3, Crystalis, etc.) fail to load with a
graceful "invalid version" error rather than corrupting state. Users
will need to re-save.

Note: SAVE_VERSION is shared by save states (.sav), battery saves
(.sram), AND the embedded game_db.dat. The bumped version also
invalidates the in-tree game_db.dat header even though its bincode
payload (Vec<GameInfo>) is structurally unchanged -- without
regenerating it, Cart::lookup_info silently falls back to "no DB"
and CRC overrides (e.g. 0x44C20420 -> 199) stop applying. game_db.dat
is therefore re-wrapped with the v2 header in this same commit so
the binary stays self-consistent.

We chose this trade-off deliberately. The alternative was
#[serde(skip)] annotations on the new fields + a companion .ext_vram
sidecar file + a fixup_after_serde() reconstruction step in Cpu::load
-- ~120 lines spanning three files to preserve compatibility with what
is, in practice, an emulator-internal binary format. Code simplicity
over backward compatibility.

Cross-verified against Mesen2 (Mappers/Mmc3Variants/MMC3_199.h), FCEUX
(boards/199.cpp), and Nestopia (Waixing::TypeG in NstBoardWaixing.cpp):
all three agree on register routing, the bank<8 -> CHR-RAM (8KB) split,
and the 4-mode mirroring scheme via $A000 & 0x03.

Signed-off-by: Kefu Chai <[email protected]>
Initialize CHR slots 1 and 3 (driven by exReg[2] / exReg[3], stored
in bank_values_ext[2] / bank_values_ext[3]) to bank values 1 and 3
respectively at construction time. Without this, the slots default
to bank 0 -- so any CHR read between Txrom::new() and the game's
$8001 init writes would render slots 1 and 3 from CHR-RAM bank 0
instead of banks 1 and 3.

In practice every observed mapper-199 ROM (Sangokushi II, Dragon
Ball Z 2 / Gaiden, etc.) overwrites these registers immediately
during boot via ctrl0=A and ctrl0=B writes, so this is defensive
alignment, not a gameplay fix. If post-fix testing shows behavior
is unchanged, this commit can be squashed into the mapper 199
feature commit.

Cross-verified against the references:
  * Mesen2 (Mmc3Variants/MMC3_199.h InitMapper):
      _exRegs[2] = 1; _exRegs[3] = 3;
  * FCEUX (boards/199.cpp M199Power):
      EXPREGS[2] = 1; EXPREGS[3] = 3;

Signed-off-by: Kefu Chai <[email protected]>
appease the warning from

```
$ cargo clippy --locked --lib --bin tetanes --target \
 wasm32-unknown-unknown --all-features --keep-going -- -D warnings
...
error: redundant reference in `format_args!` argument
   --> tetanes-core/src/cart.rs:900:57
    |
900 |             .field("mapper_num", &format_args!("{:03}", &self.mapper_num))
    |                                                         ^^^^^^^^^^^^^^^^ help: remove the redundant `&`: `self.mapper_num`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#useless_borrows_in_formatting
    = note: `-D clippy::useless-borrows-in-formatting` implied by `-D warnings`
    = help: to override `-D warnings` add `#[allow(clippy::useless_borrows_in_formatting)]`
```

Signed-off-by: Kefu Chai <[email protected]>
@lukexor
Copy link
Copy Markdown
Owner

lukexor commented May 6, 2026

Thanks for finding and putting up a fix for this! I have some feedback but haven't had the time yet to fully review so hopefully later this week

@tchaikov
Copy link
Copy Markdown
Contributor Author

@lukexor hi Luke, thanks in advance. i extracted the JAM/KIL opcode support into #469, in hope that it can be reviewed / merged sooner. as its implementation has been cross verified by referencing three other NES emulator implementations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants