diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index 7a1519ab5..a502a579d 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -90,4 +90,55 @@ jobs: uses: actions/upload-artifact@v7 with: name: butterscotch-android - path: src/android/frontend/app/build/outputs/apk/debug/app-debug.apk \ No newline at end of file + path: src/android/frontend/app/build/outputs/apk/debug/app-debug.apk + + build-webos: + name: Build (webOS) + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v7 + + - name: Set up webOS toolchain + run: | + curl -Lo native-toolchain.tar.gz https://github.com/openlgtv/buildroot-nc4/releases/latest/download/arm-webos-linux-gnueabi_sdk-buildroot-x86_64.tar.gz + sudo tar xzf native-toolchain.tar.gz -C /opt/ + + PLATFORM=linux-x86_64 + TAG=$(curl -fsSL https://api.github.com/repos/webosbrew/ares-cli-rs/releases/latest | grep '"tag_name"' | cut -d '"' -f 4) + curl -fsSL "https://github.com/webosbrew/ares-cli-rs/releases/download/$TAG/ares-cli-rs-$TAG-$PLATFORM.tar.gz" | tar xz + sudo install -m 755 "ares-cli-rs-$TAG-$PLATFORM"/ares-* /usr/local/bin/ + + - name: Install ccache + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ccache + + - uses: actions/cache@v6 + with: + path: ~/.cache/ccache + key: ccache-webos-${{ github.sha }} + restore-keys: | + ccache-webos- + + - name: Build Butterscotch + run: | + cmake -B build -G Ninja \ + -DWERROR=ON -DPLATFORM=webos \ + -DCMAKE_TOOLCHAIN_FILE=/opt/arm-webos-linux-gnueabi_sdk-buildroot/share/buildroot/toolchainfile.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + cmake --build build + + - name: Package IPK + run: | + mkdir -p dist/ + cp build/butterscotch dist/ + cp src/webos/dist/appinfo.json dist/ + cp src/webos/dist/icon.png dist/ + ares-package dist/ -o dist/ + + - name: Upload IPK + uses: actions/upload-artifact@v7 + with: + name: butterscotch-webos + path: dist/*.ipk \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index f0081f368..db9ae1f9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,8 @@ endif() set(BACKEND "sdl2") +elseif(PLATFORM STREQUAL "webos") + set(BACKEND "sdl2") endif() project(butterscotch C) @@ -121,7 +123,7 @@ option(ENABLE_WAD14 "Enable support for WAD Version 14 and below" ON) option(ENABLE_WAD16 "Enable support for WAD Version 15/16" ON) option(ENABLE_WAD17 "Enable support for WAD Version 17" ON) option(ENABLE_LEGACY_GL "Enable the legacy OpenGL renderer" ON) -if(PLATFORM STREQUAL "switch") +if(PLATFORM STREQUAL "switch" OR PLATFORM STREQUAL "webos") set(ENABLE_LEGACY_GL OFF CACHE BOOL "Enable the legacy OpenGL renderer" FORCE) endif() option(ENABLE_MODERN_GL "Enable the modern OpenGL renderer" ON) @@ -260,8 +262,13 @@ if(ENABLE_VM_EXCEPTIONS_LOGS) add_compile_definitions(ENABLE_VM_EXCEPTIONS_LOGS) endif() -if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "switch") - if(PLATFORM STREQUAL "vita") +if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "switch" OR PLATFORM STREQUAL "webos") + if(PLATFORM STREQUAL "webos") + set(VM_GML_PROFILER_DEFAULT OFF) + set(VM_TRACING_DEFAULT OFF) + set(VM_OPCODE_PROFILER_DEFAULT OFF) + set(VM_STUB_LOGS_DEFAULT OFF) + elseif(PLATFORM STREQUAL "vita") # Normally, __VITA__ should already be defined, but for some reason, # in my case, this didn't really work... so just in case, when we're # building for the Vita, just force-feed these... diff --git a/README.md b/README.md index 804e0da61..520fb4ed8 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ Of course, there are exceptions that break game compatibility altogether: * PlayStation 3 * PlayStation Vita * Nintendo Switch +* LG webOS * ...and maybe more in the future! Additionally, any platform with reasonably complete C and POSIX conformance should work, the following have been tested. @@ -120,7 +121,8 @@ All RISC architecture (ARM, MIPS, PowerPC, RISC-V) builds require hardware float | Platform | Download | Notes | | -------- | -------- | ----- | | WebAssembly | [butterscotch-web.zip](https://nightly.link/ButterscotchRunner/Butterscotch/workflows/build/main/butterscotch-web.zip) | [Play online](https://butterscotch.mrpowergamerbr.com/web/) | -| Android | [butterscotch-android.zip](https://nightly.link/ButterscotchRunner/Butterscotch/workflows/build/main/butterscotch-android.zip) | +| Android | [butterscotch-android.zip](https://nightly.link/ButterscotchRunner/Butterscotch/workflows/build/main/butterscotch-android.zip) | +| webOS | [butterscotch-webos.zip](https://nightly.link/ButterscotchRunner/Butterscotch/workflows/build/main/butterscotch-webos.zip) | After installing the IPK, place the game's `data.win` and other required files in `/media/developer/apps/usr/palm/applications/com.mrpowergamerbr.butterscotch/` | ## Community Ports diff --git a/src/input_recording.c b/src/input_recording.c index 61bd63137..b2601da59 100644 --- a/src/input_recording.c +++ b/src/input_recording.c @@ -228,7 +228,13 @@ bool InputRecording_save(InputRecording* recording) { } const char* output = JsonWriter_getOutput(&w); - fwrite(output, 1, JsonWriter_getLength(&w), file); + size_t len = JsonWriter_getLength(&w); + if (fwrite(output, 1, len, file) != len) { + logWarn("Error: Could not write input recording to '%s'\n", recording->recordFilePath); + JsonWriter_free(&w); + fclose(file); + return false; + } fputc('\n', file); fclose(file); diff --git a/src/loop.c b/src/loop.c index f9b44f669..32729edec 100644 --- a/src/loop.c +++ b/src/loop.c @@ -1081,25 +1081,34 @@ int loop(CommandLineArgs args, const char *argv0) { logDebug("Dumping runner state at frame %d\n", runner->frameCount); char* json = Runner_dumpStateJson(runner); - if (args.dumpJsonFilePattern != nullptr) { - char filename[512]; - snprintf(filename, sizeof(filename), args.dumpJsonFilePattern, runner->frameCount); - FILE* f = fopen(filename, "wb"); - if (f != nullptr) { - fwrite(json, 1, strlen(json), f); - fputc('\n', f); - fclose(f); - logInfo("JSON dump saved: %s\n", filename); + if (json != nullptr) { + if (args.dumpJsonFilePattern != nullptr) { + char filename[512]; + snprintf(filename, sizeof(filename), args.dumpJsonFilePattern, runner->frameCount); + FILE* f = fopen(filename, "wb"); + + if (f != nullptr) { + size_t len = strlen(json); + if (fwrite(json, 1, len, f) != len) { + logWarn("Error: Could not write JSON dump to '%s'\n", filename); + fclose(f); + } else { + fputc('\n', f); + fclose(f); + logInfo("JSON dump saved: %s\n", filename); + } + } else { + logWarn("Could not write JSON dump to '%s'\n", filename); + } } else { - logWarn("Could not write JSON dump to '%s'\n", filename); + logInfo("%s\n", json); } - } else { - logInfo("%s\n", json); - } - free(json); + free(json); + } } + // Toggle the collision mask debug overlay if (RunnerKeyboard_checkPressed(runner->keyboard, VK_F2)) { debugShowCollisionMasks = !debugShowCollisionMasks; @@ -1195,22 +1204,31 @@ int loop(CommandLineArgs args, const char *argv0) { // Dump runner state as JSON if this frame was requested if (hmget(args.dumpJsonFrames, runner->frameCount)) { char* json = Runner_dumpStateJson(runner); - if (args.dumpJsonFilePattern != nullptr) { - char filename[512]; - snprintf(filename, sizeof(filename), args.dumpJsonFilePattern, runner->frameCount); - FILE* f = fopen(filename, "wb"); - if (f != nullptr) { - fwrite(json, 1, strlen(json), f); - fputc('\n', f); - fclose(f); - logInfo("JSON dump saved: %s\n", filename); + + if (json != nullptr) { + if (args.dumpJsonFilePattern != nullptr) { + char filename[512]; + snprintf(filename, sizeof(filename), args.dumpJsonFilePattern, runner->frameCount); + FILE* f = fopen(filename, "wb"); + + if (f != nullptr) { + size_t len = strlen(json); + if (fwrite(json, 1, len, f) != len) { + logWarn("Error: Could not write JSON dump to '%s'\n", filename); + fclose(f); + } else { + fputc('\n', f); + fclose(f); + logInfo("JSON dump saved: %s\n", filename); + } + } else { + logWarn("Could not write JSON dump to '%s'\n", filename); + } } else { - logWarn("Could not write JSON dump to '%s'\n", filename); + logInfo("%s\n", json); } - } else { - logInfo("%s\n", json); + free(json); } - free(json); } // Query actual framebuffer size diff --git a/src/webos/dist/appinfo.json b/src/webos/dist/appinfo.json new file mode 100644 index 000000000..be393cdc9 --- /dev/null +++ b/src/webos/dist/appinfo.json @@ -0,0 +1,9 @@ +{ + "id": "com.mrpowergamerbr.butterscotch", + "version": "1.0.0", + "vendor": "Perfect Dreams", + "type": "native", + "main": "butterscotch", + "title": "Butterscotch", + "icon": "icon.png" +} diff --git a/src/webos/dist/icon.png b/src/webos/dist/icon.png new file mode 100644 index 000000000..9ab77c40f Binary files /dev/null and b/src/webos/dist/icon.png differ diff --git a/src/webos/glibc_compat.c b/src/webos/glibc_compat.c new file mode 100644 index 000000000..3a1f82ede --- /dev/null +++ b/src/webos/glibc_compat.c @@ -0,0 +1,15 @@ +#include +#include + +extern int __xstat(int ver, const char *path, struct stat *buf); +extern int __fxstat(int ver, int fd, struct stat *buf); + +int stat(const char *path, struct stat *buf) +{ + return __xstat(0, path, buf); +} + +int fstat(int fd, struct stat *buf) +{ + return __fxstat(0, fd, buf); +} diff --git a/src/webos/log.c b/src/webos/log.c new file mode 100644 index 000000000..566a78428 --- /dev/null +++ b/src/webos/log.c @@ -0,0 +1,25 @@ +#include + +#include "log.h" + +void platformLog(const logType type, const char *format, va_list va) +{ + FILE *out = stderr; + + switch (type) { + case LOG_TYPE_NORMAL: + out = stdout; + break; + case LOG_TYPE_WARNING: + fputs("Warning: ", out); + break; + case LOG_TYPE_ERROR: + fputs("Error: ", out); + break; + case LOG_TYPE_DEBUG: + fputs("Debug: ", out); + break; + } + + vfprintf(out, format, va); +} diff --git a/src/webos/main.c b/src/webos/main.c new file mode 100644 index 000000000..55d0e4022 --- /dev/null +++ b/src/webos/main.c @@ -0,0 +1,36 @@ +#include + +#include "platformdefs.h" + +int main(int argc, char **argv) +{ + (void)argc; + + CommandLineArgs args = {0}; + + args.exitAtFrame = -1; + +#ifdef ENABLE_VM_TRACING + args.traceBytecodeAfterFrame = 0; +#endif + + args.speedMultiplier = 1.0; + args.fastForwardSpeed = 0.0; + + /* + * webOS native apps receive a JSON launch object in argv[1]. + * It is not a data.win path, so deliberately ignore it here. + */ + args.osType = OS_WINDOWS; + args.profilerFramesBetween = 0; + args.loadType = DATAWINLOADTYPE_LOAD_IN_MEMORY_AHEAD_OF_TIME; + args.renderer = MODERN_GL; + + args.dataWinPath = "./data.win"; + args.saveFolder = "./saves"; + + int ret = loop(args, argv[0]); + + freeCommandLineArgs(&args); + return ret; +} diff --git a/src/webos/stb_impl.c b/src/webos/stb_impl.c new file mode 100644 index 000000000..d99b31ef3 --- /dev/null +++ b/src/webos/stb_impl.c @@ -0,0 +1,9 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_STDIO +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-result" +#include "stb_image_write.h" +#pragma GCC diagnostic pop \ No newline at end of file