diff --git a/CMakeLists.txt b/CMakeLists.txt index a4dec6d..9ede742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -288,7 +288,7 @@ if(WARN_VARIABLE_LENGTH_ARRAY_AVAILABLE) endif(WARN_VARIABLE_LENGTH_ARRAY_AVAILABLE) if(EMSCRIPTEN) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-rtti -fno-exceptions -O3 -s USE_SDL=2 -s USE_ZLIB=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DEMSCRIPTEN -ffile-prefix-map=${CMAKE_SOURCE_DIR}=hatari -ffile-prefix-map=${CMAKE_BINARY_DIR}=build -fno-rtti -fno-exceptions -O3 -s USE_SDL=2 -s USE_ZLIB=1") set(CMAKE_EXECUTABLE_SUFFIX ".html") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 540c97e..a022d00 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,7 +12,7 @@ set(SOURCES vdi.c inffile.c video.c wavFormat.c xbios.c ymFormat.c lilo.c) if(EMSCRIPTEN) - set(SOURCES ${SOURCES} emscripten.js emscripten_shell.html) + set(SOURCES ${SOURCES} lcweb.c emscripten.js emscripten_shell.html) endif() # Disk image code is shared with the hmsa tool, so we put it into a library: @@ -202,14 +202,7 @@ elseif(EMSCRIPTEN) ${APP_NAME} PROPERTIES LINK_FLAGS - "-O3 \ --fno-exceptions \ --s USE_SDL=2 -s USE_ZLIB=1 -s INITIAL_MEMORY=262144000 -s ASYNCIFY -s -s ASSERTIONS=0 \ --s EXPORTED_RUNTIME_METHODS='[\"ccall\",\"cwrap\"]' \ --s EXPORTED_FUNCTIONS=\"['_main','_Reset_Warm','_Reset_Cold','_Statusbar_UpdateInfo','_Main_UnPauseEmulation','_Floppy_InsertDiskIntoDrive','_Floppy_SetDiskFileName','_Floppy_EjectDiskFromDrive','_Configuration_ChangeMemory','_Configuration_ChangeSystem','_Configuration_ChangeTos','_Configuration_ChangeUseHardDiskDirectories','_Configuration_ChangeFastForward','_Configuration_Apply','_IoMem_UnInit','_IoMem_Init']\" \ ---preload-file ${CMAKE_BINARY_DIR}/files/@/share/hatari/ \ ---pre-js ${CMAKE_SOURCE_DIR}/src/emscripten.js \ ---shell-file ${CMAKE_SOURCE_DIR}/src/emscripten_shell.html" + "-O3 -fno-exceptions -sUSE_SDL=2 -sUSE_ZLIB=1 -sINITIAL_MEMORY=67108864 -sASYNCIFY -sASSERTIONS=0 -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,HEAPU8,ENV -sMODULARIZE -sEXPORT_ES6 -sEXPORT_NAME=createHatari -sENVIRONMENT=web -sINCOMING_MODULE_JS_API=arguments,canvas,print,printErr,preRun,postRun,preInit,locateFile,wasmBinary,onRuntimeInitialized,onAbort,setStatus,monitorRunDependencies -sEXPORTED_FUNCTIONS=_main,_Reset_Warm,_Reset_Cold,_Main_UnPauseEmulation,_Floppy_InsertDiskIntoDrive,_Floppy_SetDiskFileName,_Floppy_EjectDiskFromDrive,_Configuration_Apply,_malloc,_free,_lcweb_set_input,_lcweb_linea,_lcweb_ram,_lcweb_ram_size,_lcweb_screen_base,_lcweb_palette,_lcweb_shifter_mode,_lcweb_ram_hash --preload-file ${CMAKE_BINARY_DIR}/files/@/share/hatari/ --pre-js ${CMAKE_SOURCE_DIR}/src/emscripten.js --shell-file ${CMAKE_SOURCE_DIR}/src/emscripten_shell.html ${LC_EXTRA_LINK}" ) # Other targets, use default sources else() diff --git a/src/cpu/CMakeLists.txt b/src/cpu/CMakeLists.txt index e91d6a7..53df8ed 100644 --- a/src/cpu/CMakeLists.txt +++ b/src/cpu/CMakeLists.txt @@ -18,30 +18,33 @@ set(WINUAE_SRCS cpudefs.c cpummu.c cpummu030.c debug.c disasm.c newcpu_common.c # once for cross compiling (with calling the host cc directly) and once # for native compiling so that the rules also work for non-Unix environments... if(CMAKE_CROSSCOMPILING) + if(NOT HOST_CC) + set(HOST_CC cc) + endif() - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/build68k - COMMAND cc -I${CMAKE_CURRENT_SOURCE_DIR} + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/build68k${HOST_EXE} + COMMAND ${HOST_CC} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/build68k.c ${CMAKE_CURRENT_SOURCE_DIR}/writelog.c - -o ${CMAKE_CURRENT_BINARY_DIR}/build68k + -o ${CMAKE_CURRENT_BINARY_DIR}/build68k${HOST_EXE} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/build68k.c) add_custom_command(OUTPUT cpudefs.c - COMMAND ./build68k < ${CMAKE_CURRENT_SOURCE_DIR}/table68k >cpudefs.c + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/build68k${HOST_EXE} < ${CMAKE_CURRENT_SOURCE_DIR}/table68k >cpudefs.c DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/table68k - ${CMAKE_CURRENT_BINARY_DIR}/build68k) + ${CMAKE_CURRENT_BINARY_DIR}/build68k${HOST_EXE}) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gencpu - COMMAND cc -I${CMAKE_CURRENT_SOURCE_DIR} + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gencpu${HOST_EXE} + COMMAND ${HOST_CC} -I${CMAKE_CURRENT_SOURCE_DIR} cpudefs.c ${CMAKE_CURRENT_SOURCE_DIR}/gencpu.c ${CMAKE_CURRENT_SOURCE_DIR}/readcpu.c - -o ${CMAKE_CURRENT_BINARY_DIR}/gencpu + -o ${CMAKE_CURRENT_BINARY_DIR}/gencpu${HOST_EXE} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/gencpu.c ${CMAKE_CURRENT_SOURCE_DIR}/readcpu.c cpudefs.c) add_custom_command(OUTPUT ${CPUEMU_SRCS} - COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gencpu - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gencpu) + COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gencpu${HOST_EXE} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gencpu${HOST_EXE}) else() # Rules for normal build follow diff --git a/src/emscripten.js b/src/emscripten.js index 57a5860..8b12cfc 100644 --- a/src/emscripten.js +++ b/src/emscripten.js @@ -1,4 +1,5 @@ -Module['arguments']=[ - '--desktop','false', - '-d','/share/hatari/fs/', - '--machine','ste']; +Module['arguments'] = Module['arguments'] || [ + '--desktop', 'false', '--confirm-quit', 'false', '--statusbar', 'false', '--drive-led', 'false', + '--machine', 'st', '--memsize', '1024', '--tos', '/share/hatari/tos.img', + '--monitor', 'rgb', '--sound', 'off', + '-d', '/share/hatari/fs/', '--auto', 'C:\LASRCHES.PRG']; diff --git a/src/includes/lcweb.h b/src/includes/lcweb.h new file mode 100644 index 0000000..72610e0 --- /dev/null +++ b/src/includes/lcweb.h @@ -0,0 +1,19 @@ +/* + Laser Chess web front end glue (Emscripten only). +*/ +#ifndef HATARI_LCWEB_H +#define HATARI_LCWEB_H + +#include + +extern void LCWeb_Vbl(void); +extern void lcweb_set_input(int x, int y, int buttons); +extern uint32_t lcweb_linea(void); +extern uint8_t *lcweb_ram(void); +extern uint32_t lcweb_ram_size(void); +extern uint32_t lcweb_screen_base(void); +extern int lcweb_palette(int n); +extern int lcweb_shifter_mode(void); +extern uint32_t lcweb_ram_hash(void); + +#endif diff --git a/src/lcweb.c b/src/lcweb.c new file mode 100644 index 0000000..6609b11 --- /dev/null +++ b/src/lcweb.c @@ -0,0 +1,141 @@ +/* + Laser Chess web front end glue (Emscripten only). + + JavaScript owns the clock: at every emulated VBL the emulator yields to + Module.lcVbl(frame), which resolves when the next frame may run (local + pacing, or once both online players' inputs for that frame are known). + Input is an absolute ST pointer position plus button state; it is turned + into relative IKBD mouse packets here, from the GEM cursor position kept + in the Line-A variables, so both online peers derive identical packets + from identical emulated state. +*/ +#include "main.h" +#include "configuration.h" +#include "stMemory.h" +#include "ikbd.h" +#include "ioMem.h" +#include "video.h" +#include "lcweb.h" +#include + +static int in_x = 160, in_y = 100, in_buttons = 0; /* wanted pointer state */ +static int cur_buttons = 0; /* buttons as sent to the ST */ +static int hold_frames = 0; +static uint32_t linea = 0; +static uint32_t frame_no = 0; + +EM_ASYNC_JS(int, lc_js_vbl, (int frame), { + return await Module.lcVbl(frame); +}); + +EMSCRIPTEN_KEEPALIVE void lcweb_set_input(int x, int y, int buttons) +{ + in_x = x; + in_y = y; + in_buttons = buttons; +} + +EMSCRIPTEN_KEEPALIVE uint32_t lcweb_linea(void) +{ + return linea; +} + +EMSCRIPTEN_KEEPALIVE uint8_t *lcweb_ram(void) +{ + return STRam; +} + +EMSCRIPTEN_KEEPALIVE uint32_t lcweb_ram_size(void) +{ + return STRamEnd; +} + +EMSCRIPTEN_KEEPALIVE uint32_t lcweb_screen_base(void) +{ + return Video_GetScreenBaseAddr(); +} + +/* ST palette register n as 0x0RGB (STE 4-bit nibbles) */ +EMSCRIPTEN_KEEPALIVE int lcweb_palette(int n) +{ + return IoMem_ReadWord(0xff8240 + n * 2); +} + +EMSCRIPTEN_KEEPALIVE int lcweb_shifter_mode(void) +{ + return IoMem_ReadByte(0xff8260) & 3; +} + +/* FNV-1a over ST RAM, for desync checks between online peers */ +EMSCRIPTEN_KEEPALIVE uint32_t lcweb_ram_hash(void) +{ + uint32_t h = 2166136261u; + for (uint32_t i = 0; i < STRamEnd; i++) + { + h ^= STRam[i]; + h *= 16777619u; + } + return h; +} + +/* The Line-A variable block: PLANES at base, WIDTH at +2, BYTES_LIN at -2, + * V_REZ_VT at -4, V_REZ_HZ at -0xc. Look for the ST low-res values. */ +static void find_linea(void) +{ + for (uint32_t a = 0x400; a + 4 < STRamEnd && a < 0x40000; a += 2) + { + if (STMemory_ReadWord(a) == 4 && STMemory_ReadWord(a + 2) == 160 + && STMemory_ReadWord(a - 2) == 160 && STMemory_ReadWord(a - 4) == 200 + && STMemory_ReadWord(a - 0xc) == 320) + { + linea = a; + return; + } + } +} + +static int clamp(int v, int lo, int hi) +{ + return v < lo ? lo : v > hi ? hi : v; +} + +static void apply_input(void) +{ + int dx = 0, dy = 0, at = 1; + + if (!linea && frame_no > 50 && (frame_no % 25) == 0) + find_linea(); + if (linea) + { + int cx = (int16_t)STMemory_ReadWord(linea - 0x25a); + int cy = (int16_t)STMemory_ReadWord(linea - 0x258); + dx = clamp(in_x - cx, -60, 60); + dy = clamp(in_y - cy, -60, 60); + at = (dx == 0 && dy == 0); + } + KeyboardProcessor.Mouse.dx += dx; + KeyboardProcessor.Mouse.dy += dy; + + /* A button change waits until the cursor has arrived (at most 10 + * frames), so a click lands where it was made. */ + if (in_buttons != cur_buttons) + { + if (at || ++hold_frames > 10) + { + cur_buttons = in_buttons; + hold_frames = 0; + } + } + else + hold_frames = 0; + Keyboard.bLButtonDown = (Keyboard.bLButtonDown & ~BUTTON_MOUSE) | ((cur_buttons & 1) ? BUTTON_MOUSE : 0); + Keyboard.bRButtonDown = (Keyboard.bRButtonDown & ~BUTTON_MOUSE) | ((cur_buttons & 2) ? BUTTON_MOUSE : 0); +} + +/* Called from Main_WaitOnVbl in place of the host-clock wait. */ +void LCWeb_Vbl(void) +{ + frame_no++; + lc_js_vbl((int)frame_no); + apply_input(); +} diff --git a/src/main.c b/src/main.c index dacf9c3..d59b788 100644 --- a/src/main.c +++ b/src/main.c @@ -76,6 +76,7 @@ const char Main_fileid[] = "Hatari main.c"; #ifdef EMSCRIPTEN #include "emscripten.h" +#include "lcweb.h" #endif bool bQuitProgram = false; /* Flag to quit program cleanly */ @@ -355,6 +356,12 @@ void Main_WaitOnVbl(void) exit(0); } +#ifdef EMSCRIPTEN + /* Laser Chess web: JavaScript paces frames and feeds input */ + LCWeb_Vbl(); + return; +#endif + // FrameDuration_micro = (int64_t) ( 1000000.0 / nScreenRefreshRate + 0.5 ); /* round to closest integer */ FrameDuration_micro = ClocksTimings_GetVBLDuration_micro ( ConfigureParams.System.nMachineType , nScreenRefreshRate ); FrameDuration_micro *= nVBLSlowdown; @@ -1022,7 +1029,12 @@ void Main_ErrorExit(const char *msg1, const char *msg2, int errval) int main(int argc, char *argv[]) { /* Generate random seed */ +#ifdef EMSCRIPTEN + /* deterministic for online lockstep: the seed comes from the page */ + Hatari_srand(EM_ASM_INT({ return Module.lcSeed | 0; })); +#else Hatari_srand(time(NULL)); +#endif /* Setup for string conversions */ Str_Init();