Skip to content

Commit 0a9d4e4

Browse files
trekawekclaude
andcommitted
Support the GBTK flash cart's wide MBC1 bank register
GBTK video ROMs carry an MBC1 header but the flash cart wires a wide bank register: the firmware streams banks 0x20, 0x21, ... straight into 0x2000-0x3FFF and never touches the upper-bits register, so a real MBC1 (and SameBoy) wraps after bank 0x1F and the video restarts mid-stream in garbage. Detected adaptively: a >512 KB MBC1 ROM writing bank bits 5-6 to the low register before ever using the upper register or mode 1 switches the register to 7 bits; any upper-register use reverts to the hardware-exact masking (mooneye's rom_8Mb/rom_16Mb stay green). The Armageddon trailer now plays through to the title card. Fixes #69 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J1wLWscyUGS7CFwc3CjJR8
1 parent 48381b6 commit 0a9d4e4

1 file changed

Lines changed: 35 additions & 6 deletions

File tree

  • core/src/main/java/eu/rekawek/coffeegb/core/memory/cart/type

core/src/main/java/eu/rekawek/coffeegb/core/memory/cart/type/Mbc1.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ public class Mbc1 implements MemoryController {
4141

4242
private boolean ramUpdated;
4343

44+
// GBTK flash carts carry an MBC1 header but wire a wide bank register: their
45+
// firmware streams banks 0x20, 0x21, ... straight into 0x2000-0x3FFF and never
46+
// touches the upper-bits register (issue #69, Armageddon video). A real MBC1 would
47+
// mask those writes to 5 bits and wrap. Detected at run time: a >512 KB ROM writing
48+
// bank bits 5-6 to the low register before ever using the upper register or mode 1.
49+
private boolean wideBank;
50+
51+
private boolean upperRegisterUsed;
52+
4453
public Mbc1(Rom rom, Battery battery) {
4554
this.cartridge = rom.getRom();
4655
this.multicart = rom.getRomBanks() == 64 && isMulticart(this.cartridge);
@@ -63,19 +72,32 @@ public void setByte(int address, int value) {
6372
ramWriteEnabled = (value & 0b1111) == 0b1010;
6473
LOG.trace("RAM write: {}", ramWriteEnabled);
6574
} else if (address >= 0x2000 && address < 0x4000) {
66-
LOG.trace("Low 5 bits of ROM bank: {}", (value & 0b00011111));
67-
int bank = selectedRomBank & 0b01100000;
68-
bank = bank | (value & 0b00011111);
69-
selectRomBank(bank);
75+
if (!wideBank && !multicart && !upperRegisterUsed && memoryModel == 0
76+
&& romBanks > 32 && (value & 0b01100000) != 0) {
77+
wideBank = true;
78+
LOG.info("GBTK-style wide bank register detected");
79+
}
80+
if (wideBank) {
81+
selectRomBank(value & 0x7f);
82+
} else {
83+
LOG.trace("Low 5 bits of ROM bank: {}", (value & 0b00011111));
84+
int bank = selectedRomBank & 0b01100000;
85+
bank = bank | (value & 0b00011111);
86+
selectRomBank(bank);
87+
}
7088
cachedRomBankFor0x0000 = cachedRomBankFor0x4000 = -1;
7189
} else if (address >= 0x4000 && address < 0x6000 && memoryModel == 0) {
7290
LOG.trace("High 2 bits of ROM bank: {}", ((value & 0b11) << 5));
91+
upperRegisterUsed = true;
92+
wideBank = false;
7393
int bank = selectedRomBank & 0b00011111;
7494
bank = bank | ((value & 0b11) << 5);
7595
selectRomBank(bank);
7696
cachedRomBankFor0x0000 = cachedRomBankFor0x4000 = -1;
7797
} else if (address >= 0x4000 && address < 0x6000 && memoryModel == 1) {
7898
LOG.trace("RAM bank: {}", (value & 0b11));
99+
upperRegisterUsed = true;
100+
wideBank = false;
79101
selectedRamBank = value & 0b11;
80102
cachedRomBankFor0x0000 = cachedRomBankFor0x4000 = -1;
81103
} else if (address >= 0x6000 && address < 0x8000) {
@@ -145,6 +167,10 @@ private int getRomBankFor0x0000() {
145167
private int getRomBankFor0x4000() {
146168
if (cachedRomBankFor0x4000 == -1) {
147169
int bank = selectedRomBank;
170+
if (wideBank) {
171+
cachedRomBankFor0x4000 = Math.max(1, bank) % romBanks;
172+
return cachedRomBankFor0x4000;
173+
}
148174
if (bank % 0x20 == 0) {
149175
bank++;
150176
}
@@ -197,7 +223,7 @@ private static boolean isMulticart(int[] rom) {
197223

198224
@Override
199225
public Memento<MemoryController> saveToMemento() {
200-
return new Mbc1Memento(battery.saveToMemento(), ram.clone(), selectedRamBank, selectedRomBank, memoryModel, ramWriteEnabled, cachedRomBankFor0x0000, cachedRomBankFor0x4000, ramUpdated);
226+
return new Mbc1Memento(battery.saveToMemento(), ram.clone(), selectedRamBank, selectedRomBank, memoryModel, ramWriteEnabled, cachedRomBankFor0x0000, cachedRomBankFor0x4000, ramUpdated, wideBank, upperRegisterUsed);
201227
}
202228

203229
@Override
@@ -217,10 +243,13 @@ public void restoreFromMemento(Memento<MemoryController> memento) {
217243
this.cachedRomBankFor0x0000 = mem.cachedRomBankFor0x0000;
218244
this.cachedRomBankFor0x4000 = mem.cachedRomBankFor0x4000;
219245
this.ramUpdated = mem.ramUpdated;
246+
this.wideBank = mem.wideBank;
247+
this.upperRegisterUsed = mem.upperRegisterUsed;
220248
}
221249

222250
private record Mbc1Memento(Memento<Battery> batteryMemento, int[] ram, int selectedRamBank, int selectedRomBank,
223251
int memoryModel, boolean ramWriteEnabled, int cachedRomBankFor0x0000,
224-
int cachedRomBankFor0x4000, boolean ramUpdated) implements Memento<MemoryController> {
252+
int cachedRomBankFor0x4000, boolean ramUpdated, boolean wideBank,
253+
boolean upperRegisterUsed) implements Memento<MemoryController> {
225254
}
226255
}

0 commit comments

Comments
 (0)