From e43ff83544cee43c2498eb4ae54832a7ca9b8f37 Mon Sep 17 00:00:00 2001 From: QuartzMovement <33850146+QuartzMovement@users.noreply.github.com> Date: Fri, 28 Aug 2026 09:42:00 -0400 Subject: [PATCH] Add SGB button remap --- firmware/handheld/src/bitstream/gameboy/mod.rs | 5 +++++ firmware/handheld/src/bitstream/gba/mod.rs | 5 +++++ firmware/handheld/src/kvs/keys.rs | 8 ++++++++ firmware/handheld/src/ui/state/settings.rs | 10 ++++++++++ .../main/scala/platform/handheld/HandheldGameboy.scala | 7 +++++-- .../src/main/scala/platform/handheld/HandheldGba.scala | 7 +++++-- 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/firmware/handheld/src/bitstream/gameboy/mod.rs b/firmware/handheld/src/bitstream/gameboy/mod.rs index da337edb..8a3a789c 100644 --- a/firmware/handheld/src/bitstream/gameboy/mod.rs +++ b/firmware/handheld/src/bitstream/gameboy/mod.rs @@ -36,6 +36,7 @@ const REG_RTC_LATCHED: u32 = 0xE000_001C; const REG_IMU_ACCEL_X: u32 = 0xE000_0020; const REG_IMU_ACCEL_Y: u32 = 0xE000_0024; const REG_DMG_PALETTE_OFF: u32 = 0xE000_0030; +const REG_SGB_BUTTONS: u32 = 0xE000_0034; const REG_STAT_STALLS: u32 = 0xE000_1000; const REG_STAT_CYCLES: u32 = 0xE000_1004; const BIOS_ADDRESS_BASE: u32 = 0xE010_0000; @@ -143,6 +144,10 @@ impl Gameboy { let is_dmg = kvs::keys::GB_IS_DMG.get().unwrap(); let config = 0 | (((!is_dmg) as u32) << 0); device.fpga.write_u32(REG_EMU_CONFIG, config)?; + device.fpga.write_u32( + REG_SGB_BUTTONS, + kvs::keys::GB_SGB_BUTTONS.get().unwrap() as u32, + )?; device.imu.disable_accel().unwrap(); diff --git a/firmware/handheld/src/bitstream/gba/mod.rs b/firmware/handheld/src/bitstream/gba/mod.rs index 4e7cf609..3af9be5b 100644 --- a/firmware/handheld/src/bitstream/gba/mod.rs +++ b/firmware/handheld/src/bitstream/gba/mod.rs @@ -32,6 +32,7 @@ const PROGRESS_UPDATE_INTERVAL: Duration = Duration::from_millis(250); const REG_EMU_CART_CONFIG: u32 = 0xE000_0000; const REG_EMU_CART_ROM_SIZE: u32 = 0xE000_0004; const REG_GB_PLAYER: u32 = 0xE000_0008; +const REG_SGB_BUTTONS: u32 = 0xE000_000C; const REG_IMU_GYRO_Z: u32 = 0xE000_0100; const REG_IMU_ACCEL_X: u32 = 0xE000_0104; const REG_IMU_ACCEL_Y: u32 = 0xE000_0108; @@ -223,6 +224,10 @@ impl Gba { REG_GB_PLAYER, kvs::keys::GBA_ENABLE_GBP.get().unwrap() as u32, )?; + device.fpga.write_u32( + REG_SGB_BUTTONS, + kvs::keys::GBA_SGB_BUTTONS.get().unwrap() as u32, + )?; // Disable Vblank IRQ device.fpga.disable_interrupt(fpga::Irq::ModuleVblank)?; diff --git a/firmware/handheld/src/kvs/keys.rs b/firmware/handheld/src/kvs/keys.rs index d2be6496..0f57861b 100644 --- a/firmware/handheld/src/kvs/keys.rs +++ b/firmware/handheld/src/kvs/keys.rs @@ -41,6 +41,12 @@ pub static GBA_COLOR_PROFILE: KvsKey = KvsKey::new_with_default("gba-colors /// Whether to enable Game Boy Player functionality pub static GBA_ENABLE_GBP: KvsKey = KvsKey::new_with_default("gba-enable-gbp", true); +// Whether to use the SGB button layout for GBA +pub static GBA_SGB_BUTTONS: KvsKey = KvsKey::new_with_default("gba-sgb-buttons", false); + +// Whether to use the SGB button layout for GB/GBC +pub static GB_SGB_BUTTONS: KvsKey = KvsKey::new_with_default("gb-sgb-buttons", false); + /// Startup action. pub static STARTUP_ACTION: KvsKey = KvsKey::new_with_default("startup-action", 0); @@ -61,6 +67,8 @@ pub fn flush_all() { GBA_SKIP_BOOT_ANIM.flush(); GBA_COLOR_PROFILE.flush(); GBA_ENABLE_GBP.flush(); + GBA_SGB_BUTTONS.flush(); + GB_SGB_BUTTONS.flush(); STARTUP_ACTION.flush(); LAST_FIRMWARE_VERSION.flush(); } diff --git a/firmware/handheld/src/ui/state/settings.rs b/firmware/handheld/src/ui/state/settings.rs index c11778c5..c186966e 100644 --- a/firmware/handheld/src/ui/state/settings.rs +++ b/firmware/handheld/src/ui/state/settings.rs @@ -100,6 +100,11 @@ mod settings { key: &keys::DMG_COLOR_PALETTE, choices: &["Grayscale", "DMG Green", "GB Pocket"], }, + // Super Game Boy button layout for GB/GBC + Entry::Checkbox { + name: "SGB Button Layout", + key: &keys::GB_SGB_BUTTONS, + }, ], }; @@ -119,6 +124,11 @@ mod settings { name: "Enable Game Boy Player", key: &keys::GBA_ENABLE_GBP, }, + // Super Game Boy button layout for GBA + Entry::Checkbox { + name: "SGB Button Layout", + key: &keys::GBA_SGB_BUTTONS, + }, ], }; } diff --git a/fpga/src/main/scala/platform/handheld/HandheldGameboy.scala b/fpga/src/main/scala/platform/handheld/HandheldGameboy.scala index dcf504d8..01701c56 100644 --- a/fpga/src/main/scala/platform/handheld/HandheldGameboy.scala +++ b/fpga/src/main/scala/platform/handheld/HandheldGameboy.scala @@ -47,6 +47,7 @@ class HandheldGameboy extends Module with HandheldModule { val configRegImuAccelX = RegInit(0.U(16.W)) val configRegImuAccelY = RegInit(0.U(16.W)) val configRegDmgOffColor = RegInit(0.U(16.W)) + val configRegSgbButtons = RegInit(0.U(1.W)) val statRegStalls = RegInit(0.U(32.W)) val statRegCycles = RegInit(0.U(32.W)) @@ -99,6 +100,7 @@ class HandheldGameboy extends Module with HandheldModule { 0x0020 -> RegisterMap.Entry.rw(configRegImuAccelX), 0x0024 -> RegisterMap.Entry.rw(configRegImuAccelY), 0x0030 -> RegisterMap.Entry.w(configRegDmgOffColor), + 0x0034 -> RegisterMap.Entry.rw(configRegSgbButtons) 0x1000 -> RegisterMap.Entry.rw(statRegStalls), 0x1004 -> RegisterMap.Entry.rw(statRegCycles), @@ -140,8 +142,9 @@ class HandheldGameboy extends Module with HandheldModule { val buttonFilter = Module(new ButtonFilter(new InputV0.Buttons)) buttonFilter.io.enable := io.host.enable buttonFilter.io.input := io.input.buttons - gameboy.io.joypad.a := buttonFilter.io.output.a - gameboy.io.joypad.b := buttonFilter.io.output.b + val sgbButtons = configRegSgbButtons(0) + gameboy.io.joypad.a := (Mux(sgbButtons, buttonFilter.io.output.b, buttonFilter.io.output.a) + gameboy.io.joypad.b := (Mux(sgbButtons, buttonFilter.io.output.y, buttonFilter.io.output.b) gameboy.io.joypad.up := buttonFilter.io.output.up gameboy.io.joypad.down := buttonFilter.io.output.down gameboy.io.joypad.left := buttonFilter.io.output.left diff --git a/fpga/src/main/scala/platform/handheld/HandheldGba.scala b/fpga/src/main/scala/platform/handheld/HandheldGba.scala index 3b206289..58b0d3c0 100644 --- a/fpga/src/main/scala/platform/handheld/HandheldGba.scala +++ b/fpga/src/main/scala/platform/handheld/HandheldGba.scala @@ -90,6 +90,7 @@ class HandheldGba extends Module with HandheldModule { val configRegEmuCart = RegInit(0.U.asTypeOf(new EmulatedCartridge.Config)) val configRegRomSize = RegInit(0.U(25.W)) val configRegGBPlayer = RegInit(0.U(1.W)) + val configRegSgbButtons = RegInit(0.U(1.W)) val configRegImuGyroZ = RegInit(0.U(12.W)) val configRegImuAccelX = RegInit(0.U(12.W)) val configRegImuAccelY = RegInit(0.U(12.W)) @@ -138,6 +139,7 @@ class HandheldGba extends Module with HandheldModule { // Rom size (minus one), max (2**25 - 1), 32MiB 0x0004 -> RegisterMap.Entry.rw(configRegRomSize), 0x0008 -> RegisterMap.Entry.rw(configRegGBPlayer), + 0x000C -> RegisterMap.Entry.rw(configRegSgbButtons), 0x0100 -> RegisterMap.Entry.rw(configRegImuGyroZ), 0x0104 -> RegisterMap.Entry.rw(configRegImuAccelX), 0x0108 -> RegisterMap.Entry.rw(configRegImuAccelY), @@ -311,8 +313,9 @@ class HandheldGba extends Module with HandheldModule { val buttonFilter = Module(new ButtonFilter(new InputV0.Buttons)) buttonFilter.io.enable := io.host.enable buttonFilter.io.input := io.input.buttons - gba.io.keypad.a := buttonFilter.io.output.a - gba.io.keypad.b := buttonFilter.io.output.b + val sgbButtons = configRegSgbButtons(0) + gba.io.keypad.a := (Mux(sgbButtons, buttonFilter.io.output.b, buttonFilter.io.output.a) + gba.io.keypad.b := (Mux(sgbButtons, buttonFilter.io.output.y, buttonFilter.io.output.b) gba.io.keypad.l := buttonFilter.io.output.l gba.io.keypad.r := buttonFilter.io.output.r gba.io.keypad.up := buttonFilter.io.output.up