From 49fe8dabf4a77937c6be3a9067ed3bda7fadf353 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 21 Oct 2026 17:57:11 +0000
Subject: [PATCH 1/4] feat(gameboy): implement native Game Boy DMG/GBC sound
driver, target, and CI workflow
- Implemented hardware APU driver in drivers/gb_sound/gb_rdj.c controlling Pulse 1, Pulse 2, Wavetable, and Noise channels.
- Implemented real hardware backend in backends/backend_gb_hw.c with direct APU memory-mapped register access and wait_vbl_done().
- Added native 20x18 Game Boy tracker UI and build pipeline under targets/gameboy/ producing out/rom.gb via GBDK-2020 lcc.
- Created dedicated CI workflow .github/workflows/build-gameboy.yml with manual workflow_dispatch support.
- Integrated Game Boy driver/backend into PC host test runner with zero regressions.
Co-authored-by: ClausValcaTD <193546948+ClausValcaTD@users.noreply.github.com>
---
.github/workflows/build-gameboy.yml | 57 +++++
.github/workflows/build-genesis.yml | 4 +
.gitignore | 8 +
backends/backend_gb_hw.c | 105 +++++++++
drivers/gb_sound/gb_rdj.c | 253 +++++++++++++++++++++
targets/gameboy/Makefile | 37 +++
targets/gameboy/src/main.c | 45 ++++
targets/gameboy/src/ui_gb.c | 341 ++++++++++++++++++++++++++++
targets/gameboy/src/ui_gb.h | 33 +++
targets/genesis/src/input_mapper.c | 9 +-
10 files changed, 888 insertions(+), 4 deletions(-)
create mode 100644 .github/workflows/build-gameboy.yml
create mode 100644 backends/backend_gb_hw.c
create mode 100644 drivers/gb_sound/gb_rdj.c
create mode 100644 targets/gameboy/Makefile
create mode 100644 targets/gameboy/src/main.c
create mode 100644 targets/gameboy/src/ui_gb.c
create mode 100644 targets/gameboy/src/ui_gb.h
diff --git a/.github/workflows/build-gameboy.yml b/.github/workflows/build-gameboy.yml
new file mode 100644
index 0000000..bca3e03
--- /dev/null
+++ b/.github/workflows/build-gameboy.yml
@@ -0,0 +1,57 @@
+name: Game Boy Build
+
+on:
+ push:
+ branches: [ main ]
+ paths:
+ - 'targets/gameboy/**'
+ - 'backends/**'
+ - 'core/**'
+ - 'drivers/gb_sound/**'
+ - '.github/workflows/build-gameboy.yml'
+ pull_request:
+ branches: [ main ]
+ paths:
+ - 'targets/gameboy/**'
+ - 'backends/**'
+ - 'core/**'
+ - 'drivers/gb_sound/**'
+ - '.github/workflows/build-gameboy.yml'
+ workflow_dispatch:
+
+jobs:
+
+ build-gameboy:
+ name: Game Boy ROM (GBDK-2020)
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Install GBDK-2020
+ run: |
+ wget -q https://github.com/gbdk-2020/gbdk-2020/releases/download/4.5.0/gbdk-linux64.tar.gz
+ tar -xzf gbdk-linux64.tar.gz
+ echo "$GITHUB_WORKSPACE/gbdk/bin" >> $GITHUB_PATH
+
+ - name: Build Game Boy ROM
+ run: |
+ make -C targets/gameboy/
+
+ - name: Verify Game Boy ROM Output
+ run: |
+ if [ -f targets/gameboy/out/rom.gb ]; then
+ echo "Successfully generated rom.gb!"
+ ls -la targets/gameboy/out/rom.gb
+ else
+ echo "Error: targets/gameboy/out/rom.gb not found!"
+ ls -la targets/gameboy/out/ || true
+ false
+ fi
+
+ - name: Upload Game Boy ROM
+ uses: actions/upload-artifact@v4
+ with:
+ name: RDJ-gameboy-rom
+ path: targets/gameboy/out/rom.gb
diff --git a/.github/workflows/build-genesis.yml b/.github/workflows/build-genesis.yml
index 04054aa..0f6c7f8 100644
--- a/.github/workflows/build-genesis.yml
+++ b/.github/workflows/build-genesis.yml
@@ -8,6 +8,7 @@ on:
- 'backends/**'
- 'core/**'
- 'drivers/ym2612/**'
+ - 'drivers/gb_sound/**'
- 'tests/**'
- '.github/workflows/build-genesis.yml'
pull_request:
@@ -17,6 +18,7 @@ on:
- 'backends/**'
- 'core/**'
- 'drivers/ym2612/**'
+ - 'drivers/gb_sound/**'
- 'tests/**'
- '.github/workflows/build-genesis.yml'
workflow_dispatch:
@@ -82,10 +84,12 @@ jobs:
tests/test_main.c \
drivers/ym2612/ym2612_rdj.c \
drivers/nes_2a03/2a03_rdj.c \
+ drivers/gb_sound/gb_rdj.c \
backends/backend_null.c \
backends/backend_vgm.c \
backends/backend_serial.c \
backends/backend_genesis_hw.c \
+ backends/backend_gb_hw.c \
targets/genesis/src/ui.c \
targets/genesis/src/input_mapper.c \
-I. -Icore/ -o test_runner
diff --git a/.gitignore b/.gitignore
index d331e7f..3877198 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,17 @@
*.s
!targets/nes/src/crt0.s
out/
+targets/gameboy/out/
test_runner
test_output.vgm
+# GBDK intermediate build artifacts
+*.ihx
+*.lst
+*.sym
+*.noi
+*.map
+
# SGDK copied sources
targets/genesis/src/core/
targets/genesis/src/drivers/
diff --git a/backends/backend_gb_hw.c b/backends/backend_gb_hw.c
new file mode 100644
index 0000000..ffec784
--- /dev/null
+++ b/backends/backend_gb_hw.c
@@ -0,0 +1,105 @@
+/* GPLv3 - RetroDJ Project - Game Boy Real Hardware Backend */
+
+/*
+ * Copyright (C) 2025 RetroDJ Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should receive a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#if defined(RDJ_GENESIS_HARDWARE) || defined(SGDK_GCC) || defined(__m68k__)
+ #include
+#elif defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ #include
+ #include
+ #include
+#else
+ #include
+ #include
+#endif
+
+#include "core/rdj_backend.h"
+
+#if !defined(__PORT_sm83) && !defined(__TARGET_gb) && !defined(GBDK)
+static uint8_t gb_apu_shadow[0x40];
+#endif
+
+static void gb_hw_write_apu(uint16_t addr, uint8_t val) {
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ *((volatile uint8_t*)addr) = val;
+#else
+ if (addr >= 0xFF10 && addr <= 0xFF3F) {
+ gb_apu_shadow[addr - 0xFF10] = val;
+ }
+#endif
+}
+
+static void gb_hw_init(void) {
+ /* Master enable APU: Bit 7 = 1 in NR52 ($FF26) */
+ gb_hw_write_apu(0xFF26, 0x80);
+ /* Enable all output channels to left and right terminals in NR51 ($FF25) */
+ gb_hw_write_apu(0xFF25, 0xFF);
+ /* Set max stereo master volume in NR50 ($FF24) */
+ gb_hw_write_apu(0xFF24, 0x77);
+}
+
+static void gb_hw_shutdown(void) {
+ /* Disable APU master enable */
+ gb_hw_write_apu(0xFF26, 0x00);
+}
+
+static void gb_hw_write_ym2612(uint8_t port, uint8_t reg, uint8_t val) {
+ (void)port;
+ (void)reg;
+ (void)val;
+}
+
+static void gb_hw_wait_samples(uint16_t samples) {
+ (void)samples;
+}
+
+static void gb_hw_wait_frame(void) {
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ wait_vbl_done();
+#endif
+}
+
+static const char gb_backend_name[] = "Game Boy Real Hardware Backend";
+
+rdj_backend_t backend_gb_hw = {
+ gb_backend_name,
+ gb_hw_init,
+ gb_hw_shutdown,
+ gb_hw_write_ym2612,
+ gb_hw_write_apu,
+ gb_hw_wait_samples,
+ gb_hw_wait_frame
+};
+
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+rdj_backend_t *rdj_active_backend = &backend_gb_hw;
+
+void rdj_set_backend(rdj_backend_t *backend) {
+ if (backend) {
+ if (rdj_active_backend && rdj_active_backend->shutdown) {
+ rdj_active_backend->shutdown();
+ }
+ rdj_active_backend = backend;
+ if (rdj_active_backend->init) {
+ rdj_active_backend->init();
+ }
+ } else {
+ rdj_active_backend = &backend_gb_hw;
+ }
+}
+#endif
diff --git a/drivers/gb_sound/gb_rdj.c b/drivers/gb_sound/gb_rdj.c
new file mode 100644
index 0000000..d1d8fe2
--- /dev/null
+++ b/drivers/gb_sound/gb_rdj.c
@@ -0,0 +1,253 @@
+/* GPLv3 - RetroDJ Project - Game Boy Sound Hardware Driver */
+
+/*
+ * Copyright (C) 2025 RetroDJ Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should receive a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#if defined(RDJ_GENESIS_HARDWARE) || defined(SGDK_GCC) || defined(__m68k__)
+ #include
+#elif defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ #include
+ #include
+ #include
+#else
+ #include
+ #include
+#endif
+
+#include "core/rdj_driver.h"
+#include "core/rdj_backend.h"
+
+/* Register map (Game Boy APU hardware registers $FF10 - $FF26) */
+#define GB_NR10 0xFF10 /* CH1 Sweep */
+#define GB_NR11 0xFF11 /* CH1 Duty / Length */
+#define GB_NR12 0xFF12 /* CH1 Envelope */
+#define GB_NR13 0xFF13 /* CH1 Frequency Low */
+#define GB_NR14 0xFF14 /* CH1 Frequency High / Trigger */
+
+#define GB_NR21 0xFF16 /* CH2 Duty / Length */
+#define GB_NR22 0xFF17 /* CH2 Envelope */
+#define GB_NR23 0xFF18 /* CH2 Frequency Low */
+#define GB_NR24 0xFF19 /* CH2 Frequency High / Trigger */
+
+#define GB_NR30 0xFF1A /* CH3 Sound On/Off */
+#define GB_NR31 0xFF1B /* CH3 Length */
+#define GB_NR32 0xFF1C /* CH3 Output Level */
+#define GB_NR33 0xFF1D /* CH3 Frequency Low */
+#define GB_NR34 0xFF1E /* CH3 Frequency High / Trigger */
+
+#define GB_NR41 0xFF20 /* CH4 Length */
+#define GB_NR42 0xFF21 /* CH4 Envelope */
+#define GB_NR43 0xFF22 /* CH4 Polynomial Counter */
+#define GB_NR44 0xFF23 /* CH4 Control / Trigger */
+
+#define GB_NR50 0xFF24 /* Master Volume / VIN */
+#define GB_NR51 0xFF25 /* Panning / Sound Output Terminal */
+#define GB_NR52 0xFF26 /* Audio Master On/Off */
+
+#define GB_WAVE_RAM 0xFF30 /* 16 bytes Wave RAM (32 4-bit samples) */
+
+typedef void (*apu_write_fn)(uint16_t addr, uint8_t val);
+
+/* Forward Declarations */
+void gb_sound_rdj_init(uint32_t clock);
+void gb_sound_rdj_reset(void);
+void gb_sound_rdj_write(uint16_t addr, uint8_t val);
+void gb_sound_note_on(uint8_t ch, uint8_t note, uint8_t inst, uint8_t vol);
+void gb_sound_note_off(uint8_t ch);
+
+void gb_sound_rdj_write(uint16_t addr, uint8_t val) {
+ if (rdj_active_backend) {
+ apu_write_fn fn = rdj_active_backend->write_apu;
+ if (fn) {
+ fn(addr, val);
+ }
+ }
+}
+
+/* Precomputed 128-note MIDI frequency timer values for Game Boy:
+ * raw_freq = 2048 - (131072 / freq_hz)
+ * Clamped to 11-bit limit (0..2047)
+ */
+static const uint16_t gb_frequency_table[128] = {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 44, 157, 263, 363,
+ 457, 547, 631, 711, 786, 856, 923, 986,
+ 1046, 1102, 1155, 1205, 1253, 1297, 1339, 1379,
+ 1417, 1452, 1486, 1517, 1547, 1575, 1602, 1627,
+ 1650, 1673, 1694, 1714, 1732, 1750, 1767, 1783,
+ 1798, 1812, 1825, 1837, 1849, 1860, 1871, 1881,
+ 1890, 1899, 1907, 1915, 1923, 1930, 1936, 1943,
+ 1949, 1954, 1959, 1964, 1969, 1974, 1978, 1982,
+ 1985, 1989, 1992, 1995, 1998, 2001, 2004, 2006,
+ 2009, 2011, 2013, 2015, 2017, 2018, 2020, 2022,
+ 2023, 2025, 2026, 2027, 2028, 2029, 2030, 2031,
+ 2032, 2033, 2034, 2035, 2036, 2036, 2037, 2038
+};
+
+/* Standard 16-byte (32 4-bit samples) Triangle / Saw RAM pattern */
+static const uint8_t gb_default_wave_ram[16] = {
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+ 0xED, 0xCB, 0xA9, 0x87, 0x65, 0x43, 0x21, 0x00
+};
+
+static uint16_t gb_calc_frequency(uint8_t note) {
+ if (note > 127) note = 127;
+ return gb_frequency_table[note];
+}
+
+void gb_sound_rdj_init(uint32_t clock) {
+ (void)clock;
+ gb_sound_rdj_reset();
+}
+
+void gb_sound_rdj_reset(void) {
+ uint8_t i;
+
+ /* 1. Master enable APU: Bit 7 MUST be set to 1 in NR52 */
+ gb_sound_rdj_write(GB_NR52, 0x80);
+
+ /* 2. Master Volume: Left 7, Right 7 */
+ gb_sound_rdj_write(GB_NR50, 0x77);
+
+ /* 3. Panning: Enable CH1-CH4 on both Left & Right channels */
+ gb_sound_rdj_write(GB_NR51, 0xFF);
+
+ /* 4. Disable sweep on CH1 */
+ gb_sound_rdj_write(GB_NR10, 0x00);
+
+ /* 5. Silence CH1 & CH2 envelopes */
+ gb_sound_rdj_write(GB_NR12, 0x00);
+ gb_sound_rdj_write(GB_NR22, 0x00);
+
+ /* 6. Disable CH3 DAC, silence CH3 output level */
+ gb_sound_rdj_write(GB_NR30, 0x00);
+ gb_sound_rdj_write(GB_NR32, 0x00);
+
+ /* 7. Silence CH4 noise envelope */
+ gb_sound_rdj_write(GB_NR42, 0x00);
+
+ /* 8. Load default Wave RAM for CH3 */
+ gb_sound_rdj_write(GB_NR30, 0x80); /* Enable CH3 DAC to access Wave RAM */
+ for (i = 0; i < 16; i++) {
+ gb_sound_rdj_write(GB_WAVE_RAM + i, gb_default_wave_ram[i]);
+ }
+}
+
+void gb_sound_note_on(uint8_t ch, uint8_t note, uint8_t inst, uint8_t vol) {
+ uint16_t freq;
+ uint8_t vol_4bit;
+ uint8_t duty_bits;
+ uint8_t shift_val;
+ uint8_t noise_poly;
+
+ (void)inst;
+ vol_4bit = vol ? (vol >> 3) : 15; /* Map 0..128 -> 0..15 range, default 15 */
+ if (vol_4bit > 15) vol_4bit = 15;
+
+ if (ch == 0) {
+ /* CH1: Pulse 1 with Sweep */
+ freq = gb_calc_frequency(note);
+ duty_bits = 0x80; /* 50% duty cycle */
+
+ gb_sound_rdj_write(GB_NR10, 0x00); /* Disable sweep */
+ gb_sound_rdj_write(GB_NR11, duty_bits); /* Duty cycle + 0 length */
+ gb_sound_rdj_write(GB_NR12, (vol_4bit << 4)); /* Initial volume, envelope stop */
+ gb_sound_rdj_write(GB_NR13, (uint8_t)(freq & 0xFF)); /* Freq low 8 bits */
+ gb_sound_rdj_write(GB_NR14, (uint8_t)(0x80 | ((freq >> 8) & 0x07))); /* Trigger (bit 7) + Freq high 3 bits */
+ }
+ else if (ch == 1) {
+ /* CH2: Pulse 2 */
+ freq = gb_calc_frequency(note);
+ duty_bits = 0x80; /* 50% duty cycle */
+
+ gb_sound_rdj_write(GB_NR21, duty_bits);
+ gb_sound_rdj_write(GB_NR22, (vol_4bit << 4));
+ gb_sound_rdj_write(GB_NR23, (uint8_t)(freq & 0xFF));
+ gb_sound_rdj_write(GB_NR24, (uint8_t)(0x80 | ((freq >> 8) & 0x07)));
+ }
+ else if (ch == 2) {
+ /* CH3: Wavetable */
+ freq = gb_calc_frequency(note);
+ shift_val = 0x20; /* 100% volume level (bits 6-5 = 01 -> 0x20) */
+
+ if (vol_4bit == 0) {
+ shift_val = 0x00; /* Mute */
+ } else if (vol_4bit < 6) {
+ shift_val = 0x60; /* 25% */
+ } else if (vol_4bit < 11) {
+ shift_val = 0x40; /* 50% */
+ }
+
+ gb_sound_rdj_write(GB_NR30, 0x80); /* Enable CH3 DAC */
+ gb_sound_rdj_write(GB_NR32, shift_val);
+ gb_sound_rdj_write(GB_NR33, (uint8_t)(freq & 0xFF));
+ gb_sound_rdj_write(GB_NR34, (uint8_t)(0x80 | ((freq >> 8) & 0x07)));
+ }
+ else if (ch == 3) {
+ /* CH4: Noise */
+ noise_poly = (note & 0x0F) | ((note & 0x70) >> 1); /* Period / clock shift */
+
+ gb_sound_rdj_write(GB_NR41, 0x00);
+ gb_sound_rdj_write(GB_NR42, (vol_4bit << 4));
+ gb_sound_rdj_write(GB_NR43, noise_poly);
+ gb_sound_rdj_write(GB_NR44, 0x80); /* Trigger */
+ }
+}
+
+void gb_sound_note_off(uint8_t ch) {
+ if (ch == 0) {
+ gb_sound_rdj_write(GB_NR12, 0x00);
+ gb_sound_rdj_write(GB_NR14, 0x80); /* Re-trigger silenced envelope */
+ } else if (ch == 1) {
+ gb_sound_rdj_write(GB_NR22, 0x00);
+ gb_sound_rdj_write(GB_NR24, 0x80);
+ } else if (ch == 2) {
+ gb_sound_rdj_write(GB_NR32, 0x00); /* Output level = Mute */
+ } else if (ch == 3) {
+ gb_sound_rdj_write(GB_NR42, 0x00);
+ gb_sound_rdj_write(GB_NR44, 0x80);
+ }
+}
+
+static void gb_sound_wrapper_write_reg(uint16_t reg, uint8_t val) {
+ gb_sound_rdj_write(reg, val);
+}
+
+static void gb_sound_wrapper_shutdown(void) {
+ gb_sound_rdj_reset();
+ gb_sound_rdj_write(GB_NR52, 0x00); /* Disable APU */
+}
+
+static const char gb_driver_name[] = "Game Boy APU Sound Driver";
+static const char gb_chip_name[] = "DMG/GBC APU";
+
+/* Global unified driver structure instance */
+rdj_driver_t gb_sound_driver = {
+ gb_driver_name,
+ gb_chip_name,
+ 4,
+ 16,
+ gb_sound_rdj_init,
+ gb_sound_rdj_reset,
+ gb_sound_note_on,
+ gb_sound_note_off,
+ gb_sound_wrapper_write_reg,
+ gb_sound_wrapper_shutdown
+};
diff --git a/targets/gameboy/Makefile b/targets/gameboy/Makefile
new file mode 100644
index 0000000..feee39b
--- /dev/null
+++ b/targets/gameboy/Makefile
@@ -0,0 +1,37 @@
+# RetroDJ - Game Boy Target Makefile (GBDK-2020)
+# GPLv3 - RetroDJ Project
+
+LCC := lcc
+
+INCS := -I. -Isrc -I../../ -I../../core -I../../drivers/gb_sound -I../../backends -I../../targets/genesis/src
+CFLAGS := -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -Wm-yc $(INCS)
+
+OUT_DIR := out
+TARGET := $(OUT_DIR)/rom.gb
+
+C_SRCS := src/main.c \
+ src/ui_gb.c \
+ ../../drivers/gb_sound/gb_rdj.c \
+ ../../backends/backend_gb_hw.c \
+ ../../targets/genesis/src/input_mapper.c
+
+C_OBJS := $(C_SRCS:.c=.o)
+
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+.PHONY: all clean
+
+all: $(TARGET)
+
+%.o: %.c
+ $(LCC) $(CFLAGS) -c $< -o $@
+
+$(TARGET): $(C_OBJS)
+ mkdir -p $(OUT_DIR)
+ $(LCC) $(CFLAGS) $(C_OBJS) -o $(TARGET)
+
+clean:
+ rm -rf $(OUT_DIR)
+ rm -f src/*.o ../../drivers/gb_sound/*.o ../../backends/*.o ../../targets/genesis/src/*.o
+ rm -f src/*.lst src/*.sym ../../drivers/gb_sound/*.lst ../../backends/*.lst
diff --git a/targets/gameboy/src/main.c b/targets/gameboy/src/main.c
new file mode 100644
index 0000000..780eea5
--- /dev/null
+++ b/targets/gameboy/src/main.c
@@ -0,0 +1,45 @@
+/* RetroDJ - Game Boy Main Entry Point */
+/* GPLv3 - RetroDJ Project */
+
+/*
+ * Copyright (C) 2025 RetroDJ Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should receive a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "ui_gb.h"
+#include "core/rdj_driver.h"
+#include "core/rdj_backend.h"
+
+extern rdj_driver_t gb_sound_driver;
+extern rdj_backend_t backend_gb_hw;
+
+int main(void) {
+ /* Initialize and activate Game Boy hardware backend */
+ rdj_set_backend(&backend_gb_hw);
+
+ /* Initialize Game Boy sound driver */
+ gb_sound_driver.init(0);
+
+ /* Initialize Tracker UI */
+ ui_init();
+
+ while (1) {
+ wait_vbl_done();
+ ui_update();
+ ui_render();
+ }
+
+ return 0;
+}
diff --git a/targets/gameboy/src/ui_gb.c b/targets/gameboy/src/ui_gb.c
new file mode 100644
index 0000000..abaf4d7
--- /dev/null
+++ b/targets/gameboy/src/ui_gb.c
@@ -0,0 +1,341 @@
+/* RetroDJ - Game Boy Tracker UI Implementation */
+/* GPLv3 - RetroDJ Project */
+
+#include "ui_gb.h"
+#include "core/rdj_driver.h"
+#include "core/rdj_backend.h"
+
+#include
+#include
+
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+#include
+#include
+#include
+#endif
+
+static rdj_song_t g_song;
+static input_tracker_state_t g_tracker_state;
+
+static uint8_t s_tick_counter = 0;
+static uint8_t s_current_play_row = 0;
+static uint8_t s_prev_is_playing = 0;
+
+static uint8_t s_last_cursor_row = 0;
+static uint8_t s_last_cursor_chan = 0;
+static uint8_t s_last_cursor_subcol = 0;
+static uint8_t s_last_start_row = 0;
+static uint8_t s_last_status_bpm = 0;
+static uint8_t s_last_status_oct = 0;
+static uint8_t s_last_status_ch = 0;
+static uint8_t s_last_status_playing = 0;
+
+static char hex_char(uint8_t val) {
+ val &= 0x0F;
+ return (val < 10) ? ('0' + val) : ('A' + (val - 10));
+}
+
+void get_note_name(uint8_t note, char *out) {
+ uint8_t note_idx;
+ int octave;
+ static const char note_names[12][2] = {
+ {'C', '-'}, {'C', '#'}, {'D', '-'}, {'D', '#'},
+ {'E', '-'}, {'F', '-'}, {'F', '#'}, {'G', '-'},
+ {'G', '#'}, {'A', '-'}, {'A', '#'}, {'B', '-'}
+ };
+
+ if (note == RDJ_NOTE_EMPTY || note == 0) {
+ out[0] = '-'; out[1] = '-'; out[2] = '-'; out[3] = '\0';
+ return;
+ }
+ if (note == RDJ_NOTE_OFF) {
+ out[0] = 'O'; out[1] = 'F'; out[2] = 'F'; out[3] = '\0';
+ return;
+ }
+ note_idx = (note - 12) % 12;
+ octave = (note - 12) / 12;
+ if (octave < 0) octave = 0;
+ if (octave > 9) octave = 9;
+
+ out[0] = note_names[note_idx][0];
+ out[1] = note_names[note_idx][1];
+ out[2] = (char)('0' + octave);
+ out[3] = '\0';
+}
+
+static void format_status_str(char *out) {
+ uint16_t bpm = g_song.bpm;
+ uint8_t oct = g_tracker_state.octave;
+ uint8_t ch = g_tracker_state.cursor_channel + 1;
+ bool playing = g_tracker_state.is_playing;
+
+ /* Fits 20 columns: "BPM:120 [STOP] O:4 C:1" */
+ out[0] = 'B'; out[1] = 'P'; out[2] = 'M'; out[3] = ':';
+ out[4] = (char)('0' + ((bpm / 100) % 10));
+ out[5] = (char)('0' + ((bpm / 10) % 10));
+ out[6] = (char)('0' + (bpm % 10));
+ out[7] = ' '; out[8] = '[';
+ if (playing) {
+ out[9] = 'P'; out[10] = 'L'; out[11] = 'A'; out[12] = 'Y';
+ } else {
+ out[9] = 'S'; out[10] = 'T'; out[11] = 'O'; out[12] = 'P';
+ }
+ out[13] = ']'; out[14] = ' ';
+ out[15] = 'O'; out[16] = ':';
+ out[17] = (char)('0' + (oct % 10));
+ out[18] = ' ';
+ out[19] = (char)('0' + (ch % 10));
+ out[20] = '\0';
+}
+
+static void format_row_str(uint8_t r, char *out) {
+ bool is_cursor_row = (r == g_tracker_state.cursor_row);
+ char note_name[4];
+ uint8_t c;
+ int pos = 0;
+
+ /* 20 columns: "00>|C-4*0|C-4 0|..." */
+ out[pos++] = hex_char(r >> 4);
+ out[pos++] = hex_char(r);
+ out[pos++] = is_cursor_row ? '>' : ' ';
+ out[pos++] = '|';
+
+ for (c = 0; c < 4; c++) {
+ rdj_row_t *cell = &g_song.patterns[0].rows[r][c];
+ bool is_curr_cell = is_cursor_row && (g_tracker_state.cursor_channel == c);
+ char sub_marker = (is_curr_cell) ? ((g_tracker_state.cursor_subcol == RDJ_SUBCOL_INST) ? 'i' : '*') : ' ';
+
+ get_note_name(cell->note, note_name);
+
+ out[pos++] = note_name[0];
+ out[pos++] = note_name[1];
+ out[pos++] = note_name[2];
+ out[pos++] = sub_marker;
+ }
+ out[pos] = '\0';
+}
+
+void ui_init(void) {
+ int r, c;
+
+ g_song.bpm = 120;
+ g_song.speed = 6;
+ g_song.order_length = 1;
+ g_song.order[0] = 0;
+
+ for (r = 0; r < RDJ_MAX_ROWS; r++) {
+ for (c = 0; c < RDJ_MAX_CHANNELS; c++) {
+ g_song.patterns[0].rows[r][c].note = RDJ_NOTE_EMPTY;
+ g_song.patterns[0].rows[r][c].instrument = 0;
+ g_song.patterns[0].rows[r][c].volume = 0;
+ g_song.patterns[0].rows[r][c].fx_cmd = 0;
+ g_song.patterns[0].rows[r][c].fx_val = 0;
+ }
+ }
+ g_song.patterns[0].length = 32;
+ g_song.patterns[0].speed = 6;
+
+ input_mapper_init(&g_tracker_state);
+
+ s_tick_counter = 0;
+ s_current_play_row = 0;
+ s_prev_is_playing = 0;
+
+ s_last_cursor_row = g_tracker_state.cursor_row;
+ s_last_cursor_chan = g_tracker_state.cursor_channel;
+ s_last_cursor_subcol = g_tracker_state.cursor_subcol;
+ s_last_start_row = 0;
+ s_last_status_bpm = (uint8_t)g_song.bpm;
+ s_last_status_oct = g_tracker_state.octave;
+ s_last_status_ch = g_tracker_state.cursor_channel;
+ s_last_status_playing = g_tracker_state.is_playing;
+
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ font_t min_font;
+ font_init();
+ min_font = font_load(font_ibm);
+ font_set(min_font);
+
+ /* Clear screen and draw initial layout */
+ cls();
+ gotoxy(0, 0);
+ printf("--- RetroDJ GameBoy ---");
+
+ char init_str[24];
+ format_status_str(init_str);
+ gotoxy(0, 1);
+ printf("%s", init_str);
+
+ gotoxy(0, 2);
+ printf("RW |PU1 PU2 WAV NOI ");
+ gotoxy(0, 3);
+ printf("---|----------------");
+
+ for (uint8_t i = 0; i < 12; i++) {
+ format_row_str(i, init_str);
+ gotoxy(0, 4 + i);
+ printf("%s", init_str);
+ }
+
+ gotoxy(0, 16);
+ printf("--------------------");
+ gotoxy(0, 17);
+ printf("A+DIR:Note B+DIR:Val");
+
+ SHOW_BKG;
+#endif
+}
+
+static void ui_trigger_row_notes(uint8_t row) {
+ uint8_t ch;
+ for (ch = 0; ch < 4; ch++) {
+ rdj_row_t *r = &g_song.patterns[0].rows[row][ch];
+ if (r->note != RDJ_NOTE_EMPTY && r->note != RDJ_NOTE_OFF) {
+ gb_sound_driver.note_on(ch, r->note, r->instrument, r->volume ? r->volume : 128);
+ } else if (r->note == RDJ_NOTE_OFF) {
+ gb_sound_driver.note_off(ch);
+ }
+ }
+}
+
+void ui_process_playback(void) {
+ uint8_t speed, pat_len, ch;
+ if (g_tracker_state.is_playing) {
+ if (!s_prev_is_playing) {
+ s_prev_is_playing = 1;
+ s_tick_counter = 0;
+ s_current_play_row = g_tracker_state.cursor_row;
+ ui_trigger_row_notes(s_current_play_row);
+ } else {
+ s_tick_counter++;
+ speed = g_song.patterns[0].speed ? g_song.patterns[0].speed : (g_song.speed ? g_song.speed : 6);
+ if (s_tick_counter >= speed) {
+ s_tick_counter = 0;
+ pat_len = g_song.patterns[0].length ? g_song.patterns[0].length : RDJ_MAX_ROWS;
+ s_current_play_row++;
+ if (s_current_play_row >= pat_len) {
+ s_current_play_row = 0;
+ }
+ g_tracker_state.cursor_row = s_current_play_row;
+ ui_trigger_row_notes(s_current_play_row);
+ }
+ }
+ } else {
+ if (s_prev_is_playing) {
+ s_prev_is_playing = 0;
+ s_tick_counter = 0;
+ for (ch = 0; ch < 4; ch++) {
+ gb_sound_driver.note_off(ch);
+ }
+ }
+ }
+}
+
+static uint16_t gb_read_controller(void) {
+ uint16_t buttons = 0;
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ uint8_t joy = joypad();
+ if (joy & J_A) buttons |= BUTTON_A;
+ if (joy & J_B) buttons |= BUTTON_B;
+ if (joy & J_START) buttons |= BUTTON_START;
+ if (joy & J_UP) buttons |= BUTTON_UP;
+ if (joy & J_DOWN) buttons |= BUTTON_DOWN;
+ if (joy & J_LEFT) buttons |= BUTTON_LEFT;
+ if (joy & J_RIGHT) buttons |= BUTTON_RIGHT;
+#endif
+ return buttons;
+}
+
+void ui_update(void) {
+ uint16_t joy = gb_read_controller();
+ input_mapper_update(&g_tracker_state, &g_song, joy);
+ ui_process_playback();
+}
+
+void ui_render(void) {
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ char cur_str[24];
+ uint8_t start_row, r, i;
+
+ /* 1. Update status bar if changed */
+ if (g_song.bpm != s_last_status_bpm ||
+ g_tracker_state.octave != s_last_status_oct ||
+ g_tracker_state.cursor_channel != s_last_status_ch ||
+ g_tracker_state.is_playing != s_last_status_playing) {
+
+ format_status_str(cur_str);
+ gotoxy(0, 1);
+ printf("%s", cur_str);
+
+ s_last_status_bpm = (uint8_t)g_song.bpm;
+ s_last_status_oct = g_tracker_state.octave;
+ s_last_status_ch = g_tracker_state.cursor_channel;
+ s_last_status_playing = g_tracker_state.is_playing;
+ }
+
+ /* 2. Calculate visible start_row */
+ start_row = g_tracker_state.cursor_row >= 6 ? g_tracker_state.cursor_row - 6 : 0;
+ if (start_row > RDJ_MAX_ROWS - 12) start_row = RDJ_MAX_ROWS - 12;
+
+ /* 3. Handle full grid redrawing if start_row changed */
+ if (start_row != s_last_start_row) {
+ s_last_start_row = start_row;
+ for (i = 0; i < 12; i++) {
+ r = start_row + i;
+ format_row_str(r, cur_str);
+ gotoxy(0, 4 + i);
+ printf("%s", cur_str);
+ }
+ s_last_cursor_row = g_tracker_state.cursor_row;
+ s_last_cursor_chan = g_tracker_state.cursor_channel;
+ s_last_cursor_subcol = g_tracker_state.cursor_subcol;
+ return;
+ }
+
+ /* 4. Update old and new cursor rows if cursor moved */
+ if (g_tracker_state.cursor_row != s_last_cursor_row) {
+ /* Render previous row without cursor */
+ r = s_last_cursor_row;
+ if (r >= start_row && r < start_row + 12) {
+ i = r - start_row;
+ format_row_str(r, cur_str);
+ gotoxy(0, 4 + i);
+ printf("%s", cur_str);
+ }
+
+ s_last_cursor_row = g_tracker_state.cursor_row;
+ s_last_cursor_chan = g_tracker_state.cursor_channel;
+ s_last_cursor_subcol = g_tracker_state.cursor_subcol;
+
+ /* Render new active row with cursor */
+ r = g_tracker_state.cursor_row;
+ if (r >= start_row && r < start_row + 12) {
+ i = r - start_row;
+ format_row_str(r, cur_str);
+ gotoxy(0, 4 + i);
+ printf("%s", cur_str);
+ }
+ return;
+ }
+
+ /* 5. Update active row if channel/subcol or cell content changed */
+ r = g_tracker_state.cursor_row;
+ if (r >= start_row && r < start_row + 12) {
+ i = r - start_row;
+ format_row_str(r, cur_str);
+ gotoxy(0, 4 + i);
+ printf("%s", cur_str);
+ }
+
+ s_last_cursor_chan = g_tracker_state.cursor_channel;
+ s_last_cursor_subcol = g_tracker_state.cursor_subcol;
+#endif
+}
+
+input_tracker_state_t *ui_get_tracker_state(void) {
+ return &g_tracker_state;
+}
+
+rdj_song_t *ui_get_song(void) {
+ return &g_song;
+}
diff --git a/targets/gameboy/src/ui_gb.h b/targets/gameboy/src/ui_gb.h
new file mode 100644
index 0000000..76d4af9
--- /dev/null
+++ b/targets/gameboy/src/ui_gb.h
@@ -0,0 +1,33 @@
+/* RetroDJ - Game Boy UI Header */
+/* GPLv3 - RetroDJ Project */
+
+#ifndef RDJ_GB_UI_H
+#define RDJ_GB_UI_H
+
+#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ #include
+ #include
+ #include
+#else
+ #include
+ #include
+#endif
+
+#include "targets/genesis/src/input_mapper.h"
+#include "core/sequencer.h"
+#include "core/rdj_driver.h"
+#include "core/rdj_backend.h"
+
+extern rdj_driver_t gb_sound_driver;
+extern rdj_backend_t backend_gb_hw;
+
+void ui_init(void);
+void ui_update(void);
+void ui_render(void);
+void ui_process_playback(void);
+
+void get_note_name(uint8_t note, char *out);
+input_tracker_state_t *ui_get_tracker_state(void);
+rdj_song_t *ui_get_song(void);
+
+#endif
diff --git a/targets/genesis/src/input_mapper.c b/targets/genesis/src/input_mapper.c
index 532908a..59a4c3e 100644
--- a/targets/genesis/src/input_mapper.c
+++ b/targets/genesis/src/input_mapper.c
@@ -3,8 +3,10 @@
#include "input_mapper.h"
+#if defined(RDJ_GENESIS_HARDWARE) || defined(SGDK_GCC) || defined(__m68k__)
extern void ym2612_rdj_note_on(uint8_t channel, uint8_t note, uint8_t instrument);
extern void ym2612_rdj_note_off(uint8_t channel);
+#endif
void input_mapper_init(input_tracker_state_t *state) {
if (!state) return;
@@ -47,14 +49,13 @@ void input_mapper_update(input_tracker_state_t *state, rdj_song_t *song, uint16_
/* BUTTON_C: Play audition note at current cursor */
if (pressed & BUTTON_C) {
- uint8_t audition_note;
+#if defined(RDJ_GENESIS_HARDWARE) || defined(SGDK_GCC) || defined(__m68k__)
cell = &song->patterns[0].rows[state->cursor_row][state->cursor_channel];
- audition_note = (cell->note != RDJ_NOTE_EMPTY && cell->note != RDJ_NOTE_OFF) ? cell->note : (state->octave * 12 + 12);
-#if !defined(__NES__) && !defined(__CC65__)
+ uint8_t audition_note = (cell->note != RDJ_NOTE_EMPTY && cell->note != RDJ_NOTE_OFF) ? cell->note : (state->octave * 12 + 12);
ym2612_rdj_note_on(state->cursor_channel, audition_note, cell->instrument);
#endif
} else if ((state->last_joy & BUTTON_C) && !(joy_state & BUTTON_C)) {
-#if !defined(__NES__) && !defined(__CC65__)
+#if defined(RDJ_GENESIS_HARDWARE) || defined(SGDK_GCC) || defined(__m68k__)
ym2612_rdj_note_off(state->cursor_channel);
#endif
}
From 0b4283cc5d2ee2d2bfee249bdb07d92a2404ae7c Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 21 Oct 2026 18:05:26 +0000
Subject: [PATCH 2/4] fix(ci): fix GBDKDIR trailing slash resolution in Game
Boy Makefile and workflow
- Updated targets/gameboy/Makefile to automatically append trailing slash to GBDKDIR when required by lcc.
- Updated .github/workflows/build-gameboy.yml to export GBDKDIR with trailing slash in GITHUB_ENV.
Co-authored-by: ClausValcaTD <193546948+ClausValcaTD@users.noreply.github.com>
---
.github/workflows/build-gameboy.yml | 1 +
targets/gameboy/Makefile | 11 ++++++++---
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/build-gameboy.yml b/.github/workflows/build-gameboy.yml
index bca3e03..adb9ae9 100644
--- a/.github/workflows/build-gameboy.yml
+++ b/.github/workflows/build-gameboy.yml
@@ -33,6 +33,7 @@ jobs:
run: |
wget -q https://github.com/gbdk-2020/gbdk-2020/releases/download/4.5.0/gbdk-linux64.tar.gz
tar -xzf gbdk-linux64.tar.gz
+ echo "GBDKDIR=$GITHUB_WORKSPACE/gbdk/" >> $GITHUB_ENV
echo "$GITHUB_WORKSPACE/gbdk/bin" >> $GITHUB_PATH
- name: Build Game Boy ROM
diff --git a/targets/gameboy/Makefile b/targets/gameboy/Makefile
index feee39b..d58bfef 100644
--- a/targets/gameboy/Makefile
+++ b/targets/gameboy/Makefile
@@ -1,7 +1,12 @@
# RetroDJ - Game Boy Target Makefile (GBDK-2020)
# GPLv3 - RetroDJ Project
-LCC := lcc
+GBDKDIR ?= /opt/gbdk
+ifneq ($(lastword $(subst /, ,$(GBDKDIR))),)
+ GBDKDIR := $(GBDKDIR)/
+endif
+
+LCC := $(GBDKDIR)bin/lcc
INCS := -I. -Isrc -I../../ -I../../core -I../../drivers/gb_sound -I../../backends -I../../targets/genesis/src
CFLAGS := -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -Wm-yc $(INCS)
@@ -25,11 +30,11 @@ C_OBJS := $(C_SRCS:.c=.o)
all: $(TARGET)
%.o: %.c
- $(LCC) $(CFLAGS) -c $< -o $@
+ GBDKDIR=$(GBDKDIR) $(LCC) $(CFLAGS) -c $< -o $@
$(TARGET): $(C_OBJS)
mkdir -p $(OUT_DIR)
- $(LCC) $(CFLAGS) $(C_OBJS) -o $(TARGET)
+ GBDKDIR=$(GBDKDIR) $(LCC) $(CFLAGS) $(C_OBJS) -o $(TARGET)
clean:
rm -rf $(OUT_DIR)
From e4e5d0f418b0897c723083a5a2eff6152dffa146 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 21 Oct 2026 18:16:27 +0000
Subject: [PATCH 3/4] feat(gameboy): enable Game Boy Color (GBC) Dual-Mode
support & LSDj color palettes
- Added -Wm-yc CGB Dual Mode header flag to targets/gameboy/Makefile and output rom.gbc alongside rom.gb.
- Implemented runtime CGB hardware check (_cpu == CGB_TYPE) in ui_gb.c to apply vibrant LSDj-inspired background palettes with DMG fallback.
- Updated .github/workflows/build-gameboy.yml CI workflow to build, verify, and upload rom.gbc artifacts.
Co-authored-by: ClausValcaTD <193546948+ClausValcaTD@users.noreply.github.com>
---
.github/workflows/build-gameboy.yml | 12 +++++++-----
targets/gameboy/src/ui_gb.c | 15 +++++++++++++++
2 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/build-gameboy.yml b/.github/workflows/build-gameboy.yml
index adb9ae9..81903e1 100644
--- a/.github/workflows/build-gameboy.yml
+++ b/.github/workflows/build-gameboy.yml
@@ -42,11 +42,11 @@ jobs:
- name: Verify Game Boy ROM Output
run: |
- if [ -f targets/gameboy/out/rom.gb ]; then
- echo "Successfully generated rom.gb!"
- ls -la targets/gameboy/out/rom.gb
+ if [ -f targets/gameboy/out/rom.gb ] && [ -f targets/gameboy/out/rom.gbc ]; then
+ echo "Successfully generated rom.gb and rom.gbc!"
+ ls -la targets/gameboy/out/
else
- echo "Error: targets/gameboy/out/rom.gb not found!"
+ echo "Error: ROM output missing!"
ls -la targets/gameboy/out/ || true
false
fi
@@ -55,4 +55,6 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: RDJ-gameboy-rom
- path: targets/gameboy/out/rom.gb
+ path: |
+ targets/gameboy/out/rom.gb
+ targets/gameboy/out/rom.gbc
diff --git a/targets/gameboy/src/ui_gb.c b/targets/gameboy/src/ui_gb.c
index abaf4d7..1c7f5b9 100644
--- a/targets/gameboy/src/ui_gb.c
+++ b/targets/gameboy/src/ui_gb.c
@@ -10,6 +10,7 @@
#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
#include
+#include
#include
#include
#endif
@@ -151,6 +152,20 @@ void ui_init(void) {
s_last_status_playing = g_tracker_state.is_playing;
#if defined(__PORT_sm83) || defined(__TARGET_gb) || defined(GBDK)
+ if (_cpu == CGB_TYPE) {
+ /* LSDj-inspired Color Palette: Light BG, Dark Navy Text, Cyan Highlight, Red Marker */
+ const palette_color_t cgb_palette[4] = {
+ RGB8(245, 245, 250), /* Color 0: Light clean background */
+ RGB8(30, 130, 230), /* Color 1: Cyan / Blue highlight */
+ RGB8(20, 25, 45), /* Color 2: Dark navy text */
+ RGB8(225, 50, 50) /* Color 3: Red cursor/accents */
+ };
+ set_bkg_palette(0, 1, cgb_palette);
+ } else {
+ /* DMG Monochrome fallback (Normal 4 shades: 11 10 01 00) */
+ BGP_REG = 0xE4;
+ }
+
font_t min_font;
font_init();
min_font = font_load(font_ibm);
From cc154e692fd7724663d9045cb267af87d88d5253 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 21 Oct 2026 18:19:35 +0000
Subject: [PATCH 4/4] feat(gameboy): enable Game Boy Color (GBC) Dual-Mode
support & LSDj color palettes
- Added -Wm-yc CGB Dual Mode header flag to targets/gameboy/Makefile and output rom.gbc alongside rom.gb.
- Implemented runtime CGB hardware check (_cpu == CGB_TYPE) in ui_gb.c to apply vibrant LSDj-inspired background palettes with DMG fallback.
- Updated .github/workflows/build-gameboy.yml CI workflow to build, verify, and upload rom.gbc artifacts.
Co-authored-by: ClausValcaTD <193546948+ClausValcaTD@users.noreply.github.com>
---
targets/gameboy/Makefile | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/targets/gameboy/Makefile b/targets/gameboy/Makefile
index d58bfef..f8abe2f 100644
--- a/targets/gameboy/Makefile
+++ b/targets/gameboy/Makefile
@@ -12,7 +12,8 @@ INCS := -I. -Isrc -I../../ -I../../core -I../../drivers/gb_sound -I../../backend
CFLAGS := -Wa-l -Wl-m -Wl-j -DUSE_SFR_FOR_REG -Wm-yc $(INCS)
OUT_DIR := out
-TARGET := $(OUT_DIR)/rom.gb
+TARGET_GB := $(OUT_DIR)/rom.gb
+TARGET_GBC := $(OUT_DIR)/rom.gbc
C_SRCS := src/main.c \
src/ui_gb.c \
@@ -27,14 +28,17 @@ C_OBJS := $(C_SRCS:.c=.o)
.PHONY: all clean
-all: $(TARGET)
+all: $(TARGET_GB) $(TARGET_GBC)
%.o: %.c
GBDKDIR=$(GBDKDIR) $(LCC) $(CFLAGS) -c $< -o $@
-$(TARGET): $(C_OBJS)
+$(TARGET_GB): $(C_OBJS)
mkdir -p $(OUT_DIR)
- GBDKDIR=$(GBDKDIR) $(LCC) $(CFLAGS) $(C_OBJS) -o $(TARGET)
+ GBDKDIR=$(GBDKDIR) $(LCC) $(CFLAGS) $(C_OBJS) -o $(TARGET_GB)
+
+$(TARGET_GBC): $(TARGET_GB)
+ cp $(TARGET_GB) $(TARGET_GBC)
clean:
rm -rf $(OUT_DIR)