diff --git a/.gitignore b/.gitignore index 7aab984b..90427473 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ *.smdh *.bnr *.cia - +*.html *.nacp *.nro *.nso diff --git a/Makefile b/Makefile index 15d25ccf..12182aeb 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,9 @@ clean-miyoomini: clean-windows: @$(MAKE) -C platform/windows clean +clean-wasm: + @$(MAKE) -C platform/Webassembly clean + 3ds: @$(MAKE) -C platform/3ds @@ -75,6 +78,9 @@ vita: sdl2: @$(MAKE) -C platform/SDL2Desktop +wasm: + @$(MAKE) -C platform/Webassembly + sdl: @$(MAKE) -C platform/SDL1_2 diff --git a/README.md b/README.md index 63e53da0..a03ffd80 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Installation will vary by console and executable type. If it is a console with a Pico 8 cart files go in the `p8carts/` directory of your memory card (SD card on 3DS, Switch, and Wii U, memory card at `ux0:/` on Vita). `.p8` text file carts and `.p8.png` image file carts are supported. +The Standalone webpage is different. You use the '/' key to upload, the '.' key to save everything, and the '`' key to reset the filesystem (don't worry, it will ask you if you're sure you want to reset everything.) + Launch FAKE-08 either via the homebrew menu or normal system UI (depending on how you installed). Use left and right to cycle through carts on the SD card. Choose a cart using the `A` (Nintendo consoles) or `X`(Vita) button. To exit the currently running cart, press `Start` or `+` to open the pause menu and select `Exit to Menu`. Press `R` to cycle between rendering sizes. Press `L` and `R` simultaneously to exit the appication. You can also close it via your console's operating system controls (home button etc). For bittboy and similar consoles, back up `emus/pico8/pico8.elf` and replace it with the one from the release. Place your cart files in `roms/pico-8/` and use the front end of choice to launch games. Press the menu button to return to the menu (though you can also press start and exit to the FAKE-08 bios menu if you would like). @@ -42,6 +44,8 @@ Building for bittboy requires builing your own toolchain first (and will probabl Building for Miyoo mini uses shauninman's Union Miyoo Mini toolchain: https://github.com/shauninman/union-miyoomini-toolchain +Building the standalone webpage requires emsdk: The installation instructions are available at https://emscripten.org/docs/getting_started/downloads.html +When emsdk is installed (and sourced), use `emmake make wasm` to build. ## Acknowledgements * Zep/Lexaloffle software for making pico 8. Buy a copy if you can. You won't regret it. https://www.lexaloffle.com/pico-8.php * Nintendo Homebrew Community diff --git a/platform/SDL2Common/source/sdl2basehost.cpp b/platform/SDL2Common/source/sdl2basehost.cpp index 44fb7f8c..e3601793 100644 --- a/platform/SDL2Common/source/sdl2basehost.cpp +++ b/platform/SDL2Common/source/sdl2basehost.cpp @@ -186,7 +186,8 @@ void Host::setPlatformParams( void Host::oneTimeSetup(Audio* audio){ - if (SDL_Init(SDL_INIT_EVERYTHING) != 0) + //Haptic support is not being used and many devices are not compiled with haptic support + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER|SDL_INIT_JOYSTICK) != 0) { fprintf(stderr, "SDL could not initialize\n"); return; diff --git a/platform/Webassembly/Makefile b/platform/Webassembly/Makefile new file mode 100644 index 00000000..32bfae15 --- /dev/null +++ b/platform/Webassembly/Makefile @@ -0,0 +1,119 @@ + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing header files +# +#--------------------------------------------------------------------------------- +TARGET := FAKE08.html +BUILD := build +SOURCES := ${SOURCES} source +INCLUDES := ${INCLUDES} + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +CC = $(CXX) + +CFLAGS := -g -Wall -Wno-deprecated -ffunction-sections -std=c++17 -sUSE_SDL=2 \ + $(DEFINES) + +CFLAGS += $(INCLUDE) -DVER_STR=\"$(APP_VERSION)\" + +CXXFLAGS := $(CFLAGS) -fno-rtti +#-std=gnu++11 was used before... not sure of difference + +LIBS := -lSDL2 + +LDFLAGS := $(LIBS) -sSINGLE_FILE -sUSE_SDL=2 -sASYNCIFY -sINITIAL_MEMORY=100mb -sALLOW_MEMORY_GROWTH -lidbfs.js --pre-js $(CURDIR)/../pre.js -s EXPORTED_RUNTIME_METHODS=callMain -fsanitize=address --shell-file $(CURDIR)/../shell.htm -sNO_DISABLE_EXCEPTION_CATCHING -O3 + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET) $(OUTPUT) + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT) + +$(OUTPUT) : $(OFILES) + $(CC) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/platform/Webassembly/pre.js b/platform/Webassembly/pre.js new file mode 100644 index 00000000..70c982fe --- /dev/null +++ b/platform/Webassembly/pre.js @@ -0,0 +1,39 @@ +//current command in ascii decimal +let currentcmd = [0,0,0] +let currentfile = ""; +const sleep = ms => new Promise(r => setTimeout(r,ms)); +Module['print'] = function(text){console.log(text);} +Module['preRun'] = function() +{ + + function stdin(){return 10}; + var stdout = null; + var stderr = null; + FS.init(stdin,stdout,stderr); + FS.mount(IDBFS,{},"/home/web_user/"); + FS.chdir("/home/web_user"); + +} +Module['noInitialRun'] = true +document.addEventListener('click', (ev) => { + console.log("event is captured only once."); + args = [] + document.getElementById("instructions").remove(); + FS.syncfs(true,function(){ + try { + FS.mkdir("/home/web_user/p8carts") + } catch (error) { + + } + try { + FS.mkdir("/home/web_user/fake08") + } catch (error) { + + } + + Module.callMain(args); +}); + + }, { once: true }); + + \ No newline at end of file diff --git a/platform/Webassembly/shell.htm b/platform/Webassembly/shell.htm new file mode 100644 index 00000000..153d9f1d --- /dev/null +++ b/platform/Webassembly/shell.htm @@ -0,0 +1,65 @@ + + + + + + + Fake-08 + + + + + + +

Once the page fully loads, click on the page to start!

+ + + + + + {{{ SCRIPT }}} + + + + \ No newline at end of file diff --git a/platform/Webassembly/source/WASMHost.cpp b/platform/Webassembly/source/WASMHost.cpp new file mode 100644 index 00000000..c4bcf690 --- /dev/null +++ b/platform/Webassembly/source/WASMHost.cpp @@ -0,0 +1,247 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +using namespace std; + +#include "../../SDL2Common/source/sdl2basehost.h" +#include "../../../source/hostVmShared.h" +#include "../../../source/nibblehelpers.h" +#include "../../../source/filehelpers.h" +#include "../../../source/logger.h" + +#include "../../../source/emojiconversion.h" + +// sdl +#include +#include + +/*#define WINDOW_SIZE_X 1280 +#define WINDOW_SIZE_Y 720*/ + +#define WINDOW_FLAGS 0 + +#define RENDERER_FLAGS SDL_RENDERER_ACCELERATED +#define PIXEL_FORMAT SDL_PIXELFORMAT_ARGB8888 + +#define KB_ENABLED true + +SDL_Event event; + + +string _desktopSdl2SettingsDir = "/home/web_user/fake08"; +string _desktopSdl2SettingsPrefix = "/home/web_user/fake08/"; +string _desktopSdl2customBiosLua = "cartpath = \"~/p8carts/\"\n" + "selectbtn = \"z\"\n" + "pausebtn = \"esc\"\n" + "exitbtn = \"close tab\"\n" + "sizebtn = \"\""; + +Host::Host() +{ + + SDL_DisplayMode current; + + SDL_Init(SDL_INIT_VIDEO); + + int should_be_zero = SDL_GetCurrentDisplayMode(0, ¤t); + + if(should_be_zero != 0) { + // In case of error... + SDL_Log("Could not get display mode for video display #%d: %s", 0, SDL_GetError()); + } + + else { + // On success, print the current display mode. + SDL_Log("Display #%d: current display mode is %dx%dpx @ %dhz.", 0, current.w, current.h, current.refresh_rate); + } + + int WINDOW_SIZE_X=current.w; + int WINDOW_SIZE_Y=current.h; + + struct stat st = {0}; + + int res = chdir(getenv("HOME")); + if (res == 0 && stat(_desktopSdl2SettingsDir.c_str(), &st) == -1) { + res = mkdir(_desktopSdl2SettingsDir.c_str(), 0777); + } + + string cartdatadir = _desktopSdl2SettingsPrefix + "cdata"; + if (res == 0 && stat(cartdatadir.c_str(), &st) == -1) { + res = mkdir(cartdatadir.c_str(), 0777); + } + + #if KB_ENABLED + SDL_StartTextInput(); + #endif + + std::string home = getenv("HOME"); + + std::string fullCartDir = home + "/p8carts"; + + setPlatformParams( + WINDOW_SIZE_X, + WINDOW_SIZE_Y, + WINDOW_FLAGS, + RENDERER_FLAGS, + PIXEL_FORMAT, + _desktopSdl2SettingsPrefix, + _desktopSdl2customBiosLua, + fullCartDir + ); +} + + +InputState_t Host::scanInput(){ + currKDown = 0; + uint8_t kUp = 0; + stretchKeyPressed = false; + + currKBDown = false; + currKBKey = ""; + + while (SDL_PollEvent(&event)) { + switch (event.type) { + + #if KB_ENABLED + case SDL_TEXTINPUT: + //Logger_Write( charset::upper_to_emoji(event.text.text).c_str() ); + //Logger_Write("\n"); + currKBKey = charset::upper_to_emoji(event.text.text); + currKBDown = true; + + break; + #endif + + + case SDL_KEYDOWN: + + #if KB_ENABLED + switch (event.key.keysym.scancode) + { + case SDL_SCANCODE_BACKSPACE: currKBDown = true; currKBKey = "\b"; break; + case SDL_SCANCODE_RETURN: currKBDown = true; currKBKey = "\r"; break; + case SDL_SCANCODE_ESCAPE: currKBDown = true; currKBKey = "\27"; break; + case SDL_SCANCODE_TAB: currKBDown = true; currKBKey = "\t"; break; + default : break; + } + #endif + + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE:case SDLK_RETURN:case SDLK_RETURN2: + currKDown |= P8_KEY_PAUSE; break; + case SDLK_LEFT: currKDown |= P8_KEY_LEFT; break; + case SDLK_RIGHT: currKDown |= P8_KEY_RIGHT; break; + case SDLK_UP: currKDown |= P8_KEY_UP; break; + case SDLK_DOWN: currKDown |= P8_KEY_DOWN; break; + case SDLK_z: currKDown |= P8_KEY_X; break; + case SDLK_x: currKDown |= P8_KEY_O; break; + case SDLK_c: currKDown |= P8_KEY_X; break; + case SDLK_r: stretchKeyPressed = true; break; + case SDLK_F2: currKDown |= P8_KEY_7; break; + #ifndef NOUPLOAD + case SDLK_SLASH: EM_ASM(cuurentdir = "p8carts"; file_selector.click();); break; + case SDLK_PERIOD: EM_ASM(FS.syncfs(false,function(err) { + if(err) + { + alert("FileSystem Save Error: " + err); + return false; + } + + alert("Filesystem Saved!"); + return true; + }));break; + case SDLK_BACKQUOTE: EM_ASM(if(confirm("Do you want to erase all carts?")){ + FS.syncfs(true, function(){ + FS.syncfs(false,function(){}); + }) + }); + #endif + //case SDLK_F2: currKBKey = "F2"; currKBDown = true; break; + //case SDLK_F4: currKBKey = "F4"; currKBDown = true; break; + } + break; + + case SDL_KEYUP: + switch (event.key.keysym.sym) + { + case SDLK_ESCAPE:case SDLK_RETURN:case SDLK_RETURN2: + kUp |= P8_KEY_PAUSE; break; + case SDLK_LEFT: kUp |= P8_KEY_LEFT; break; + case SDLK_RIGHT: kUp |= P8_KEY_RIGHT; break; + case SDLK_UP: kUp |= P8_KEY_UP; break; + case SDLK_DOWN: kUp |= P8_KEY_DOWN; break; + case SDLK_z: kUp |= P8_KEY_X; break; + case SDLK_x: kUp |= P8_KEY_O; break; + case SDLK_c: kUp |= P8_KEY_X; break; + } + break; + + case SDL_QUIT: + quit = 1; + break; + } + } + + int mouseX = 0; + int mouseY = 0; + uint32_t sdlMouseBtnState = SDL_GetMouseState(&mouseX, &mouseY); + //adjust for scale + mouseX -= mouseOffsetX; + mouseY -= mouseOffsetY; + mouseX /= scaleX; + mouseY /= scaleY; + uint8_t picoMouseState = 0; + if (sdlMouseBtnState & SDL_BUTTON(SDL_BUTTON_LEFT)) { + picoMouseState |= 1; + } + if (sdlMouseBtnState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) { + picoMouseState |= 4; + } + if (sdlMouseBtnState & SDL_BUTTON(SDL_BUTTON_RIGHT)) { + picoMouseState |= 2; + } + + currKHeld |= currKDown; + currKHeld ^= kUp; + + return InputState_t { + currKDown, + currKHeld, + (int16_t)mouseX, + (int16_t)mouseY, + picoMouseState, + currKBDown, + currKBKey + }; +} + +vector Host::listcarts(){ + vector carts; + + DIR *dir; + struct dirent *ent; + if ((dir = opendir (_cartDirectory.c_str())) != NULL) { + /* print all the files and directories within directory */ + while ((ent = readdir (dir)) != NULL) { + if (isCartFile(ent->d_name)){ + carts.push_back(ent->d_name); + } + } + closedir (dir); + } else { + /* could not open directory */ + perror (""); + } + + return carts; +} + diff --git a/platform/Webassembly/source/sdl2basehost.cpp b/platform/Webassembly/source/sdl2basehost.cpp new file mode 100644 index 00000000..46b60deb --- /dev/null +++ b/platform/Webassembly/source/sdl2basehost.cpp @@ -0,0 +1,453 @@ + +#include +#include +#include +#include +#include + +#include +#include +using namespace std; + +#include "sdl2basehost.h" +#include "../../../source/hostVmShared.h" +#include "../../../source/nibblehelpers.h" +#include "../../../source/logger.h" +#include "../../../source/filehelpers.h" + +// sdl +#include + +#define SAMPLERATE 22050 +#define SAMPLESPERBUF (SAMPLERATE / 30) +#define NUM_BUFFERS 2 + +int _windowWidth = 128; +int _windowHeight = 128; + + +int _screenWidth = 128; +int _screenHeight = 128; + +int _maxNoStretchWidth = 128; +int _maxNoStretchHeight = 128; + +const int PicoScreenWidth = 128; +const int PicoScreenHeight = 128; + +uint32_t _windowFlags; +uint32_t _rendererFlags; +uint32_t _pixelFormat; + +uint32_t last_time; +uint32_t now_time; +uint32_t frame_time; +uint32_t targetFrameTimeMs; + +uint8_t currKDown; +uint8_t currKHeld; +bool stretchKeyPressed = false; + +Audio* _audio; + +SDL_Window* window; +SDL_Renderer *renderer; +SDL_Texture *texture = NULL; +SDL_AudioSpec want, have; +SDL_AudioDeviceID dev; +void *pixels; +uint8_t *base; +int pitch; + +SDL_Rect DestR; +SDL_Rect SrcR; +double textureAngle; +SDL_RendererFlip flip; + +int joystickCount = 0; + +bool audioInitialized = false; + + +void postFlipFunction(){ + // We're done rendering, so we end the frame here. + SDL_UnlockTexture(texture); + SDL_RenderCopyEx(renderer, texture, &SrcR, &DestR, textureAngle, NULL, flip); + + SDL_RenderPresent(renderer); +} + +void audioCleanup(){ + audioInitialized = false; + + SDL_CloseAudioDevice(dev); +} + + +void FillAudioDeviceBuffer(void* UserData, Uint8* DeviceBuffer, int Length) +{ + _audio->FillAudioBuffer(DeviceBuffer, 0, Length / 4); +} + +void audioSetup(){ + //modifed from SDL docs: https://wiki.libsdl.org/SDL_OpenAudioDevice + + SDL_memset(&want, 0, sizeof(want)); /* or SDL_zero(want) */ + want.freq = SAMPLERATE; + want.format = AUDIO_S16; + want.channels = 2; + want.samples = 4096; + want.callback = FillAudioDeviceBuffer; + + + dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (dev == 0) { + Logger_Write("Failed to open audio: %s", SDL_GetError()); + } else { + if (have.format != want.format) { /* we let this one thing change. */ + Logger_Write("We didn't get requested audio format."); + } + SDL_PauseAudioDevice(dev, 0); /* start audio playing. */ + audioInitialized = true; + } +} + +void _changeStretch(StretchOption newStretch){ + if (newStretch == PixelPerfect) { + _screenWidth = PicoScreenWidth; + _screenHeight = PicoScreenHeight; + } + else if (newStretch == StretchToFit) { + _screenWidth = _windowHeight; + _screenHeight = _windowHeight; + } + else if (newStretch == StretchToFill){ + _screenWidth = _windowWidth; + _screenHeight = _windowHeight; + } + else if (newStretch == PixelPerfectStretch) { + _screenWidth = _maxNoStretchWidth; + _screenHeight = _maxNoStretchHeight; + } + else if (newStretch == FourByThreeVertPerfect) { + _screenWidth = _maxNoStretchHeight * 4 / 3; + _screenHeight = _maxNoStretchHeight; + } + else if (newStretch == FourByThreeStretch) { + _screenWidth = _windowHeight * 4 / 3; + _screenHeight = _windowHeight; + } + + + DestR.x = _windowWidth / 2 - _screenWidth / 2; + DestR.y = _windowHeight / 2 - _screenHeight / 2; + DestR.w = _screenWidth; + DestR.h = _screenHeight; + + SrcR.x = 0; + SrcR.y = 0; + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + + textureAngle = 0; + flip = SDL_FLIP_NONE; +} + +void Host::setPlatformParams( + int windowWidth, + int windowHeight, + uint32_t sdlWindowFlags, + uint32_t sdlRendererFlags, + uint32_t sdlPixelFormat, + std::string logFilePrefix, + std::string customBiosLua, + std::string cartDirectory) +{ + _windowWidth = windowWidth; + _windowHeight = windowHeight; + + //assume wide screen, height is limiting factor + int maxNoStretchFactor = _windowHeight / PicoScreenHeight; + + _maxNoStretchWidth = maxNoStretchFactor * PicoScreenWidth; + _maxNoStretchHeight = maxNoStretchFactor * PicoScreenHeight; + + _screenWidth = _maxNoStretchWidth; + _screenHeight = _maxNoStretchHeight; + + _windowFlags = sdlWindowFlags; + _rendererFlags = sdlRendererFlags; + _pixelFormat = sdlPixelFormat; + + _logFilePrefix = logFilePrefix; + _customBiosLua = customBiosLua; + _cartDirectory = cartDirectory; + +} + + +void Host::oneTimeSetup(Audio* audio){ + //Haptic support is not being used and many devices are not compiled with haptic support + if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_TIMER|SDL_INIT_JOYSTICK) != 0) + { + fprintf(stderr, "SDL could not initialize\n"); + return; + } + + window = SDL_CreateWindow("FAKE-08", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, _windowWidth, _windowHeight, _windowFlags); + if (!window) + { + quit = 1; + return; + } + + renderer = SDL_CreateRenderer(window, -1, _rendererFlags); + if (!renderer) + { + quit = 1; + return; + } + + texture = SDL_CreateTexture(renderer, _pixelFormat, SDL_TEXTUREACCESS_STREAMING, PicoScreenWidth, PicoScreenHeight); + if (!texture) + { + fprintf(stderr, "Error creating texture.\n"); + quit = 1; + return; + } + + atexit(SDL_Quit); + + _audio = audio; + audioSetup(); + + joystickCount = SDL_NumJoysticks(); + for (int i = 0; i < joystickCount; i++) { + if (SDL_JoystickOpen(i) == NULL) { + printf("Failed to open joystick %d!\n", i); + quit = 1; + } + } + + last_time = 0; + now_time = 0; + frame_time = 0; + targetFrameTimeMs = 0; + + currKDown = 0; + currKHeld = 0; + + loadSettingsIni(); + + _changeStretch(stretch); + + scaleX = _screenWidth / (float)PicoScreenWidth; + scaleY = _screenHeight / (float)PicoScreenHeight; + mouseOffsetX = DestR.x; + mouseOffsetY = DestR.y; +} + +void Host::oneTimeCleanup(){ + saveSettingsIni(); + + audioCleanup(); + + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); +} + +void Host::setTargetFps(int targetFps){ + targetFrameTimeMs = 1000 / targetFps; +} + +void Host::changeStretch(){ + if (stretchKeyPressed && resizekey == YesResize) { + StretchOption newStretch = stretch; + + if (stretch == PixelPerfectStretch) { + newStretch = PixelPerfect; + } + else if (stretch == PixelPerfect) { + newStretch = StretchToFit; + } + else if (stretch == StretchToFit) { + newStretch = StretchToFill; + } + else if (stretch == StretchToFill) { + newStretch = FourByThreeVertPerfect; + } + else if (stretch == FourByThreeVertPerfect) { + newStretch = FourByThreeStretch; + } + else if (stretch == FourByThreeStretch) { + newStretch = PixelPerfectStretch; + } + + _changeStretch(newStretch); + + stretch = newStretch; + scaleX = _screenWidth / (float)PicoScreenWidth; + scaleY = _screenHeight / (float)PicoScreenHeight; + mouseOffsetX = DestR.x; + mouseOffsetY = DestR.y; + } +} + +void Host::forceStretch(StretchOption newStretch) { + _changeStretch(newStretch); + stretch = newStretch; + scaleX = _screenWidth / (float)PicoScreenWidth; + scaleY = _screenHeight / (float)PicoScreenHeight; + mouseOffsetX = DestR.x; + mouseOffsetY = DestR.y; +} + +bool Host::shouldQuit() { + return quit == 1; +} + +void Host::waitForTargetFps(){ + now_time = SDL_GetTicks(); + frame_time = now_time - last_time; + last_time = now_time; + + + //sleep for remainder of time + if (frame_time < targetFrameTimeMs) { + uint32_t msToSleep = targetFrameTimeMs - frame_time; + + emscripten_sleep(msToSleep); + + last_time += msToSleep; + } +} + + +void Host::drawFrame(uint8_t* picoFb, uint8_t* screenPaletteMap, uint8_t drawMode){ + //clear screen to all black + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); + SDL_RenderClear(renderer); + + SDL_LockTexture(texture, NULL, &pixels, &pitch); + + for (int y = 0; y < PicoScreenHeight; y ++){ + for (int x = 0; x < PicoScreenWidth; x ++){ + uint8_t c = getPixelNibble(x, y, picoFb); + Color col = _paletteColors[screenPaletteMap[c]]; + + base = ((Uint8 *)pixels) + (4 * ( y * PicoScreenHeight + x)); + base[0] = col.Blue; + base[1] = col.Green; + base[2] = col.Red; + base[3] = col.Alpha; + } + } + + SrcR.x = 0; + SrcR.y = 0; + + switch(drawMode){ + case 1: + SrcR.w = 64; + SrcR.h = PicoScreenHeight; + textureAngle = 0; + flip = SDL_FLIP_NONE; + break; + case 2: + SrcR.w = PicoScreenWidth; + SrcR.h = 64; + textureAngle = 0; + flip = SDL_FLIP_NONE; + break; + case 3: + SrcR.w = 64; + SrcR.h = 64; + textureAngle = 0; + flip = SDL_FLIP_NONE; + break; + //todo: mirroring + //case 4,6,7 + case 129: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 0; + flip = SDL_FLIP_HORIZONTAL; + break; + case 130: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 0; + flip = SDL_FLIP_VERTICAL; + break; + case 131: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 0; + flip = (SDL_RendererFlip)(SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL); + break; + case 133: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 90; + flip = SDL_FLIP_NONE; + break; + case 134: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 180; + flip = SDL_FLIP_NONE; + break; + case 135: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 270; + flip = SDL_FLIP_NONE; + break; + default: + SrcR.w = PicoScreenWidth; + SrcR.h = PicoScreenHeight; + textureAngle = 0; + flip = SDL_FLIP_NONE; + break; + } + + + postFlipFunction(); +} + +bool Host::shouldFillAudioBuff(){ + return false; +} + +void* Host::getAudioBufferPointer(){ + return nullptr; +} + +size_t Host::getAudioBufferSize(){ + return 0; +} + +void Host::playFilledAudioBuffer(){ +} + +bool Host::shouldRunMainLoop(){ + if (shouldQuit()){ + return false; + } + + return true; +} + +const char* Host::logFilePrefix() { + return _logFilePrefix.c_str(); +} + +std::string Host::customBiosLua() { + return _customBiosLua; +} + +std::string Host::getCartDirectory() { + return _cartDirectory; +} diff --git a/platform/Webassembly/source/sdl2basehost.h b/platform/Webassembly/source/sdl2basehost.h new file mode 100644 index 00000000..0f08b91f --- /dev/null +++ b/platform/Webassembly/source/sdl2basehost.h @@ -0,0 +1,8 @@ +#pragma once + +#include "../../../source/host.h" + +class SDL2BaseHost : public Host { + + +}; \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index 6fffa7a4..2b6cd93c 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -17,7 +17,6 @@ #endif - int main(int argc, char* argv[]) { Host *host = new Host(); @@ -70,9 +69,7 @@ int main(int argc, char* argv[]) // Main loop Logger_Write("Starting main loop\n"); - vm->GameLoop(); - Logger_Write("Turning off vm and exiting logger\n"); vm->CloseCart(); diff --git a/source/picoluaapi.cpp b/source/picoluaapi.cpp index a7204812..a0c008fe 100644 --- a/source/picoluaapi.cpp +++ b/source/picoluaapi.cpp @@ -17,6 +17,10 @@ using namespace std; #include //} +#ifdef __EMSCRIPTEN__ +#include +#endif + Graphics* _graphicsForLuaApi; Input* _inputForLuaApi; Vm* _vmForLuaApi; @@ -1187,11 +1191,21 @@ int dget(lua_State *L) { } int dset(lua_State *L) { + int dest = lua_tonumber(L,1); fix32 val = lua_tonumber(L,2); _vmForLuaApi->vm_dset(dest, val); + EM_ASM(FS.syncfs(false,function(err) { + if(err) + { + alert("FileSystem Save Error: " + err); + return false; + } + return true; + })); + return 0; } diff --git a/source/vm.cpp b/source/vm.cpp index 36316293..d24c3277 100644 --- a/source/vm.cpp +++ b/source/vm.cpp @@ -32,6 +32,7 @@ #include //} + using namespace z8; static const char BiosCartName[] = "__FAKE08-BIOS.p8"; @@ -689,15 +690,19 @@ string Vm::GetBiosError() { } void Vm::GameLoop() { + while (_host->shouldRunMainLoop()) { + //shouldn't need to set this every frame _host->setTargetFps(_targetFps); //is this better at the end of the loop? _host->waitForTargetFps(); + if (_host->shouldQuit()) break; // break in order to return to hbmenu + //this should probably be handled just in the host class _host->changeStretch(); @@ -716,7 +721,9 @@ void Vm::GameLoop() { _host->playFilledAudioBuffer(); } + } + } bool Vm::ExecuteLua(string luaString, string callbackFunction){ diff --git a/test/doctest.h b/test/doctest.h index d25f5268..f6ec0664 100644 --- a/test/doctest.h +++ b/test/doctest.h @@ -265,8 +265,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(4623) // default constructor was implicitly define #undef DOCTEST_CONFIG_WINDOWS_SEH #endif // DOCTEST_CONFIG_NO_WINDOWS_SEH -#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && \ - !defined(__EMSCRIPTEN__) +#if !defined(_WIN32) && !defined(__QNX__) && !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(__EMSCRIPTEN__) #define DOCTEST_CONFIG_POSIX_SIGNALS #endif // _WIN32 #if defined(DOCTEST_CONFIG_NO_POSIX_SIGNALS) && defined(DOCTEST_CONFIG_POSIX_SIGNALS)