From 52f4b377d32ce8edf480ed39c79ed28e2e3bb0e0 Mon Sep 17 00:00:00 2001 From: Chris Greening Date: Tue, 10 Dec 2024 17:41:23 +0000 Subject: [PATCH] Support for drag and drop in emscripten --- desktop/Makefile.ems | 13 +++++- desktop/src/loadgame.cpp | 4 +- desktop/src/loadgame.h | 2 +- desktop/src/main.mm | 80 +++++++++++++++++++++++++++++++++++++ desktop/src/shell.html | 84 +++++++++++++++++++++++++++++++++++++++ firmware/data/JetSet.tzx | Bin 0 -> 41069 bytes 6 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 desktop/src/shell.html create mode 100644 firmware/data/JetSet.tzx diff --git a/desktop/Makefile.ems b/desktop/Makefile.ems index 73bd131..6471ca7 100644 --- a/desktop/Makefile.ems +++ b/desktop/Makefile.ems @@ -16,7 +16,16 @@ CXXFLAGS = \ -I../firmware/src \ -D__DESKTOP__ \ -s USE_SDL=2 \ - -sSTACK_SIZE=200000 + -sSTACK_SIZE=200000 \ + --shell-file src/shell.html \ + -s "EXPORTED_RUNTIME_METHODS=['ccall','cwrap']" \ + -s EXPORTED_FUNCTIONS="['_main']" \ + -s DISABLE_EXCEPTION_CATCHING=0 \ + -s ALLOW_MEMORY_GROWTH=1 \ + -s NO_EXIT_RUNTIME=1 \ + -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'cwrap']" \ + -s WASM=1 \ + -lembind # Linker flags (including Cocoa framework for macOS file picker support) LDFLAGS = -L/usr/local/lib @@ -49,7 +58,7 @@ all: $(TARGET) # Create executable from object files $(TARGET): $(OBJS) Makefile - $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) `sdl2-config --libs` $(LDFLAGS) --embed-file filesystem + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) `sdl2-config --libs` $(LDFLAGS) --embed-file filesystem -lembind # Object file rules (object files are placed next to the source files) %.o: %.cpp Makefile diff --git a/desktop/src/loadgame.cpp b/desktop/src/loadgame.cpp index da122f1..5710d4b 100644 --- a/desktop/src/loadgame.cpp +++ b/desktop/src/loadgame.cpp @@ -24,10 +24,10 @@ void loadGame(const std::string& filename, ZXSpectrum* machine) { } std::vector buffer(std::istreambuf_iterator(file), {}); - loadGame(buffer.data(), buffer.size(), filename, machine); + loadTapeGame(buffer.data(), buffer.size(), filename, machine); } -void loadGame(uint8_t* tzx_data, size_t file_size, const std::string& filename, ZXSpectrum* machine) { +void loadTapeGame(uint8_t* tzx_data, size_t file_size, const std::string& filename, ZXSpectrum* machine) { // time how long it takes to load the tape int start = SDL_GetTicks(); // load the tape diff --git a/desktop/src/loadgame.h b/desktop/src/loadgame.h index c3821b2..4cd44f5 100644 --- a/desktop/src/loadgame.h +++ b/desktop/src/loadgame.h @@ -5,4 +5,4 @@ #include "spectrum.h" void loadGame(const std::string& filename, ZXSpectrum* machine); -void loadGame(uint8_t* data, size_t length, const std::string& filename, ZXSpectrum* machine); \ No newline at end of file +void loadTapeGame(uint8_t* data, size_t length, const std::string& filename, ZXSpectrum* machine); \ No newline at end of file diff --git a/desktop/src/main.mm b/desktop/src/main.mm index a790436..19f5695 100644 --- a/desktop/src/main.mm +++ b/desktop/src/main.mm @@ -1,6 +1,8 @@ #include #ifdef __EMSCRIPTEN__ #include +#include +#include #endif #ifndef __EMSCRIPTEN__ #import @@ -263,6 +265,84 @@ void main_loop() #endif } +#ifdef __EMSCRIPTEN__ +void loadDroppedFile(std::string filename, const emscripten::val& arrayBuffer) { + machine->reset(); + machine->init_spectrum(SPECMDL_48K); + machine->reset_spectrum(machine->z80Regs); + for(int i = 0; i < 200; i++) { + machine->runForFrame(nullptr, nullptr); + } + // we need to do load "" + machine->updateKey(SPECKEY_J, 1); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_J, 0); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_SYMB, 1); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_P, 1); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_P, 0); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_P, 1); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_P, 0); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_SYMB, 0); + // press enter + machine->updateKey(SPECKEY_ENTER, 1); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + machine->updateKey(SPECKEY_ENTER, 0); + for(int i = 0; i < 10; i++) { + machine->runForFrame(nullptr, nullptr); + } + + + // press the enter key to trigger tape loading + // machine->updateKey(SPECKEY_ENTER, 1); + // for(int i = 0; i < 10; i++) { + // machine->runForFrame(nullptr, nullptr); + // } + // machine->updateKey(SPECKEY_ENTER, 0); + // for(int i = 0; i < 10; i++) { + // machine->runForFrame(nullptr, nullptr); + // } + + size_t length = arrayBuffer["byteLength"].as(); + uint8_t* data = (uint8_t*)malloc(length); + + // Copy data from JS ArrayBuffer to C++ memory + emscripten::val memoryView = emscripten::val::global("Uint8Array").new_(arrayBuffer); + for (size_t i = 0; i < length; i++) { + data[i] = memoryView[i].as(); + } + isLoading = true; + loadGame(data, length, filename, machine); + isLoading = false; + free(data); +} + +EMSCRIPTEN_BINDINGS(module) { + emscripten::function("loadDroppedFile", &loadDroppedFile); +} +#endif + // Main function int main() { diff --git a/desktop/src/shell.html b/desktop/src/shell.html new file mode 100644 index 0000000..e341e97 --- /dev/null +++ b/desktop/src/shell.html @@ -0,0 +1,84 @@ + + + + + + ZX Spectrum Emulator + + + + +
Drop TAP, TZX, or Z80 file here
+ + {{{ SCRIPT }}} + + \ No newline at end of file diff --git a/firmware/data/JetSet.tzx b/firmware/data/JetSet.tzx new file mode 100644 index 0000000000000000000000000000000000000000..1210bdf73778fa67bda364aed5e6ede077f9e53f GIT binary patch literal 41069 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 C(xo~8 literal 0 HcmV?d00001