From 825d92ce94fc43367383d048ea9c79ff2951e31f Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Tue, 25 Aug 2026 23:48:54 +0530 Subject: [PATCH 01/21] Add WebOS platform port --- CMakeLists.txt | 79 +++- README.md | 69 ++- src/webos/glibc_compat.c | 15 + src/webos/main.c | 969 +++++++++++++++++++++++++++++++++++++++ src/webos/stat_compat.c | 15 + src/webos/stb_impl.c | 9 + 6 files changed, 1151 insertions(+), 5 deletions(-) create mode 100644 src/webos/glibc_compat.c create mode 100644 src/webos/main.c create mode 100644 src/webos/stat_compat.c create mode 100644 src/webos/stb_impl.c diff --git a/CMakeLists.txt b/CMakeLists.txt index ac52fb9b9..83d1bc6a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,8 @@ if(AUDIO_BACKEND STREQUAL "") set(AUDIO_BACKEND "openal") elseif(PLATFORM STREQUAL "ps2") set(AUDIO_BACKEND "ps2") + elseif(PLATFORM STREQUAL "webos") + set(AUDIO_BACKEND "miniaudio") else() set(AUDIO_BACKEND "miniaudio") endif() @@ -143,12 +145,25 @@ else() file(GLOB SOURCES src/*.c) # These platforms haven't been converted to the refactored system yet. # This list should NEVER grow, all new platforms should use the new system. - if(PLATFORM STREQUAL "ps2" OR PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "web" OR PLATFORM STREQUAL "android") + if(PLATFORM STREQUAL "ps2" OR PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "web" OR PLATFORM STREQUAL "android" OR PLATFORM STREQUAL "webos") list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/loop.c") endif() + + if(PLATFORM STREQUAL "webos") + list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/stb_ds.c") + endif() endif() # Platform specific files builds -file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) +if(PLATFORM STREQUAL "webos") + # stat_compat.c and glibc_compat.c both provide stat/fstat + # compatibility; use only the glibc shim for WebOS. + file(GLOB PLATFORM_SOURCES + src/webos/main.c + src/webos/glibc_compat.c + ) +else() + file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) +endif() if(PLATFORM STREQUAL "android") add_library(butterscotch SHARED ${SOURCES} ${PLATFORM_SOURCES} ${AUDIO_SOURCES}) @@ -531,6 +546,66 @@ elseif(PLATFORM STREQUAL "android") # - GLESv3: OpenGL ES 3.0 functions # - OpenSLES: miniaudio's OpenSL ES backend (it dlopen's libaaudio.so at runtime when available, so no link-time aaudio dep) target_link_libraries(butterscotch PRIVATE log GLESv3 OpenSLES bzip2 stb_ds sha1 stb_vorbis) +elseif(PLATFORM STREQUAL "webos") + if(NOT ENABLE_MODERN_GL) + message(FATAL_ERROR "WebOS requires modern gl!") + endif() + + if(NOT TARGET glad) + add_library(glad STATIC vendor/glad/src/glad.c) + target_include_directories(glad PUBLIC + ${CMAKE_SOURCE_DIR}/vendor/glad/include + ) + endif() + + file(GLOB GL_SOURCES + src/gl/*.c + src/gl_common/*.c + src/image/*.c + ) + + target_sources(butterscotch PRIVATE ${GL_SOURCES}) + target_sources(butterscotch PRIVATE src/webos/stb_impl.c) + + target_include_directories(butterscotch PRIVATE + ${CMAKE_SOURCE_DIR}/src/gl + ${CMAKE_SOURCE_DIR}/src/gl_common + ${CMAKE_SOURCE_DIR}/src/image + ${CMAKE_SOURCE_DIR}/vendor/glad/include + ) + + target_include_directories(butterscotch PUBLIC + ${CMAKE_SOURCE_DIR}/vendor/stb/image + ${CMAKE_SOURCE_DIR}/vendor/stb/vorbis + ) + + find_package(PkgConfig REQUIRED) + pkg_check_modules(SDL2 REQUIRED sdl2) + + target_include_directories(butterscotch PRIVATE + ${SDL2_INCLUDE_DIRS} + ) + + target_link_directories(butterscotch PRIVATE + ${SDL2_LIBRARY_DIRS} + ) + + target_link_libraries(butterscotch PRIVATE + glad + ${SDL2_LIBRARIES} + GLESv2 + EGL + bzip2 + stb_ds + sha1 + stb_vorbis + ${CMAKE_DL_LIBS} + pthread + m + ) + + add_compile_definitions(PLATFORM_WEBOS) + elseif(PLATFORM STREQUAL "ps2") file(GLOB DEBUG_FONT_SOURCES src/debug_font/*.c) target_sources(butterscotch PRIVATE ${DEBUG_FONT_SOURCES}) diff --git a/README.md b/README.md index 6c1aee72a..249116769 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,66 @@ +# note: this webOS port was developed with significant assistance from ChatGPT as a proof of concept. ChatGPT was used throughout development to understand the existing platform ports, write and adapt code, troubleshoot webOS SDK/CMake/toolchain issues, and debug the build. + +# CONTROLLER REQUIRED. The B button triggers the webOS Back action. Use the in-game controller configuration to remap the controls. Tested with Undertale 1.08. + +## current status: + +The port successfully builds using the webOS SDK and produces a 32-bit ARM EABI5 executable. + +The port has been tested on webOS 23. Further testing is welcome. + +(likely more issues) + +## how to use: + +1. Install the Butterscotch `.ipk`. +2. Copy the required game files to the installed application's directory using `scp`. +3. `data.win` is the minimum required file, but other game files may also be required depending on the game. +4. Launch Butterscotch from the webOS launcher. + +## how to build: + +1. Install and source the webOS SDK / Homebrew SDK. +2. Clone this repository. +3. Configure with: + + `/usr/bin/cmake -S . -B build-webos -DPLATFORM=webos -DENABLE_MODERN_GL=ON` + +4. Build with: + + `/usr/bin/cmake --build build-webos` + +This produces: + +`build-webos/butterscotch` + +## how to package and install: + +1. Prepare a package directory containing: + - `butterscotch` + - `appinfo.json` + - `icon.png` + +2. Package it: + + `ares-package ` + +3. Install it: + + `ares-install .ipk` + +4. Launch it: + + `ares-launch ` + +5. Copy the required game files into the installed application directory: + + `scp -r root@:/` + +The webOS SDK environment must be sourced so that the Ares tools are available. + +## original readme: + +
Butterscotch Logo
@@ -66,7 +129,7 @@ Of course, there are exceptions that break game compatibility altogether: * ...and maybe more in the future! Additionally, any platform with reasonably complete C and POSIX conformance should work, the following have been tested. -* Linux with glibc as old as about ~1995 +* Linux with glibc as old as about ~1996 * FreeBSD as old as 2.2.8 * OpenBSD * NetBSD @@ -95,11 +158,11 @@ The following compilers have been tested to successfully build butterscotch, old ```bash mkdir build && cd build -cmake -DBACKEND=glfw3 -DCMAKE_BUILD_TYPE=Debug .. +cmake -DPLATFORM=desktop -DDESKTOP_BACKEND=glfw3 -DCMAKE_BUILD_TYPE=Debug .. make ``` -If you are using CLion, set the platform in `Settings` > `Build, Execution, Deployment` > `CMake` and add `-DBACKEND=glfw3` +If you are using CLion, set the platform in `Settings` > `Build, Execution, Deployment` > `CMake` and add `-DDESKTOP_BACKEND=glfw3` Then run Butterscotch with `./butterscotch /path/to/data.win`! 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/main.c b/src/webos/main.c new file mode 100644 index 000000000..ddcbe7e89 --- /dev/null +++ b/src/webos/main.c @@ -0,0 +1,969 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "data_win.h" +#include "runner.h" +#include "runner_keyboard.h" +#include "runner_mouse.h" +#include "overlay_file_system.h" +#include "ma_audio_system.h" +#include "noop_audio_system.h" +#include "gl/gl_renderer.h" +#include "gettime.h" +#include "log.h" +#include "stb_ds.h" + +static SDL_Window *gWindow = NULL; +static SDL_GLContext gGLContext = NULL; +static Runner *gRunner = NULL; +static SDL_GameController *gController = NULL; +static FILE *gInputLog = NULL; + +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); +} + +static void *webosGetProcAddress(const char *name) +{ + return SDL_GL_GetProcAddress(name); +} + +static bool webosGetWindowSize(int32_t *outW, int32_t *outH) +{ + if (gWindow == NULL || outW == NULL || outH == NULL) + return false; + + int w = 0; + int h = 0; + + SDL_GL_GetDrawableSize(gWindow, &w, &h); + + if (w <= 0 || h <= 0) + return false; + + *outW = w; + *outH = h; + return true; +} + +static void webosSetWindowTitle(const char *title) +{ + if (gWindow == NULL) + return; + + if (title == NULL) + title = "Butterscotch"; + + SDL_SetWindowTitle(gWindow, title); +} + +static int32_t SDLKeyToGml(SDL_Keycode key) +{ + if (key >= SDLK_a && key <= SDLK_z) + return (int32_t)(key - SDLK_a + 'A'); + + if (key >= SDLK_0 && key <= SDLK_9) + return (int32_t)key; + + switch (key) { + case SDLK_ESCAPE: return VK_ESCAPE; + case SDLK_RETURN: return VK_ENTER; + case SDLK_TAB: return VK_TAB; + case SDLK_BACKSPACE: return VK_BACKSPACE; + case SDLK_SPACE: return VK_SPACE; + case SDLK_LSHIFT: + case SDLK_RSHIFT: return VK_SHIFT; + case SDLK_LCTRL: + case SDLK_RCTRL: return VK_CONTROL; + case SDLK_LALT: + case SDLK_RALT: return VK_ALT; + case SDLK_UP: return VK_UP; + case SDLK_DOWN: return VK_DOWN; + case SDLK_LEFT: return VK_LEFT; + case SDLK_RIGHT: return VK_RIGHT; + case SDLK_F1: return VK_F1; + case SDLK_F2: return VK_F2; + case SDLK_F3: return VK_F3; + case SDLK_F4: return VK_F4; + case SDLK_F5: return VK_F5; + case SDLK_F6: return VK_F6; + case SDLK_F7: return VK_F7; + case SDLK_F8: return VK_F8; + case SDLK_F9: return VK_F9; + case SDLK_F10: return VK_F10; + case SDLK_F11: return VK_F11; + case SDLK_F12: return VK_F12; + case SDLK_INSERT: return VK_INSERT; + case SDLK_DELETE: return VK_DELETE; + case SDLK_HOME: return VK_HOME; + case SDLK_END: return VK_END; + case SDLK_PAGEUP: return VK_PAGEUP; + case SDLK_PAGEDOWN: return VK_PAGEDOWN; + default: return -1; + } +} + +static int32_t SDLMouseButtonToGml(uint8_t button) +{ + switch (button) { + case SDL_BUTTON_LEFT: return GML_MB_LEFT; + case SDL_BUTTON_RIGHT: return GML_MB_RIGHT; + case SDL_BUTTON_MIDDLE: return GML_MB_MIDDLE; + default: return -1; + } +} + +static uint32_t utf8_to_codepoint(const char *s) +{ + const unsigned char *p = (const unsigned char *)s; + + if (p == NULL || p[0] == '\0') + return 0; + + if (p[0] < 0x80) + return p[0]; + + if ((p[0] & 0xE0) == 0xC0) + return ((p[0] & 0x1F) << 6) | + (p[1] & 0x3F); + + if ((p[0] & 0xF0) == 0xE0) + return ((p[0] & 0x0F) << 12) | + ((p[1] & 0x3F) << 6) | + (p[2] & 0x3F); + + if ((p[0] & 0xF8) == 0xF0) + return ((p[0] & 0x07) << 18) | + ((p[1] & 0x3F) << 12) | + ((p[2] & 0x3F) << 6) | + (p[3] & 0x3F); + + return 0xFFFD; +} + +static bool webosInitGraphics(int width, int height) +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { + logError("SDL_Init failed: %s\n", SDL_GetError()); + return false; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); + + gWindow = SDL_CreateWindow( + "Butterscotch", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + SDL_WINDOW_OPENGL + ); + + if (gWindow == NULL) { + logError("SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return false; + } + + gGLContext = SDL_GL_CreateContext(gWindow); + + if (gGLContext == NULL) { + logError("SDL_GL_CreateContext failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(gWindow); + gWindow = NULL; + SDL_Quit(); + return false; + } + + SDL_GL_MakeCurrent(gWindow, gGLContext); + + if (!gladLoadGLES2Loader(webosGetProcAddress)) { + logError("gladLoadGLES2Loader failed\n"); + SDL_GL_DeleteContext(gGLContext); + gGLContext = NULL; + SDL_DestroyWindow(gWindow); + gWindow = NULL; + SDL_Quit(); + return false; + } + + logInfo("WebOS GL version: %s\n", (const char *)glGetString(GL_VERSION)); + logInfo("WebOS GL renderer: %s\n", (const char *)glGetString(GL_RENDERER)); + + return true; +} + +static bool mkdirP(const char *path) +{ + char buf[512]; + size_t len; + + if (path == NULL || path[0] == '\0') + return true; + + len = strlen(path); + + if (len >= sizeof(buf)) + return false; + + memcpy(buf, path, len + 1); + + for (size_t i = 1; i < len; i++) { + if (buf[i] == '/') { + buf[i] = '\0'; + + if (mkdir(buf, 0777) != 0 && errno != EEXIST) + return false; + + buf[i] = '/'; + } + } + + if (mkdir(buf, 0777) != 0 && errno != EEXIST) + return false; + + return true; +} + +static bool startRunner(const char *dataWinPath, const char *savesPath) +{ + DataWinParserOptions options = {0}; + + options.parseGen8 = true; + options.parseOptn = true; + options.parseLang = true; + options.parseExtn = true; + options.parseSond = true; + options.parseAgrp = true; + options.parseSprt = true; + options.parseBgnd = true; + options.parsePath = true; + options.parseScpt = true; + options.parseGlob = true; + options.parseShdr = true; + options.parseFont = true; + options.parseTmln = true; + options.parseObjt = true; + options.parseRoom = true; + options.parseTpag = true; + options.parseCode = true; + options.parseVari = true; + options.parseFunc = true; + options.parseStrg = true; + options.parseTxtr = true; + options.parseAudo = true; + options.skipLoadingPreciseMasksForNonPreciseSprites = true; + options.lazyLoadRooms = false; + options.eagerlyLoadedRooms = NULL; + + if (!mkdirP(savesPath)) { + logWarn("Could not create saves directory: %s\n", savesPath); + } + + logInfo("Loading data.win: %s\n", dataWinPath); + + DataWin *dataWin = DataWin_parse(dataWinPath, options); + + if (dataWin == NULL) { + logError("Failed to parse data.win: %s\n", dataWinPath); + return false; + } + + VMContext *vm = VM_create(dataWin); + + if (vm == NULL) { + logError("VM_create failed\n"); + DataWin_free(dataWin); + return false; + } + + Renderer *renderer = GLRenderer_create(); + + if (renderer == NULL) { + logError("GLRenderer_create failed\n"); + VM_free(vm); + DataWin_free(dataWin); + return false; + } + + const char *lastSlash = strrchr(dataWinPath, '/'); + char *bundleDir = NULL; + + if (lastSlash != NULL) { + size_t len = (size_t)(lastSlash - dataWinPath + 1); + + bundleDir = (char *)safeMalloc(len + 1); + memcpy(bundleDir, dataWinPath, len); + bundleDir[len] = '\0'; + } else { + bundleDir = safeStrdup("./"); + } + + OverlayFileSystem *overlayFs = + OverlayFileSystem_create(bundleDir, savesPath); + + free(bundleDir); + + if (overlayFs == NULL) { + logError("OverlayFileSystem_create failed\n"); + renderer->vtable->destroy(renderer); + VM_free(vm); + DataWin_free(dataWin); + return false; + } + + AudioSystem *audioSystem = + (AudioSystem *)MaAudioSystem_create(dataWin); + + if (audioSystem == NULL) { + logWarn("MaAudioSystem_create failed; using silent audio\n"); + audioSystem = (AudioSystem *)NoopAudioSystem_create(); + } + + gRunner = Runner_create( + dataWin, + vm, + renderer, + (FileSystem *)overlayFs, + audioSystem, + 0 + ); + + if (gRunner == NULL) { + logError("Runner_create failed\n"); + audioSystem->vtable->destroy(audioSystem); + renderer->vtable->destroy(renderer); + VM_free(vm); + DataWin_free(dataWin); + return false; + } + + gRunner->osType = OS_LINUX; + gRunner->setWindowTitle = webosSetWindowTitle; + gRunner->getWindowSize = webosGetWindowSize; + gRunner->windowHasFocus = NULL; + + char **args = NULL; + arrput(args, safeStrdup("butterscotch")); + Runner_setGameArgs(gRunner, args, (int32_t)arrlen(args)); + free(args[0]); + arrfree(args); + + const char *title = dataWin->gen8.displayName; + + if (title == NULL || title[0] == '\0') + title = dataWin->gen8.name; + + webosSetWindowTitle(title); + + gRunner->gameStartTime = nowNanos(); + + Runner_initFirstRoom(gRunner); + + logInfo("Runner started: %s\n", title); + + return true; +} + + +static void updateWebOSGamepad(void) +{ + if (gController == NULL || + !SDL_GameControllerGetAttached(gController)) + return; + + GamepadSlot *slot = &gRunner->gamepads->slots[0]; + + slot->connected = true; + slot->jid = 0; + + const char *name = SDL_GameControllerName(gController); + + if (name != NULL) { + strncpy( + slot->description, + name, + sizeof(slot->description) - 1 + ); + slot->description[ + sizeof(slot->description) - 1 + ] = '\0'; + } + + slot->axisValue[0] = + (float)SDL_GameControllerGetAxis( + gController, + SDL_CONTROLLER_AXIS_LEFTX + ) / 32767.0f; + + slot->axisValue[1] = + (float)SDL_GameControllerGetAxis( + gController, + SDL_CONTROLLER_AXIS_LEFTY + ) / 32767.0f; + + slot->axisValue[2] = + (float)SDL_GameControllerGetAxis( + gController, + SDL_CONTROLLER_AXIS_RIGHTX + ) / 32767.0f; + + slot->axisValue[3] = + (float)SDL_GameControllerGetAxis( + gController, + SDL_CONTROLLER_AXIS_RIGHTY + ) / 32767.0f; + + /* Feed SDL controller buttons into the existing RunnerGamepad slot. */ + { + const SDL_GameControllerButton sdlButtons[] = { + SDL_CONTROLLER_BUTTON_A, + SDL_CONTROLLER_BUTTON_B, + SDL_CONTROLLER_BUTTON_X, + SDL_CONTROLLER_BUTTON_Y, + SDL_CONTROLLER_BUTTON_LEFTSHOULDER, + SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, + SDL_CONTROLLER_BUTTON_BACK, + SDL_CONTROLLER_BUTTON_START, + SDL_CONTROLLER_BUTTON_LEFTSTICK, + SDL_CONTROLLER_BUTTON_RIGHTSTICK, + SDL_CONTROLLER_BUTTON_DPAD_UP, + SDL_CONTROLLER_BUTTON_DPAD_DOWN, + SDL_CONTROLLER_BUTTON_DPAD_LEFT, + SDL_CONTROLLER_BUTTON_DPAD_RIGHT + }; + + const int runnerIndices[] = { + 0, 1, 2, 3, + 4, 5, + 8, 9, + 10, 11, + 12, 13, 14, 15 + }; + + for (size_t i = 0; i < sizeof(sdlButtons) / sizeof(sdlButtons[0]); i++) { + int idx = runnerIndices[i]; + bool wasDown = slot->buttonDown[idx]; + bool isDown = SDL_GameControllerGetButton( + gController, sdlButtons[i] + ) != 0; + + if (isDown && !wasDown) + slot->buttonPressed[idx] = true; + + if (!isDown && wasDown) + slot->buttonReleased[idx] = true; + + slot->buttonDown[idx] = isDown; + slot->buttonValue[idx] = isDown ? 1.0f : 0.0f; + } + } + + gRunner->gamepads->connectedCount = 1; + + if (gInputLog != NULL) { + fprintf( + gInputLog, + "RUNNER: count=%d connected=%d axes=%d buttons=%d desc=%s\n", + RunnerGamepad_getDeviceCount(gRunner->gamepads), + RunnerGamepad_isConnected(gRunner->gamepads, 0), + RunnerGamepad_getAxisCount(gRunner->gamepads, 0), + RunnerGamepad_getButtonCount(gRunner->gamepads, 0), + RunnerGamepad_getDescription(gRunner->gamepads, 0) + ); + fflush(gInputLog); +} + + if (gInputLog != NULL) { + int a = SDL_GameControllerGetButton( + gController, SDL_CONTROLLER_BUTTON_A + ); + int b = SDL_GameControllerGetButton( + gController, SDL_CONTROLLER_BUTTON_B + ); + int x = SDL_GameControllerGetButton( + gController, SDL_CONTROLLER_BUTTON_X + ); + int y = SDL_GameControllerGetButton( + gController, SDL_CONTROLLER_BUTTON_Y + ); + + fprintf( + gInputLog, + "LX=%.3f LY=%.3f RX=%.3f RY=%.3f A=%d B=%d X=%d Y=%d\n", + slot->axisValue[0], + slot->axisValue[1], + slot->axisValue[2], + slot->axisValue[3], + a, b, x, y + ); + fflush(gInputLog); + } +} + +static void initControllerDebug(void) +{ + int count = SDL_NumJoysticks(); + + gInputLog = fopen("/tmp/butterscotch_input.log", "w"); + + if (gInputLog != NULL) { + fprintf(gInputLog, "SDL joystick count: %d\n", count); + + for (int i = 0; i < count; i++) { + const char *name = SDL_GameControllerNameForIndex(i); + + fprintf( + gInputLog, + "joystick %d: isGameController=%d name=%s\n", + i, + SDL_IsGameController(i), + name ? name : "(null)" + ); + } + + fflush(gInputLog); + } + + for (int i = 0; i < count; i++) { + if (!SDL_IsGameController(i)) + continue; + + gController = SDL_GameControllerOpen(i); + + if (gController != NULL) { + logInfo( + "WebOS controller opened: %s\n", + SDL_GameControllerName(gController) + ? SDL_GameControllerName(gController) + : "(unknown)" + ); + break; + } + } +} + +static void handleEvents(void) +{ + SDL_Event e; + + RunnerKeyboard_beginFrame(gRunner->keyboard); + RunnerGamepad_beginFrame(gRunner->gamepads); + + SDL_GameControllerUpdate(); + updateWebOSGamepad(); + + while (SDL_PollEvent(&e)) { + logInfo("WEBOS SDL EVENT: type=%u\\n", (unsigned)e.type); + + if (gInputLog != NULL) { + fprintf(gInputLog, "SDL EVENT: type=%u\\n", (unsigned)e.type); + + if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { + fprintf( + gInputLog, + " KEY: type=%u sym=%d name=%s\\n", + (unsigned)e.type, + (int)e.key.keysym.sym, + SDL_GetKeyName(e.key.keysym.sym) + ); + } + + if (e.type == SDL_CONTROLLERBUTTONDOWN || + e.type == SDL_CONTROLLERBUTTONUP) { + fprintf( + gInputLog, + " CONTROLLER: type=%u button=%d\\n", + (unsigned)e.type, + (int)e.cbutton.button + ); + } + + fflush(gInputLog); + } + + if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { + logInfo( + "KEY EVENT: type=%u sym=%d name=%s repeat=%d\\n", + (unsigned)e.type, + (int)e.key.keysym.sym, + SDL_GetKeyName(e.key.keysym.sym), + (int)e.key.repeat + ); + } + + if (e.type == SDL_CONTROLLERBUTTONDOWN || + e.type == SDL_CONTROLLERBUTTONUP) { + logInfo( + "CONTROLLER EVENT: type=%u button=%d\\n", + (unsigned)e.type, + (int)e.cbutton.button + ); + } + + switch (e.type) { + case SDL_JOYBUTTONDOWN: + case SDL_JOYBUTTONUP: + /* Consume duplicate raw joystick events. */ + break; + + case SDL_QUIT: + gRunner->shouldExit = true; + break; + + case SDL_KEYDOWN: { + if (e.key.repeat) + break; + + int32_t key = SDLKeyToGml(e.key.keysym.sym); + + if (key >= 0) + RunnerKeyboard_onKeyDown(gRunner->keyboard, key); + break; + } + + case SDL_KEYUP: { + int32_t key = SDLKeyToGml(e.key.keysym.sym); + + if (key >= 0) + RunnerKeyboard_onKeyUp(gRunner->keyboard, key); + break; + } + + case SDL_TEXTINPUT: + RunnerKeyboard_onCharacter( + gRunner->keyboard, + utf8_to_codepoint(e.text.text) + ); + break; + + case SDL_MOUSEBUTTONDOWN: { + int32_t button = + SDLMouseButtonToGml(e.button.button); + + if (button >= 0) + RunnerMouse_onButtonDown( + gRunner->mouse, + button + ); + break; + } + + case SDL_MOUSEBUTTONUP: { + int32_t button = + SDLMouseButtonToGml(e.button.button); + + if (button >= 0) + RunnerMouse_onButtonUp( + gRunner->mouse, + button + ); + break; + } + + case SDL_MOUSEWHEEL: + if (e.wheel.y != 0) + RunnerMouse_onWheel( + gRunner->mouse, + (float)e.wheel.y + ); + break; + + case SDL_MOUSEMOTION: + Runner_updateMousePosition( + gRunner, + e.motion.windowID ? 1280 : 1280, + 720, + e.motion.x, + e.motion.y + ); + break; + + default: + break; + } + } +} + +static void runFrame(void) +{ + static uint64_t lastTime = 0; + uint64_t now = nowNanos(); + + if (lastTime == 0) + lastTime = now; + + gRunner->deltaTime = + (double)(now - lastTime) / 1000.0; + + lastTime = now; + + int32_t winW = 0; + int32_t winH = 0; + + if (!webosGetWindowSize(&winW, &winH)) { + winW = 1280; + winH = 720; + } + + Runner_step(gRunner); + + if (gRunner->audioSystem != NULL) { + float audioDt = + (float)(gRunner->deltaTime / 1000000.0); + + if (audioDt < 0.0f) + audioDt = 0.0f; + + if (audioDt > 0.1f) + audioDt = 0.1f; + + gRunner->audioSystem->vtable->update( + gRunner->audioSystem, + audioDt + ); + } + + int32_t gameW = + gRunner->dataWin->gen8.defaultWindowWidth; + + int32_t gameH = + gRunner->dataWin->gen8.defaultWindowHeight; + + if (gRunner->appSurfaceEnabled) { + if (gRunner->applicationWidth > 0) + gameW = gRunner->applicationWidth; + + if (gRunner->applicationHeight > 0) + gameH = gRunner->applicationHeight; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + Runner_drawPre(gRunner, winW, winH); + + Runner_beginFrame( + gRunner, + gameW, + gameH, + winW, + winH, + winW, + winH + ); + + Runner_drawViews( + gRunner, + gameW, + gameH, + false + ); + + gRunner->renderer->vtable->endFrameInit( + gRunner->renderer + ); + + Runner_drawPost(gRunner, winW, winH); + + gRunner->renderer->vtable->endFrameEnd( + gRunner->renderer + ); + + Runner_drawGUI( + gRunner, + winW, + winH, + gameW, + gameH + ); + + bool shouldSwap = (gRunner->pendingRoom == -1); + + Runner_handlePendingRoomChange(gRunner); + + if (shouldSwap) + SDL_GL_SwapWindow(gWindow); +} + +static void destroyRunner(void) +{ + if (gRunner == NULL) + return; + + AudioSystem *audio = gRunner->audioSystem; + Renderer *renderer = gRunner->renderer; + DataWin *dataWin = gRunner->dataWin; + VMContext *vm = gRunner->vmContext; + + gRunner = NULL; + + if (audio != NULL) + audio->vtable->destroy(audio); + + if (renderer != NULL) + renderer->vtable->destroy(renderer); + + Runner_free(gRunner); + VM_free(vm); + DataWin_free(dataWin); +} + +static void webosShutdownGraphics(void) +{ + if (gGLContext != NULL) { + SDL_GL_DeleteContext(gGLContext); + gGLContext = NULL; + } + + if (gWindow != NULL) { + SDL_DestroyWindow(gWindow); + gWindow = NULL; + } + + SDL_Quit(); +} + +int main(int argc, char **argv) +{ + const char *dataWinPath = "./data.win"; + const char *savesPath = "./saves"; + + /* + * webOS native apps receive a JSON launch object in argv[1]. + * It is NOT a data.win path, so deliberately ignore it here. + */ + + if (!webosInitGraphics(1280, 720)) { + return 1; + } + + SDL_StartTextInput(); + + { + gInputLog = fopen("/tmp/butterscotch_input.log", "w"); + + if (gInputLog != NULL) { + int count = SDL_NumJoysticks(); + + fprintf(gInputLog, "SDL joystick count: %d\n", count); + + for (int i = 0; i < count; i++) { + const char *name = SDL_GameControllerNameForIndex(i); + + fprintf( + gInputLog, + "SDL joystick %d: isGameController=%d name=%s\n", + i, + SDL_IsGameController(i), + name ? name : "(null)" + ); + + if (gController == NULL && + SDL_IsGameController(i)) { + gController = SDL_GameControllerOpen(i); + + if (gController != NULL) { + fprintf( + gInputLog, + "Opened controller %d: %s\n", + i, + SDL_GameControllerName(gController) + ? SDL_GameControllerName(gController) + : "(unknown)" + ); + } else { + fprintf( + gInputLog, + "Failed to open controller %d: %s\n", + i, + SDL_GetError() + ); + } + } + } + + fflush(gInputLog); + } + } + + if (!startRunner(dataWinPath, savesPath)) { + logError("Could not start Butterscotch Runner\n"); + webosShutdownGraphics(); + return 1; + } + + bool running = true; + uint64_t nextFrameTime = nowNanos(); + + while (running && !gRunner->shouldExit) { + handleEvents(); + + if (gRunner->shouldExit) + break; + + runFrame(); + + if (gRunner->currentRoom != NULL && + gRunner->currentRoom->speed > 0) { + uint64_t targetFrameNs = + 1000000000ULL / (uint64_t)gRunner->currentRoom->speed; + + nextFrameTime += targetFrameNs; + + uint64_t now = nowNanos(); + + if (now > nextFrameTime + targetFrameNs * 4) { + nextFrameTime = now + targetFrameNs; + } else { + while (now < nextFrameTime) { + uint64_t remaining = nextFrameTime - now; + + if (remaining > 2000000ULL) { + SDL_Delay((uint32_t)((remaining - 1000000ULL) / 1000000ULL)); + } + + now = nowNanos(); + } + } + } else { + nextFrameTime = nowNanos(); + } + } + + destroyRunner(); + SDL_StopTextInput(); + webosShutdownGraphics(); + + return 0; +} diff --git a/src/webos/stat_compat.c b/src/webos/stat_compat.c new file mode 100644 index 000000000..3a1f82ede --- /dev/null +++ b/src/webos/stat_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/stb_impl.c b/src/webos/stb_impl.c new file mode 100644 index 000000000..c349348c7 --- /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 +#include "stb_image_write.h" + +#define STB_DS_IMPLEMENTATION +#include "stb_ds.h" From f55138368d52038ae40b31e1c86fbcebda77c767 Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Sun, 30 Aug 2026 22:56:21 +0530 Subject: [PATCH 02/21] Refactor WebOS to upstream SDL2 backend --- CMakeLists.txt | 86 +--- src/webos/log.c | 25 ++ src/webos/main.c | 975 +--------------------------------------- src/webos/stat_compat.c | 15 - src/webos/stb_impl.c | 3 - 5 files changed, 63 insertions(+), 1041 deletions(-) create mode 100644 src/webos/log.c delete mode 100644 src/webos/stat_compat.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e118f7fb..c6ec9317c 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) @@ -123,7 +125,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) @@ -158,20 +160,16 @@ else() file(GLOB SOURCES src/*.c) # These platforms haven't been converted to the refactored system yet. # This list should NEVER grow, all new platforms should use the new system. - if(PLATFORM STREQUAL "ps2" OR PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "web" OR PLATFORM STREQUAL "android" OR PLATFORM STREQUAL "webos") + if(PLATFORM STREQUAL "ps2" OR PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "web" OR PLATFORM STREQUAL "android") list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/loop.c") endif() - if(PLATFORM STREQUAL "webos") - list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/stb_ds.c") - endif() endif() # Platform specific files builds if(PLATFORM STREQUAL "webos") - # stat_compat.c and glibc_compat.c both provide stat/fstat - # compatibility; use only the glibc shim for WebOS. file(GLOB PLATFORM_SOURCES src/webos/main.c + src/webos/log.c src/webos/glibc_compat.c ) else() @@ -261,8 +259,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... @@ -441,6 +444,11 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "swi nx_create_nro(butterscotch NACP Butterscotch.nacp) endif() endif() + if(PLATFORM STREQUAL "webos") + target_sources(butterscotch PRIVATE src/webos/stb_impl.c) + target_link_libraries(butterscotch PRIVATE GLESv2 EGL) + endif() + if(BACKEND STREQUAL "appkit") target_sources(butterscotch PRIVATE src/backends/${BACKEND}.m) else() @@ -576,66 +584,6 @@ elseif(PLATFORM STREQUAL "android") # - GLESv3: OpenGL ES 3.0 functions # - OpenSLES: miniaudio's OpenSL ES backend (it dlopen's libaaudio.so at runtime when available, so no link-time aaudio dep) target_link_libraries(butterscotch PRIVATE log GLESv3 OpenSLES bzip2 stb_ds sha1 stb_vorbis) -elseif(PLATFORM STREQUAL "webos") - if(NOT ENABLE_MODERN_GL) - message(FATAL_ERROR "WebOS requires modern gl!") - endif() - - if(NOT TARGET glad) - add_library(glad STATIC vendor/glad/src/glad.c) - target_include_directories(glad PUBLIC - ${CMAKE_SOURCE_DIR}/vendor/glad/include - ) - endif() - - file(GLOB GL_SOURCES - src/gl/*.c - src/gl_common/*.c - src/image/*.c - ) - - target_sources(butterscotch PRIVATE ${GL_SOURCES}) - target_sources(butterscotch PRIVATE src/webos/stb_impl.c) - - target_include_directories(butterscotch PRIVATE - ${CMAKE_SOURCE_DIR}/src/gl - ${CMAKE_SOURCE_DIR}/src/gl_common - ${CMAKE_SOURCE_DIR}/src/image - ${CMAKE_SOURCE_DIR}/vendor/glad/include - ) - - target_include_directories(butterscotch PUBLIC - ${CMAKE_SOURCE_DIR}/vendor/stb/image - ${CMAKE_SOURCE_DIR}/vendor/stb/vorbis - ) - - find_package(PkgConfig REQUIRED) - pkg_check_modules(SDL2 REQUIRED sdl2) - - target_include_directories(butterscotch PRIVATE - ${SDL2_INCLUDE_DIRS} - ) - - target_link_directories(butterscotch PRIVATE - ${SDL2_LIBRARY_DIRS} - ) - - target_link_libraries(butterscotch PRIVATE - glad - ${SDL2_LIBRARIES} - GLESv2 - EGL - bzip2 - stb_ds - sha1 - stb_vorbis - ${CMAKE_DL_LIBS} - pthread - m - ) - - add_compile_definitions(PLATFORM_WEBOS) - elseif(PLATFORM STREQUAL "ps2") file(GLOB DEBUG_FONT_SOURCES src/debug_font/*.c) target_sources(butterscotch PRIVATE ${DEBUG_FONT_SOURCES}) 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 index ddcbe7e89..55d0e4022 100644 --- a/src/webos/main.c +++ b/src/webos/main.c @@ -1,969 +1,36 @@ -#include -#include +#include -#include +#include "platformdefs.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include "common.h" -#include "data_win.h" -#include "runner.h" -#include "runner_keyboard.h" -#include "runner_mouse.h" -#include "overlay_file_system.h" -#include "ma_audio_system.h" -#include "noop_audio_system.h" -#include "gl/gl_renderer.h" -#include "gettime.h" -#include "log.h" -#include "stb_ds.h" - -static SDL_Window *gWindow = NULL; -static SDL_GLContext gGLContext = NULL; -static Runner *gRunner = NULL; -static SDL_GameController *gController = NULL; -static FILE *gInputLog = NULL; - -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); -} - -static void *webosGetProcAddress(const char *name) -{ - return SDL_GL_GetProcAddress(name); -} - -static bool webosGetWindowSize(int32_t *outW, int32_t *outH) -{ - if (gWindow == NULL || outW == NULL || outH == NULL) - return false; - - int w = 0; - int h = 0; - - SDL_GL_GetDrawableSize(gWindow, &w, &h); - - if (w <= 0 || h <= 0) - return false; - - *outW = w; - *outH = h; - return true; -} - -static void webosSetWindowTitle(const char *title) -{ - if (gWindow == NULL) - return; - - if (title == NULL) - title = "Butterscotch"; - - SDL_SetWindowTitle(gWindow, title); -} - -static int32_t SDLKeyToGml(SDL_Keycode key) -{ - if (key >= SDLK_a && key <= SDLK_z) - return (int32_t)(key - SDLK_a + 'A'); - - if (key >= SDLK_0 && key <= SDLK_9) - return (int32_t)key; - - switch (key) { - case SDLK_ESCAPE: return VK_ESCAPE; - case SDLK_RETURN: return VK_ENTER; - case SDLK_TAB: return VK_TAB; - case SDLK_BACKSPACE: return VK_BACKSPACE; - case SDLK_SPACE: return VK_SPACE; - case SDLK_LSHIFT: - case SDLK_RSHIFT: return VK_SHIFT; - case SDLK_LCTRL: - case SDLK_RCTRL: return VK_CONTROL; - case SDLK_LALT: - case SDLK_RALT: return VK_ALT; - case SDLK_UP: return VK_UP; - case SDLK_DOWN: return VK_DOWN; - case SDLK_LEFT: return VK_LEFT; - case SDLK_RIGHT: return VK_RIGHT; - case SDLK_F1: return VK_F1; - case SDLK_F2: return VK_F2; - case SDLK_F3: return VK_F3; - case SDLK_F4: return VK_F4; - case SDLK_F5: return VK_F5; - case SDLK_F6: return VK_F6; - case SDLK_F7: return VK_F7; - case SDLK_F8: return VK_F8; - case SDLK_F9: return VK_F9; - case SDLK_F10: return VK_F10; - case SDLK_F11: return VK_F11; - case SDLK_F12: return VK_F12; - case SDLK_INSERT: return VK_INSERT; - case SDLK_DELETE: return VK_DELETE; - case SDLK_HOME: return VK_HOME; - case SDLK_END: return VK_END; - case SDLK_PAGEUP: return VK_PAGEUP; - case SDLK_PAGEDOWN: return VK_PAGEDOWN; - default: return -1; - } -} - -static int32_t SDLMouseButtonToGml(uint8_t button) -{ - switch (button) { - case SDL_BUTTON_LEFT: return GML_MB_LEFT; - case SDL_BUTTON_RIGHT: return GML_MB_RIGHT; - case SDL_BUTTON_MIDDLE: return GML_MB_MIDDLE; - default: return -1; - } -} - -static uint32_t utf8_to_codepoint(const char *s) -{ - const unsigned char *p = (const unsigned char *)s; - - if (p == NULL || p[0] == '\0') - return 0; - - if (p[0] < 0x80) - return p[0]; - - if ((p[0] & 0xE0) == 0xC0) - return ((p[0] & 0x1F) << 6) | - (p[1] & 0x3F); - - if ((p[0] & 0xF0) == 0xE0) - return ((p[0] & 0x0F) << 12) | - ((p[1] & 0x3F) << 6) | - (p[2] & 0x3F); - - if ((p[0] & 0xF8) == 0xF0) - return ((p[0] & 0x07) << 18) | - ((p[1] & 0x3F) << 12) | - ((p[2] & 0x3F) << 6) | - (p[3] & 0x3F); - - return 0xFFFD; -} - -static bool webosInitGraphics(int width, int height) -{ - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { - logError("SDL_Init failed: %s\n", SDL_GetError()); - return false; - } - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); - - gWindow = SDL_CreateWindow( - "Butterscotch", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - width, - height, - SDL_WINDOW_OPENGL - ); - - if (gWindow == NULL) { - logError("SDL_CreateWindow failed: %s\n", SDL_GetError()); - SDL_Quit(); - return false; - } - - gGLContext = SDL_GL_CreateContext(gWindow); - - if (gGLContext == NULL) { - logError("SDL_GL_CreateContext failed: %s\n", SDL_GetError()); - SDL_DestroyWindow(gWindow); - gWindow = NULL; - SDL_Quit(); - return false; - } - - SDL_GL_MakeCurrent(gWindow, gGLContext); - - if (!gladLoadGLES2Loader(webosGetProcAddress)) { - logError("gladLoadGLES2Loader failed\n"); - SDL_GL_DeleteContext(gGLContext); - gGLContext = NULL; - SDL_DestroyWindow(gWindow); - gWindow = NULL; - SDL_Quit(); - return false; - } - - logInfo("WebOS GL version: %s\n", (const char *)glGetString(GL_VERSION)); - logInfo("WebOS GL renderer: %s\n", (const char *)glGetString(GL_RENDERER)); - - return true; -} - -static bool mkdirP(const char *path) -{ - char buf[512]; - size_t len; - - if (path == NULL || path[0] == '\0') - return true; - - len = strlen(path); - - if (len >= sizeof(buf)) - return false; - - memcpy(buf, path, len + 1); - - for (size_t i = 1; i < len; i++) { - if (buf[i] == '/') { - buf[i] = '\0'; - - if (mkdir(buf, 0777) != 0 && errno != EEXIST) - return false; - - buf[i] = '/'; - } - } - - if (mkdir(buf, 0777) != 0 && errno != EEXIST) - return false; - - return true; -} - -static bool startRunner(const char *dataWinPath, const char *savesPath) -{ - DataWinParserOptions options = {0}; - - options.parseGen8 = true; - options.parseOptn = true; - options.parseLang = true; - options.parseExtn = true; - options.parseSond = true; - options.parseAgrp = true; - options.parseSprt = true; - options.parseBgnd = true; - options.parsePath = true; - options.parseScpt = true; - options.parseGlob = true; - options.parseShdr = true; - options.parseFont = true; - options.parseTmln = true; - options.parseObjt = true; - options.parseRoom = true; - options.parseTpag = true; - options.parseCode = true; - options.parseVari = true; - options.parseFunc = true; - options.parseStrg = true; - options.parseTxtr = true; - options.parseAudo = true; - options.skipLoadingPreciseMasksForNonPreciseSprites = true; - options.lazyLoadRooms = false; - options.eagerlyLoadedRooms = NULL; - - if (!mkdirP(savesPath)) { - logWarn("Could not create saves directory: %s\n", savesPath); - } - - logInfo("Loading data.win: %s\n", dataWinPath); - - DataWin *dataWin = DataWin_parse(dataWinPath, options); - - if (dataWin == NULL) { - logError("Failed to parse data.win: %s\n", dataWinPath); - return false; - } - - VMContext *vm = VM_create(dataWin); - - if (vm == NULL) { - logError("VM_create failed\n"); - DataWin_free(dataWin); - return false; - } - - Renderer *renderer = GLRenderer_create(); - - if (renderer == NULL) { - logError("GLRenderer_create failed\n"); - VM_free(vm); - DataWin_free(dataWin); - return false; - } - - const char *lastSlash = strrchr(dataWinPath, '/'); - char *bundleDir = NULL; - - if (lastSlash != NULL) { - size_t len = (size_t)(lastSlash - dataWinPath + 1); - - bundleDir = (char *)safeMalloc(len + 1); - memcpy(bundleDir, dataWinPath, len); - bundleDir[len] = '\0'; - } else { - bundleDir = safeStrdup("./"); - } - - OverlayFileSystem *overlayFs = - OverlayFileSystem_create(bundleDir, savesPath); - - free(bundleDir); - - if (overlayFs == NULL) { - logError("OverlayFileSystem_create failed\n"); - renderer->vtable->destroy(renderer); - VM_free(vm); - DataWin_free(dataWin); - return false; - } - - AudioSystem *audioSystem = - (AudioSystem *)MaAudioSystem_create(dataWin); - - if (audioSystem == NULL) { - logWarn("MaAudioSystem_create failed; using silent audio\n"); - audioSystem = (AudioSystem *)NoopAudioSystem_create(); - } - - gRunner = Runner_create( - dataWin, - vm, - renderer, - (FileSystem *)overlayFs, - audioSystem, - 0 - ); - - if (gRunner == NULL) { - logError("Runner_create failed\n"); - audioSystem->vtable->destroy(audioSystem); - renderer->vtable->destroy(renderer); - VM_free(vm); - DataWin_free(dataWin); - return false; - } - - gRunner->osType = OS_LINUX; - gRunner->setWindowTitle = webosSetWindowTitle; - gRunner->getWindowSize = webosGetWindowSize; - gRunner->windowHasFocus = NULL; - - char **args = NULL; - arrput(args, safeStrdup("butterscotch")); - Runner_setGameArgs(gRunner, args, (int32_t)arrlen(args)); - free(args[0]); - arrfree(args); - - const char *title = dataWin->gen8.displayName; - - if (title == NULL || title[0] == '\0') - title = dataWin->gen8.name; - - webosSetWindowTitle(title); - - gRunner->gameStartTime = nowNanos(); - - Runner_initFirstRoom(gRunner); - - logInfo("Runner started: %s\n", title); - - return true; -} - - -static void updateWebOSGamepad(void) -{ - if (gController == NULL || - !SDL_GameControllerGetAttached(gController)) - return; - - GamepadSlot *slot = &gRunner->gamepads->slots[0]; - - slot->connected = true; - slot->jid = 0; - - const char *name = SDL_GameControllerName(gController); - - if (name != NULL) { - strncpy( - slot->description, - name, - sizeof(slot->description) - 1 - ); - slot->description[ - sizeof(slot->description) - 1 - ] = '\0'; - } - - slot->axisValue[0] = - (float)SDL_GameControllerGetAxis( - gController, - SDL_CONTROLLER_AXIS_LEFTX - ) / 32767.0f; - - slot->axisValue[1] = - (float)SDL_GameControllerGetAxis( - gController, - SDL_CONTROLLER_AXIS_LEFTY - ) / 32767.0f; - - slot->axisValue[2] = - (float)SDL_GameControllerGetAxis( - gController, - SDL_CONTROLLER_AXIS_RIGHTX - ) / 32767.0f; - - slot->axisValue[3] = - (float)SDL_GameControllerGetAxis( - gController, - SDL_CONTROLLER_AXIS_RIGHTY - ) / 32767.0f; - - /* Feed SDL controller buttons into the existing RunnerGamepad slot. */ - { - const SDL_GameControllerButton sdlButtons[] = { - SDL_CONTROLLER_BUTTON_A, - SDL_CONTROLLER_BUTTON_B, - SDL_CONTROLLER_BUTTON_X, - SDL_CONTROLLER_BUTTON_Y, - SDL_CONTROLLER_BUTTON_LEFTSHOULDER, - SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, - SDL_CONTROLLER_BUTTON_BACK, - SDL_CONTROLLER_BUTTON_START, - SDL_CONTROLLER_BUTTON_LEFTSTICK, - SDL_CONTROLLER_BUTTON_RIGHTSTICK, - SDL_CONTROLLER_BUTTON_DPAD_UP, - SDL_CONTROLLER_BUTTON_DPAD_DOWN, - SDL_CONTROLLER_BUTTON_DPAD_LEFT, - SDL_CONTROLLER_BUTTON_DPAD_RIGHT - }; - - const int runnerIndices[] = { - 0, 1, 2, 3, - 4, 5, - 8, 9, - 10, 11, - 12, 13, 14, 15 - }; - - for (size_t i = 0; i < sizeof(sdlButtons) / sizeof(sdlButtons[0]); i++) { - int idx = runnerIndices[i]; - bool wasDown = slot->buttonDown[idx]; - bool isDown = SDL_GameControllerGetButton( - gController, sdlButtons[i] - ) != 0; - - if (isDown && !wasDown) - slot->buttonPressed[idx] = true; - - if (!isDown && wasDown) - slot->buttonReleased[idx] = true; - - slot->buttonDown[idx] = isDown; - slot->buttonValue[idx] = isDown ? 1.0f : 0.0f; - } - } - - gRunner->gamepads->connectedCount = 1; - - if (gInputLog != NULL) { - fprintf( - gInputLog, - "RUNNER: count=%d connected=%d axes=%d buttons=%d desc=%s\n", - RunnerGamepad_getDeviceCount(gRunner->gamepads), - RunnerGamepad_isConnected(gRunner->gamepads, 0), - RunnerGamepad_getAxisCount(gRunner->gamepads, 0), - RunnerGamepad_getButtonCount(gRunner->gamepads, 0), - RunnerGamepad_getDescription(gRunner->gamepads, 0) - ); - fflush(gInputLog); -} - - if (gInputLog != NULL) { - int a = SDL_GameControllerGetButton( - gController, SDL_CONTROLLER_BUTTON_A - ); - int b = SDL_GameControllerGetButton( - gController, SDL_CONTROLLER_BUTTON_B - ); - int x = SDL_GameControllerGetButton( - gController, SDL_CONTROLLER_BUTTON_X - ); - int y = SDL_GameControllerGetButton( - gController, SDL_CONTROLLER_BUTTON_Y - ); - - fprintf( - gInputLog, - "LX=%.3f LY=%.3f RX=%.3f RY=%.3f A=%d B=%d X=%d Y=%d\n", - slot->axisValue[0], - slot->axisValue[1], - slot->axisValue[2], - slot->axisValue[3], - a, b, x, y - ); - fflush(gInputLog); - } -} - -static void initControllerDebug(void) -{ - int count = SDL_NumJoysticks(); - - gInputLog = fopen("/tmp/butterscotch_input.log", "w"); - - if (gInputLog != NULL) { - fprintf(gInputLog, "SDL joystick count: %d\n", count); - - for (int i = 0; i < count; i++) { - const char *name = SDL_GameControllerNameForIndex(i); - - fprintf( - gInputLog, - "joystick %d: isGameController=%d name=%s\n", - i, - SDL_IsGameController(i), - name ? name : "(null)" - ); - } - - fflush(gInputLog); - } - - for (int i = 0; i < count; i++) { - if (!SDL_IsGameController(i)) - continue; - - gController = SDL_GameControllerOpen(i); - - if (gController != NULL) { - logInfo( - "WebOS controller opened: %s\n", - SDL_GameControllerName(gController) - ? SDL_GameControllerName(gController) - : "(unknown)" - ); - break; - } - } -} - -static void handleEvents(void) -{ - SDL_Event e; - - RunnerKeyboard_beginFrame(gRunner->keyboard); - RunnerGamepad_beginFrame(gRunner->gamepads); - - SDL_GameControllerUpdate(); - updateWebOSGamepad(); - - while (SDL_PollEvent(&e)) { - logInfo("WEBOS SDL EVENT: type=%u\\n", (unsigned)e.type); - - if (gInputLog != NULL) { - fprintf(gInputLog, "SDL EVENT: type=%u\\n", (unsigned)e.type); - - if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { - fprintf( - gInputLog, - " KEY: type=%u sym=%d name=%s\\n", - (unsigned)e.type, - (int)e.key.keysym.sym, - SDL_GetKeyName(e.key.keysym.sym) - ); - } - - if (e.type == SDL_CONTROLLERBUTTONDOWN || - e.type == SDL_CONTROLLERBUTTONUP) { - fprintf( - gInputLog, - " CONTROLLER: type=%u button=%d\\n", - (unsigned)e.type, - (int)e.cbutton.button - ); - } - - fflush(gInputLog); - } - - if (e.type == SDL_KEYDOWN || e.type == SDL_KEYUP) { - logInfo( - "KEY EVENT: type=%u sym=%d name=%s repeat=%d\\n", - (unsigned)e.type, - (int)e.key.keysym.sym, - SDL_GetKeyName(e.key.keysym.sym), - (int)e.key.repeat - ); - } - - if (e.type == SDL_CONTROLLERBUTTONDOWN || - e.type == SDL_CONTROLLERBUTTONUP) { - logInfo( - "CONTROLLER EVENT: type=%u button=%d\\n", - (unsigned)e.type, - (int)e.cbutton.button - ); - } - - switch (e.type) { - case SDL_JOYBUTTONDOWN: - case SDL_JOYBUTTONUP: - /* Consume duplicate raw joystick events. */ - break; - - case SDL_QUIT: - gRunner->shouldExit = true; - break; - - case SDL_KEYDOWN: { - if (e.key.repeat) - break; - - int32_t key = SDLKeyToGml(e.key.keysym.sym); - - if (key >= 0) - RunnerKeyboard_onKeyDown(gRunner->keyboard, key); - break; - } - - case SDL_KEYUP: { - int32_t key = SDLKeyToGml(e.key.keysym.sym); - - if (key >= 0) - RunnerKeyboard_onKeyUp(gRunner->keyboard, key); - break; - } - - case SDL_TEXTINPUT: - RunnerKeyboard_onCharacter( - gRunner->keyboard, - utf8_to_codepoint(e.text.text) - ); - break; - - case SDL_MOUSEBUTTONDOWN: { - int32_t button = - SDLMouseButtonToGml(e.button.button); - - if (button >= 0) - RunnerMouse_onButtonDown( - gRunner->mouse, - button - ); - break; - } - - case SDL_MOUSEBUTTONUP: { - int32_t button = - SDLMouseButtonToGml(e.button.button); - - if (button >= 0) - RunnerMouse_onButtonUp( - gRunner->mouse, - button - ); - break; - } - - case SDL_MOUSEWHEEL: - if (e.wheel.y != 0) - RunnerMouse_onWheel( - gRunner->mouse, - (float)e.wheel.y - ); - break; - - case SDL_MOUSEMOTION: - Runner_updateMousePosition( - gRunner, - e.motion.windowID ? 1280 : 1280, - 720, - e.motion.x, - e.motion.y - ); - break; - - default: - break; - } - } -} - -static void runFrame(void) -{ - static uint64_t lastTime = 0; - uint64_t now = nowNanos(); - - if (lastTime == 0) - lastTime = now; - - gRunner->deltaTime = - (double)(now - lastTime) / 1000.0; - - lastTime = now; - - int32_t winW = 0; - int32_t winH = 0; - - if (!webosGetWindowSize(&winW, &winH)) { - winW = 1280; - winH = 720; - } - - Runner_step(gRunner); - - if (gRunner->audioSystem != NULL) { - float audioDt = - (float)(gRunner->deltaTime / 1000000.0); - - if (audioDt < 0.0f) - audioDt = 0.0f; - - if (audioDt > 0.1f) - audioDt = 0.1f; - - gRunner->audioSystem->vtable->update( - gRunner->audioSystem, - audioDt - ); - } - - int32_t gameW = - gRunner->dataWin->gen8.defaultWindowWidth; - - int32_t gameH = - gRunner->dataWin->gen8.defaultWindowHeight; - - if (gRunner->appSurfaceEnabled) { - if (gRunner->applicationWidth > 0) - gameW = gRunner->applicationWidth; - - if (gRunner->applicationHeight > 0) - gameH = gRunner->applicationHeight; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - Runner_drawPre(gRunner, winW, winH); - - Runner_beginFrame( - gRunner, - gameW, - gameH, - winW, - winH, - winW, - winH - ); - - Runner_drawViews( - gRunner, - gameW, - gameH, - false - ); - - gRunner->renderer->vtable->endFrameInit( - gRunner->renderer - ); - - Runner_drawPost(gRunner, winW, winH); - - gRunner->renderer->vtable->endFrameEnd( - gRunner->renderer - ); - - Runner_drawGUI( - gRunner, - winW, - winH, - gameW, - gameH - ); - - bool shouldSwap = (gRunner->pendingRoom == -1); - - Runner_handlePendingRoomChange(gRunner); - - if (shouldSwap) - SDL_GL_SwapWindow(gWindow); -} - -static void destroyRunner(void) +int main(int argc, char **argv) { - if (gRunner == NULL) - return; - - AudioSystem *audio = gRunner->audioSystem; - Renderer *renderer = gRunner->renderer; - DataWin *dataWin = gRunner->dataWin; - VMContext *vm = gRunner->vmContext; + (void)argc; - gRunner = NULL; + CommandLineArgs args = {0}; - if (audio != NULL) - audio->vtable->destroy(audio); + args.exitAtFrame = -1; - if (renderer != NULL) - renderer->vtable->destroy(renderer); +#ifdef ENABLE_VM_TRACING + args.traceBytecodeAfterFrame = 0; +#endif - Runner_free(gRunner); - VM_free(vm); - DataWin_free(dataWin); -} - -static void webosShutdownGraphics(void) -{ - if (gGLContext != NULL) { - SDL_GL_DeleteContext(gGLContext); - gGLContext = NULL; - } - - if (gWindow != NULL) { - SDL_DestroyWindow(gWindow); - gWindow = NULL; - } - - SDL_Quit(); -} - -int main(int argc, char **argv) -{ - const char *dataWinPath = "./data.win"; - const char *savesPath = "./saves"; + 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. + * 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; - if (!webosInitGraphics(1280, 720)) { - return 1; - } - - SDL_StartTextInput(); - - { - gInputLog = fopen("/tmp/butterscotch_input.log", "w"); - - if (gInputLog != NULL) { - int count = SDL_NumJoysticks(); - - fprintf(gInputLog, "SDL joystick count: %d\n", count); - - for (int i = 0; i < count; i++) { - const char *name = SDL_GameControllerNameForIndex(i); - - fprintf( - gInputLog, - "SDL joystick %d: isGameController=%d name=%s\n", - i, - SDL_IsGameController(i), - name ? name : "(null)" - ); - - if (gController == NULL && - SDL_IsGameController(i)) { - gController = SDL_GameControllerOpen(i); - - if (gController != NULL) { - fprintf( - gInputLog, - "Opened controller %d: %s\n", - i, - SDL_GameControllerName(gController) - ? SDL_GameControllerName(gController) - : "(unknown)" - ); - } else { - fprintf( - gInputLog, - "Failed to open controller %d: %s\n", - i, - SDL_GetError() - ); - } - } - } - - fflush(gInputLog); - } - } - - if (!startRunner(dataWinPath, savesPath)) { - logError("Could not start Butterscotch Runner\n"); - webosShutdownGraphics(); - return 1; - } - - bool running = true; - uint64_t nextFrameTime = nowNanos(); - - while (running && !gRunner->shouldExit) { - handleEvents(); - - if (gRunner->shouldExit) - break; - - runFrame(); - - if (gRunner->currentRoom != NULL && - gRunner->currentRoom->speed > 0) { - uint64_t targetFrameNs = - 1000000000ULL / (uint64_t)gRunner->currentRoom->speed; - - nextFrameTime += targetFrameNs; - - uint64_t now = nowNanos(); - - if (now > nextFrameTime + targetFrameNs * 4) { - nextFrameTime = now + targetFrameNs; - } else { - while (now < nextFrameTime) { - uint64_t remaining = nextFrameTime - now; - - if (remaining > 2000000ULL) { - SDL_Delay((uint32_t)((remaining - 1000000ULL) / 1000000ULL)); - } - - now = nowNanos(); - } - } - } else { - nextFrameTime = nowNanos(); - } - } + args.dataWinPath = "./data.win"; + args.saveFolder = "./saves"; - destroyRunner(); - SDL_StopTextInput(); - webosShutdownGraphics(); + int ret = loop(args, argv[0]); - return 0; + freeCommandLineArgs(&args); + return ret; } diff --git a/src/webos/stat_compat.c b/src/webos/stat_compat.c deleted file mode 100644 index 3a1f82ede..000000000 --- a/src/webos/stat_compat.c +++ /dev/null @@ -1,15 +0,0 @@ -#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/stb_impl.c b/src/webos/stb_impl.c index c349348c7..873f3656b 100644 --- a/src/webos/stb_impl.c +++ b/src/webos/stb_impl.c @@ -4,6 +4,3 @@ #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" - -#define STB_DS_IMPLEMENTATION -#include "stb_ds.h" From 4834bf28a41d334c7c4e715e51575b424d9d2b7a Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Mon, 31 Aug 2026 20:45:07 +0530 Subject: [PATCH 03/21] Address WebOS review feedback --- CMakeLists.txt | 12 +----------- README.md | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c6ec9317c..5128db7c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -95,8 +95,6 @@ if(AUDIO_BACKEND STREQUAL "") set(AUDIO_BACKEND "openal") elseif(PLATFORM STREQUAL "ps2") set(AUDIO_BACKEND "ps2") - elseif(PLATFORM STREQUAL "webos") - set(AUDIO_BACKEND "miniaudio") else() set(AUDIO_BACKEND "miniaudio") endif() @@ -166,15 +164,7 @@ else() endif() # Platform specific files builds -if(PLATFORM STREQUAL "webos") - file(GLOB PLATFORM_SOURCES - src/webos/main.c - src/webos/log.c - src/webos/glibc_compat.c - ) -else() - file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) -endif() +file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) if(PLATFORM STREQUAL "android") add_library(butterscotch SHARED ${SOURCES} ${PLATFORM_SOURCES} ${AUDIO_SOURCES}) diff --git a/README.md b/README.md index 68d6f5ed6..249116769 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,6 @@ Of course, there are exceptions that break game compatibility altogether: * PlayStation 2 * PlayStation 3 * PlayStation Vita -* Nintendo Switch * ...and maybe more in the future! Additionally, any platform with reasonably complete C and POSIX conformance should work, the following have been tested. From e328f63108c02bc9fad9836a6a5d0e0db599c566 Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Mon, 31 Aug 2026 20:54:32 +0530 Subject: [PATCH 04/21] Restore upstream README --- README.md | 70 ++++--------------------------------------------------- 1 file changed, 4 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 249116769..687311706 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,3 @@ -# note: this webOS port was developed with significant assistance from ChatGPT as a proof of concept. ChatGPT was used throughout development to understand the existing platform ports, write and adapt code, troubleshoot webOS SDK/CMake/toolchain issues, and debug the build. - -# CONTROLLER REQUIRED. The B button triggers the webOS Back action. Use the in-game controller configuration to remap the controls. Tested with Undertale 1.08. - -## current status: - -The port successfully builds using the webOS SDK and produces a 32-bit ARM EABI5 executable. - -The port has been tested on webOS 23. Further testing is welcome. - -(likely more issues) - -## how to use: - -1. Install the Butterscotch `.ipk`. -2. Copy the required game files to the installed application's directory using `scp`. -3. `data.win` is the minimum required file, but other game files may also be required depending on the game. -4. Launch Butterscotch from the webOS launcher. - -## how to build: - -1. Install and source the webOS SDK / Homebrew SDK. -2. Clone this repository. -3. Configure with: - - `/usr/bin/cmake -S . -B build-webos -DPLATFORM=webos -DENABLE_MODERN_GL=ON` - -4. Build with: - - `/usr/bin/cmake --build build-webos` - -This produces: - -`build-webos/butterscotch` - -## how to package and install: - -1. Prepare a package directory containing: - - `butterscotch` - - `appinfo.json` - - `icon.png` - -2. Package it: - - `ares-package ` - -3. Install it: - - `ares-install .ipk` - -4. Launch it: - - `ares-launch ` - -5. Copy the required game files into the installed application directory: - - `scp -r root@:/` - -The webOS SDK environment must be sourced so that the Ares tools are available. - -## original readme: - -
Butterscotch Logo
@@ -126,10 +63,11 @@ Of course, there are exceptions that break game compatibility altogether: * PlayStation 2 * PlayStation 3 * PlayStation Vita +* Nintendo Switch * ...and maybe more in the future! Additionally, any platform with reasonably complete C and POSIX conformance should work, the following have been tested. -* Linux with glibc as old as about ~1996 +* Linux with glibc as old as about ~1995 * FreeBSD as old as 2.2.8 * OpenBSD * NetBSD @@ -158,11 +96,11 @@ The following compilers have been tested to successfully build butterscotch, old ```bash mkdir build && cd build -cmake -DPLATFORM=desktop -DDESKTOP_BACKEND=glfw3 -DCMAKE_BUILD_TYPE=Debug .. +cmake -DBACKEND=glfw3 -DCMAKE_BUILD_TYPE=Debug .. make ``` -If you are using CLion, set the platform in `Settings` > `Build, Execution, Deployment` > `CMake` and add `-DDESKTOP_BACKEND=glfw3` +If you are using CLion, set the platform in `Settings` > `Build, Execution, Deployment` > `CMake` and add `-DBACKEND=glfw3` Then run Butterscotch with `./butterscotch /path/to/data.win`! From d9da6a86034da460ed4dc4dae25504e6e60e57e7 Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Mon, 31 Aug 2026 21:07:09 +0530 Subject: [PATCH 05/21] Address remaining WebOS review feedback --- CMakeLists.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5128db7c6..cbd647092 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,10 +434,6 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "swi nx_create_nro(butterscotch NACP Butterscotch.nacp) endif() endif() - if(PLATFORM STREQUAL "webos") - target_sources(butterscotch PRIVATE src/webos/stb_impl.c) - target_link_libraries(butterscotch PRIVATE GLESv2 EGL) - endif() if(BACKEND STREQUAL "appkit") target_sources(butterscotch PRIVATE src/backends/${BACKEND}.m) From e47213e9ee2f891b50edfa0c62dbfd3ccb69a256 Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Mon, 31 Aug 2026 21:11:24 +0530 Subject: [PATCH 06/21] Remove extra blank line --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbd647092..d8b96dc30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,7 +161,6 @@ else() if(PLATFORM STREQUAL "ps2" OR PLATFORM STREQUAL "ps3" OR PLATFORM STREQUAL "web" OR PLATFORM STREQUAL "android") list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/loop.c") endif() - endif() # Platform specific files builds file(GLOB PLATFORM_SOURCES src/${PLATFORM}/*.c) From 1beff80bbc58b07901fb9b5edd33ef0e980d6f1a Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 16:57:22 +0100 Subject: [PATCH 07/21] webOS CI --- .github/workflows/build-other.yml | 52 +++++++++++++++++++++++++++++- src/webos/dist/appinfo.json | 9 ++++++ src/webos/dist/icon.png | Bin 0 -> 1063 bytes 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/webos/dist/appinfo.json create mode 100644 src/webos/dist/icon.png diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index 7a1519ab5..564f6baa3 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -90,4 +90,54 @@ 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/ + + - 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-x86_64/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/ + + - name: Upload IPK + uses: actions/upload-artifact@v7 + with: + name: butterscotch-webos + path: dist/com.mrpowergamerbr.butterscotch_1.0.0_all.ipk \ No newline at end of file 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 0000000000000000000000000000000000000000..9ab77c40ff09480ca4a39197222975e43ec3705e GIT binary patch literal 1063 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7+9Er6kFKIlR!#6z$e7@`|jCkuwat}iap~kklgy>_{$roH*fqd z>vSE5vIM==c^9>;dgInF`*msAuS)-nTc98+W?r#&>TP>91_$v2q1m&4*ZVLuoHb%R zb=NEdDE)%@#-Fc-2~6weu)Jktn8X+}v*Cw?gpJXB7V~cm46+QHeuaGuGe8cp;G6}gW-{n44*jBMHS^8UtmSr$AaP94@OtsRR{t`&xo+#v z9k*L2_u_Na8lQU;>VYa97-T-DE4H&s+&>Eniv&J~kDDj_XARhJ+JQln*Qr`(yH}9ofc9KHH;QA2Y!gDfgE7W z+@a6F&Z@CCFJ4VS(_te!!-*Q9p5O7pKr6i(cKr9P1o8SA8Z4P&+}fjFmE6CUd>`oi z4<-zc{#^$-MM2Tw&i`2rOBjwSGaOKhh}H(0+c1Tp@c(Qfg?II~z#uszknq2rZO!6s zuY(q|hw}oZnVERRZ|r71Qa@FkGr$*U$VtXi-?p+l?7YqDd%%d%OIqt&LoMSf_k@WI zg~gkndlqbDxXQv%F~`4gQYq7^L+2TP=Qpk0lEu%kA)ul3%H}KG?h#ogd$WKBo#9c) zo3NQF1mymrhE26gSy#MW_ec=vOnI;~w=meTw#{MQ#Kd#I?JeU~wI42wWihXQ1x^R% zo(4&VBY*Rw8nS?H_TUTHwtfrH7q2n&8ZjODyWV!eEtX)$FFIdy{x1c^Iu_8dI0`7g zEPW*wIgn678-uNPt(hzi=?ET&^Fg>03ZDF=;`!>m`R!S{2an0tOaYng>FVdQ&MBb@ E0JT(8uK)l5 literal 0 HcmV?d00001 From ea5fd55089dc420ac20a9f609c9a8cc2359b8870 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 16:57:49 +0100 Subject: [PATCH 08/21] readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 687311706..75f79a57d 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,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. From 9989ff294f97ed86336d028ea711efeb14deab98 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 17:03:08 +0100 Subject: [PATCH 09/21] futureproof --- .github/workflows/build-other.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index 564f6baa3..3f6deca22 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -134,10 +134,10 @@ jobs: cp build/butterscotch dist/ cp src/webos/dist/appinfo.json dist/ cp src/webos/dist/icon.png dist/ - ares-package dist/ + ares-package dist/ -o dist/ - name: Upload IPK uses: actions/upload-artifact@v7 with: name: butterscotch-webos - path: dist/com.mrpowergamerbr.butterscotch_1.0.0_all.ipk \ No newline at end of file + path: dist/*.ipk \ No newline at end of file From 7135af4657df9554110718f1066e4402393ebb7f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 17:15:50 +0100 Subject: [PATCH 10/21] whoops --- .github/workflows/build-other.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index 3f6deca22..cdf295de4 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -99,15 +99,13 @@ jobs: - uses: actions/checkout@v7 - name: Set up webOS toolchain - run: + 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 + 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/ - uses: actions/cache@v6 From 2c75b0e42f7cae8d981b0ab8a51d0f8afe21b583 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 17:17:53 +0100 Subject: [PATCH 11/21] toolchainfile --- .github/workflows/build-other.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index cdf295de4..6effbf6a5 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -119,7 +119,7 @@ jobs: run: | cmake -B build -G Ninja \ -DWERROR=ON -DPLATFORM=webos \ - -DCMAKE_TOOLCHAIN_FILE=/opt/arm-webos-linux-gnueabi_sdk-buildroot-x86_64/share/buildroot/toolchainfile.cmake \ + -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 \ From edee465d5d16c9860d29280804df24f4e14d2214 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 17:19:47 +0100 Subject: [PATCH 12/21] ccache --- .github/workflows/build-other.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-other.yml b/.github/workflows/build-other.yml index 6effbf6a5..a502a579d 100644 --- a/.github/workflows/build-other.yml +++ b/.github/workflows/build-other.yml @@ -108,6 +108,9 @@ jobs: 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 From 4029fdc9187b0b731e30c42c6c93b084a328fb34 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 19:16:45 +0100 Subject: [PATCH 13/21] fix fwrite warns for webos --- src/input_recording.c | 8 +++++++- src/loop.c | 24 ++++++++++++++++-------- vendor/stb/image/stb_image_write.h | 9 +++++++-- 3 files changed, 30 insertions(+), 11 deletions(-) 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 2f4ad008d..282d70737 100644 --- a/src/loop.c +++ b/src/loop.c @@ -1046,10 +1046,14 @@ int loop(CommandLineArgs args, const char *argv0) { 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); + size_t len = strlen(json); + if (fwrite(json, 1, len, f) != len) { + logWarn("Error: Could not write JSON dump to '%s'\n", filename); + } else { + fputc('\n', f); + fclose(f); + logInfo("JSON dump saved: %s\n", filename); + } } else { logWarn("Could not write JSON dump to '%s'\n", filename); } @@ -1154,10 +1158,14 @@ int loop(CommandLineArgs args, const char *argv0) { 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); + size_t len = strlen(json); + if (fwrite(json, 1, len, f) != len) { + logWarn("Error: Could not write JSON dump to '%s'\n", filename); + } else { + fputc('\n', f); + fclose(f); + logInfo("JSON dump saved: %s\n", filename); + } } else { logWarn("Could not write JSON dump to '%s'\n", filename); } diff --git a/vendor/stb/image/stb_image_write.h b/vendor/stb/image/stb_image_write.h index e4b32ed1b..86d60afe1 100644 --- a/vendor/stb/image/stb_image_write.h +++ b/vendor/stb/image/stb_image_write.h @@ -283,7 +283,8 @@ static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func static void stbi__stdio_write(void *context, void *data, int size) { - fwrite(data,1,size,(FILE*) context); + size_t len = (size_t)fwrite(data, 1, size, (FILE*)context); + (void)len; } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) @@ -1221,7 +1222,11 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); + if (fwrite(png, 1, len, f) != (size_t)len) { + fclose(f); + STBIW_FREE(png); + return 0; + } fclose(f); STBIW_FREE(png); return 1; From 77693d83f3b223d63d58715da2c3784964a23630 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 31 Aug 2026 19:17:54 +0100 Subject: [PATCH 14/21] fclose --- src/loop.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/loop.c b/src/loop.c index 282d70737..25416f8a3 100644 --- a/src/loop.c +++ b/src/loop.c @@ -1049,6 +1049,7 @@ int loop(CommandLineArgs args, const char *argv0) { 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); @@ -1161,6 +1162,7 @@ int loop(CommandLineArgs args, const char *argv0) { 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); From eb1f46a3c722db6790e5ecd74592a3258523b6e7 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 1 Sep 2026 09:04:38 +0100 Subject: [PATCH 15/21] pragma --- vendor/stb/image/stb_image_write.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/vendor/stb/image/stb_image_write.h b/vendor/stb/image/stb_image_write.h index 86d60afe1..95d79567f 100644 --- a/vendor/stb/image/stb_image_write.h +++ b/vendor/stb/image/stb_image_write.h @@ -283,8 +283,10 @@ static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func static void stbi__stdio_write(void *context, void *data, int size) { - size_t len = (size_t)fwrite(data, 1, size, (FILE*)context); - (void)len; + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-result" + fwrite(data, 1, size, (FILE*)context); + #pragma GCC diagnostic pop } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) @@ -1222,11 +1224,10 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } - if (fwrite(png, 1, len, f) != (size_t)len) { - fclose(f); - STBIW_FREE(png); - return 0; - } + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-result" + fwrite(png, 1, len, f); + #pragma GCC diagnostic pop fclose(f); STBIW_FREE(png); return 1; From 7bd5c2956c833968febfbfc2b7a6f97655a49d27 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 1 Sep 2026 09:11:56 +0100 Subject: [PATCH 16/21] webos entry readme, notes to be filled by sankar --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a61aab7b..c359b414d 100644 --- a/README.md +++ b/README.md @@ -121,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) | place notes here | ## Community Ports From e63d1280a2475f91e81515c80627e1172431989f Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Tue, 1 Sep 2026 16:24:18 +0530 Subject: [PATCH 17/21] Document webOS game file location --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c359b414d..b4bba5df3 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ All RISC architecture (ARM, MIPS, PowerPC, RISC-V) builds require hardware float | -------- | -------- | ----- | | 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) | -| webOS | [butterscotch-webos.zip](https://nightly.link/ButterscotchRunner/Butterscotch/workflows/build/main/butterscotch-webos.zip) | place notes here | +| 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 From d2394da779cd4e67692e223a7b33ca8782612e7e Mon Sep 17 00:00:00 2001 From: SankarRealm Date: Tue, 1 Sep 2026 17:20:39 +0530 Subject: [PATCH 18/21] removed extra line at 436 --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d8b96dc30..30bfd1b99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,7 +433,6 @@ if(PLATFORM STREQUAL "cli" OR PLATFORM STREQUAL "vita" OR PLATFORM STREQUAL "swi nx_create_nro(butterscotch NACP Butterscotch.nacp) endif() endif() - if(BACKEND STREQUAL "appkit") target_sources(butterscotch PRIVATE src/backends/${BACKEND}.m) else() From 9d2aceacb002bd5a3fee26eac6e693e182a594a8 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 1 Sep 2026 15:08:56 +0100 Subject: [PATCH 19/21] pragma --- src/webos/stb_impl.c | 3 +++ vendor/stb/image/stb_image_write.h | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/webos/stb_impl.c b/src/webos/stb_impl.c index 873f3656b..d99b31ef3 100644 --- a/src/webos/stb_impl.c +++ b/src/webos/stb_impl.c @@ -3,4 +3,7 @@ #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 diff --git a/vendor/stb/image/stb_image_write.h b/vendor/stb/image/stb_image_write.h index 95d79567f..1eb03cc29 100644 --- a/vendor/stb/image/stb_image_write.h +++ b/vendor/stb/image/stb_image_write.h @@ -283,10 +283,7 @@ static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func static void stbi__stdio_write(void *context, void *data, int size) { - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-result" fwrite(data, 1, size, (FILE*)context); - #pragma GCC diagnostic pop } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) @@ -1224,10 +1221,7 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const f = stbiw__fopen(filename, "wb"); if (!f) { STBIW_FREE(png); return 0; } - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-result" fwrite(png, 1, len, f); - #pragma GCC diagnostic pop fclose(f); STBIW_FREE(png); return 1; From 0c8752d5bf2b37a14aa7fff81c16acfa48e105f0 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 1 Sep 2026 15:57:24 +0100 Subject: [PATCH 20/21] you're either a smart fella or --- vendor/stb/image/stb_image_write.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/image/stb_image_write.h b/vendor/stb/image/stb_image_write.h index 1eb03cc29..717b38858 100644 --- a/vendor/stb/image/stb_image_write.h +++ b/vendor/stb/image/stb_image_write.h @@ -283,7 +283,7 @@ static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func static void stbi__stdio_write(void *context, void *data, int size) { - fwrite(data, 1, size, (FILE*)context); + fwrite(data,1,size,(FILE*) context); } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) From 2663639117ceb9dfc4ec57836b5e0353c2cf353a Mon Sep 17 00:00:00 2001 From: Cobalt <65132371+cobaltgit@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:00:05 +0100 Subject: [PATCH 21/21] fart smella --- vendor/stb/image/stb_image_write.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/image/stb_image_write.h b/vendor/stb/image/stb_image_write.h index 717b38858..e4b32ed1b 100644 --- a/vendor/stb/image/stb_image_write.h +++ b/vendor/stb/image/stb_image_write.h @@ -283,7 +283,7 @@ static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func static void stbi__stdio_write(void *context, void *data, int size) { - fwrite(data,1,size,(FILE*) context); + fwrite(data,1,size,(FILE*) context); } #if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8)