So the current way of adding thrird party code is annoying ... either someone need to do the patch in the project (like gbstudio or rgbds-live) or borher you with PR (and sometimes they are wrong (sorry again)) I read your `emulator-debug.c` code and got to the conclusion that this should be done like there the idea is : 1. move all third party code to `src/emscripten/thirdparty-default-hooks.c` in similar way as `emulator-debug.c` does 2. remove third party exports from exported.json (and using `EMSCRIPTEN_KEEPALIVE` in `thirdparty-default-hooks.c`) 3. modify CMakeList.txt in the way that you could just replace `thirdparty-default-hooks.c` with your custom code 4. add `EMULATOR_FIELDS_HOOK` inside `Emulator` structure (for extending like breakpoints) only 2 new hooks are needed for existing code ```C static inline void HOOK_emulator_step_after(Emulator* e, const char* f) { (void)e; (void)f; } static inline void HOOK_serial_write(Emulator* e, const char* f, u8 v) { (void)e; (void)f; (void)v; } ``` in ```C static void emulator_step_internal(Emulator* e) { //.... execute_instruction(e); HOOK0(emulator_step_after); //... } static void write_io(Emulator* e, MaskedAddress addr, u8 value) { //... case IO_SB_ADDR: //... HOOK(serial_write, value); break; //... } ``` <details> <summary>and EMSCRIPTEN section of CMakeList.txt may looks like (click to show)</summary> ```cmake add_executable(binjgb src/memory.c src/common.c src/joypad.c src/rewind.c src/emscripten/wrapper.c) set(EXPORTED_JSON ${PROJECT_SOURCE_DIR}/src/emscripten/exported.json) target_include_directories(binjgb PUBLIC ${PROJECT_SOURCE_DIR}/src) set(BINJGB_THIRDPARTY_HOOKS_FILE "${CMAKE_CURRENT_SOURCE_DIR}/src/emscripten/thirdparty-default-hooks.c" CACHE FILEPATH "Path to the emulator hooks implementation file") if (GBSTUDIO) set(THIRDPARTY_HOOKS ON) target_compile_definitions(binjgb PUBLIC GBSTUDIO) endif () if (THIRDPARTY_HOOKS) target_sources(binjgb PRIVATE ${BINJGB_THIRDPARTY_HOOKS_FILE}) else() target_sources(binjgb PRIVATE src/emulator.c) endif() if(NOT BINJGB_LINKER_OPTIONS) set(BINJGB_LINKER_OPTIONS -sMALLOC=emmalloc -sASSERTIONS=0 -sENVIRONMENT=web -sFILESYSTEM=0 -sEXIT_RUNTIME=0 -sMODULARIZE=1 -sEXPORT_NAME=Binjgb ) endif() if (WASM) list(APPEND BINJGB_LINKER_OPTIONS -sWASM=1) else() list(APPEND BINJGB_LINKER_OPTIONS -sWASM=0) endif () list(APPEND BINJGB_LINKER_OPTIONS -sEXPORTED_FUNCTIONS=@${EXPORTED_JSON}) target_link_options(binjgb PRIVATE ${BINJGB_LINKER_OPTIONS}) set_target_properties(binjgb PROPERTIES LINK_DEPENDS "${EXPORTED_JSON}" ) ``` </details> I have code ready to PR (tested) but this will obsolete #69 ... also for sanity purpose i would recomend to accept #71 first as we could see if builds fail or not <details> <summary>here is an thirdparty-default-hooks.c (click to show)</summary> ```C #include "common.h" #include "emulator.h" #include <emscripten.h> // hooks we wana implement #define IS_SPEC_emulator_step_after ~,1 #define IS_SPEC_serial_write ~,1 //fallback to default/dummy "implementations" #define HOOK_SELECT_INNER(...) HOOK_SELECT_GET_3(__VA_ARGS__) #define HOOK_SELECT_GET_3(a, b, slot, ...) slot #define HOOK_VOID_CALL(name, e, f, ...) HOOK_##name(e, f, ##__VA_ARGS__) #define HOOK_VOID_IGNORE(...) ((void)0) #define HOOK_BOOL_CALL(name, e, f, ...) HOOK_##name(e, f) #define HOOK_BOOL_IGNORE(...) (FALSE) #define HOOK0(name) \ HOOK_SELECT_INNER(IS_SPEC_##name, HOOK_VOID_CALL, HOOK_VOID_IGNORE)(name, e, __func__) #define HOOK(name, ...) \ HOOK_SELECT_INNER(IS_SPEC_##name, HOOK_VOID_CALL, HOOK_VOID_IGNORE)(name, e, __func__, __VA_ARGS__) #define HOOK0_FALSE(name) \ HOOK_SELECT_INNER(IS_SPEC_##name, HOOK_BOOL_CALL, HOOK_BOOL_IGNORE)(name, e, __func__) #ifndef BREAKPOINTS_MAX_BANKS_NUMBER #define BREAKPOINTS_MAX_BANKS_NUMBER 1 #endif typedef uint32_t breakpoints_type; #define MEMORY_SIZE (64 * 1024) #define BREAKPOINTS_BIT_SIZE (sizeof(breakpoints_type) * 8) #define BREAKPOINTS_SIZE ((BREAKPOINTS_MAX_BANKS_NUMBER * MEMORY_SIZE) / BREAKPOINTS_BIT_SIZE) #define BREAKPOINTS_SHIFT (__builtin_ctz(BREAKPOINTS_BIT_SIZE)) #define BREAKPOINTS_MASK (BREAKPOINTS_BIT_SIZE - 1) #define BREAKPOINTS_BANK_SHIFT (16 - BREAKPOINTS_SHIFT) #define EMULATOR_FIELDS_HOOK breakpoints_type breakpoint[BREAKPOINTS_SIZE] __attribute__((aligned(8))); static void HOOK_emulator_step_after(Emulator*, const char* func_name); static void HOOK_serial_write(Emulator*, const char* func_name, u8 value); #include "emulator.c" static inline uint32_t emulator_get_banked_PC_inline(Emulator *e) { #if BREAKPOINTS_MAX_BANKS_NUMBER > 1 uint16_t pc = REG.PC; if (pc < 0x4000) { return (MMAP_STATE.rom_base[0] << (16 - ROM_BANK_SHIFT)) | pc; } else if (pc < 0x8000) { return (MMAP_STATE.rom_base[1] << (16 - ROM_BANK_SHIFT)) | pc; } else if (pc < 0xA000) { return (e->state.vram.bank << 16) | pc; } else if (pc < 0xC000) { return (MMAP_STATE.ext_ram_base << (16 - EXT_RAM_BANK_SHIFT)) | pc; } else if (pc < 0xE000) { return (e->state.wram.bank << 16) | pc; } return pc; #else return REG.PC; #endif } static inline bool is_breakpoint(Emulator* e, uint32_t banked_pc) { uint32_t idx = banked_pc >> BREAKPOINTS_SHIFT; return (e->breakpoint[idx] & ((breakpoints_type)1 << (banked_pc & BREAKPOINTS_MASK))); } void HOOK_serial_write(Emulator* e, const char* func_name, u8 value) { EM_ASM({emulator.serialCallback($0);}, value); } void HOOK_emulator_step_after(Emulator* e, const char* func_name) { uint32_t banked_pc = emulator_get_banked_PC_inline(e); if (is_breakpoint(e, banked_pc)) { e->state.event |= EMULATOR_EVENT_BREAKPOINT; } } EMSCRIPTEN_KEEPALIVE void emulator_set_breakpoint(Emulator* e, uint32_t addr) { uint32_t idx = addr >> BREAKPOINTS_SHIFT; e->breakpoint[idx] |= ((breakpoints_type)1 << (addr & BREAKPOINTS_MASK)); } EMSCRIPTEN_KEEPALIVE void emulator_clear_breakpoints(Emulator* e) { ZERO_MEMORY(e->breakpoint); } EMSCRIPTEN_KEEPALIVE uint32_t emulator_get_banked_PC(Emulator *e) { return emulator_get_banked_PC_inline(e); } EMSCRIPTEN_KEEPALIVE void emulator_render_vram(Emulator* e, u32* buffer) { memset(buffer, 0, sizeof(u32) * 256 * 256); for (int ty = 0; ty < 24; ty++) { for (int bank = 0; bank < 2; bank++) { for (int tx = 0; tx < 16; tx++) { for (int row = 0; row < 8; row++) { int n = tx * 16 + ty * 16 * 16 + row * 2 + (bank << 13); u8 a = VRAM.data[n]; u8 b = VRAM.data[n + 1]; for (int x = 0; x < 8; x++) { u32 color = 0xFFC2F0C4; u8 bit = (0x80 >> x); if ((a & bit) && (b & bit)) { color = 0xFF001B2D; } else if (a & bit) { color = 0xFFA8B95A; } else if (b & bit) { color = 0xFF6E601E; } else if (x == 7 || row == 7) { color = 0xFFB2E0B4; } buffer[(tx * 8 + x + bank * 128) + (ty * 8 + row) * 256] = color; } } } } } if (IS_CGB) { for (int idx = 0; idx < 8; idx++) { for (int col = 0; col < PALETTE_COLOR_COUNT; col++) { for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { buffer[x + idx * 8 + (200 + col * 8 + y) * 256] = PPU.bgcp.palettes[idx].color[col]; buffer[x + idx * 8 + (200 + col * 8 + y) * 256 + 128] = PPU.obcp.palettes[idx].color[col]; } } } } } else { for (int type = 0; type < PALETTE_TYPE_COUNT; type++) { for (int col = 0; col < PALETTE_COLOR_COUNT; col++) { for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { buffer[x + type * 8 + (200 + col * 8 + y) * 256] = e->pal[type].color[col]; } } } } } } EMSCRIPTEN_KEEPALIVE void emulator_render_background(Emulator* e, u32* buffer, int type) { memset(buffer, 0, sizeof(u32) * 256 * 256); int bank = 0; int tile_map = 0x1800 + ((type & 1) ? 0x400 : 0); for (int ty = 0; ty < 32; ty++) { for (int tx = 0; tx < 32; tx++) { u8 tile = VRAM.data[tile_map + tx + ty * 32]; int offset = 0; if(tile < 128) offset = (LCDC.bg_tile_data_select == TILE_DATA_8000_8FFF) ? 0 : 0x1000; for (int row = 0; row < 8; row++) { int n = offset + tile * 16 + row * 2; u8 a = VRAM.data[n]; u8 b = VRAM.data[n + 1]; for (int x = 0; x < 8; x++) { u32 color = 0xFFC2F0C4; u8 bit = (0x80 >> x); if ((a & bit) && (b & bit)) { color = 0xFF001B2D; } else if (a & bit) { color = 0xFFA8B95A; } else if (b & bit) { color = 0xFF6E601E; } else if (x == 7 || row == 7) { color = 0xFFB2E0B4; } buffer[(tx * 8 + x) + (ty * 8 + row) * 256] = color; } } } } for (int x = 0; x < SCREEN_WIDTH; x++) { buffer[((PPU.scx + x) % 256) + (PPU.scy * 256)] &= 0xFF7F7F7F; buffer[((PPU.scx + x) % 256) + ((PPU.scy + SCREEN_HEIGHT - 1) % 256) * 256] &= 0xFF7F7F7F; } for (int y = 0; y < SCREEN_HEIGHT; y++) { buffer[PPU.scx + ((PPU.scy + y) % 256) * 256] &= 0xFF7F7F7F; buffer[((PPU.scx + SCREEN_WIDTH) % 256) + ((PPU.scy + y) % 256) * 256] &= 0xFF7F7F7F; } } #ifdef GBSTUDIO EMSCRIPTEN_KEEPALIVE Bool set_audio_channel_mute(Emulator *e, int channel, Bool muted) { EmulatorConfig emu_config = emulator_get_config(e); emu_config.disable_sound[channel] = muted; emulator_set_config(e, &emu_config); return emu_config.disable_sound[channel]; } #endif ``` </details> Now if rgbds-live developer wana change something (fx me) they can just do custom hooks based on `thirdparty-default-hooks.c` as long as existing hooks allow