From 86a4cc3a92c8cb95241d3e157143d14364c32402 Mon Sep 17 00:00:00 2001 From: imsys <911254+imsys@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:43:55 -0300 Subject: [PATCH 1/5] Update SDL to release-2.32.10 (pipewire fix) (#2827) * Update SDL2 submodule to release-2.32.10 * SDL2 update MSVC fix Tries to ensure SDL2 builds with the same runtime as the rest of the project * Another try to compile the updated SDL2 on MSVC. * Yet another try to compile the updated SDL2 on MSVC. * Directly link libs for SDL2 win compiling * SDL2 update linking MSVC try using CMP0079 * another sdl2 win build try --- cmake/sdl.cmake | 13 +++++++++++++ vendor/sdl2 | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/cmake/sdl.cmake b/cmake/sdl.cmake index 0311e870..7c09df7b 100644 --- a/cmake/sdl.cmake +++ b/cmake/sdl.cmake @@ -24,8 +24,21 @@ if(BUILD_SDL AND NOT EMSCRIPTEN AND NOT RPI AND NOT PREFER_SYSTEM_LIBRARIES) set(SDL_STATIC_PIC ON CACHE BOOL "" FORCE) endif() + add_subdirectory(${THIRDPARTY_DIR}/sdl2) + if(MSVC) + # CMake policy CMP0079 + # This allows linking libraries to targets not built in the current directory. + cmake_policy(SET CMP0079 NEW) + + target_link_libraries(SDL2 PRIVATE + libcmt.lib + libvcruntime.lib + libucrt.lib + ) + endif() + endif() ################################ diff --git a/vendor/sdl2 b/vendor/sdl2 index f070c83a..5d249570 160000 --- a/vendor/sdl2 +++ b/vendor/sdl2 @@ -1 +1 @@ -Subproject commit f070c83a6059c604cbd098680ddaee391b0a7341 +Subproject commit 5d249570393f7a37e037abf22cd6012a4cc56a71 From cd2be4f659494030a34a17f1ac4abd82ca1fd8ef Mon Sep 17 00:00:00 2001 From: imsys <911254+imsys@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:50:04 -0300 Subject: [PATCH 2/5] Informative screenshot filenames (#2826) * Informative screenshot filenames screenshots and recordings now contain the rom name + datetime * Informative screenshot filenames - multiplataform fix In my last commit, the code to get the milliseconds would not work on Windows. This new code should be able to compile on Windows too. * Informative screenshot filenames - multiplataform fix2 --- src/studio/studio.c | 99 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 17 deletions(-) diff --git a/src/studio/studio.c b/src/studio/studio.c index 3f1ce83b..fa7e95e8 100644 --- a/src/studio/studio.c +++ b/src/studio/studio.c @@ -24,6 +24,12 @@ #if defined(BUILD_EDITORS) +#if defined(_WIN32) +#include +#else +#include +#endif + #include "editors/code.h" #include "editors/sprite.h" #include "editors/map.h" @@ -67,6 +73,9 @@ #define MD5_HASHSIZE 16 +// interval between the Windows and Unix epoch +#define UNIX_EPOCH_IN_FILETIME 116444736000000000ULL + #if defined(TIC80_PRO) #define TIC_EDITOR_BANKS (TIC_BANKS) #else @@ -230,13 +239,6 @@ struct Studio }; -#if defined(BUILD_EDITORS) - -static const char VideoGif[] = "video%i.gif"; -static const char ScreenGif[] = "screen%i.gif"; - -#endif - static void emptyDone(void* data) {} void fadePalette(tic_palette* pal, s32 value) @@ -1663,18 +1665,81 @@ static void setCoverImage(Studio* studio) } } -static void stopVideoRecord(Studio* studio, const char* name) +static void generateScreenshotName(Studio* studio, const char* extension, char filenameOut[TICNAME_MAX]) +{ + const char* romName = studio->console->rom.name; + + // --- Strip extension --- + const char* dot = strrchr(romName, '.'); + size_t baseLen = dot ? (size_t)(dot - romName) : strlen(romName); + + // --- Get current time with millisecond precision --- + time_t sec = 0; + long msec = 0; + +#if defined(_WIN32) + // Windows: Use GetSystemTimeAsFileTime for high precision. + // It provides time in 100-nanosecond intervals since Jan 1, 1601. + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + + // Convert FILETIME to Unix seconds and milliseconds + sec = (time_t)((uli.QuadPart - UNIX_EPOCH_IN_FILETIME) / 10000000ULL); + msec = (long)((uli.QuadPart / 10000) % 1000); +#else + // Other systems (Linux, macOS, 3DS, etc.): Use gettimeofday, which is widely available. + struct timeval tv; + gettimeofday(&tv, NULL); + sec = tv.tv_sec; + msec = (long)(tv.tv_usec / 1000); +#endif + + // --- Convert seconds to a local time structure (thread-safe) --- + struct tm tm_local; + + // Thread-safe localtime +#if defined(_WIN32) + // Windows secure version + localtime_s(&tm_local, &sec); +#else + // POSIX re-entrant version + localtime_r(&sec, &tm_local); +#endif + + // --- Format the timestamp string --- + char timestamp[32] = {0}; + + // Format as -yymmdd-hhmmss + strftime(timestamp, sizeof(timestamp), "-%y%m%d-%H%M%S", &tm_local); + + // Append milliseconds + size_t len = strlen(timestamp); + snprintf(timestamp + len, sizeof(timestamp) - len, "-%03ld", msec); + + // --- Adjust baseLen to prevent overflow --- + size_t maxBaseLen = TICNAME_MAX + - strlen(timestamp) + - strlen(extension) + - 1; // for null terminator + + if (baseLen > maxBaseLen) + baseLen = maxBaseLen; + + // --- Build the final filename string --- + snprintf(filenameOut, TICNAME_MAX, "%.*s%s%s", + (int)baseLen, romName, timestamp, extension); +} + +static void stopVideoRecord(Studio* studio) { MsfGifResult result = msf_gif_end(&studio->video.gif); - // Find an available filename to save. - s32 i = 0; char filename[TICNAME_MAX]; - do - { - snprintf(filename, sizeof filename, name, ++i); - } - while(tic_fs_exists(studio->fs, filename)); + generateScreenshotName(studio, ".gif", filename); // Now that it has found an available filename, save it. if(tic_fs_save(studio->fs, filename, result.data, result.dataSize, true)) @@ -1696,7 +1761,7 @@ static void startVideoRecord(Studio* studio) { if(studio->video.record) { - stopVideoRecord(studio, VideoGif); + stopVideoRecord(studio); } else { @@ -2037,7 +2102,7 @@ static void recordFrame(Studio* studio, u32* pixels) if(studio->video.screenshot) { studio->video.screenshot = false; - stopVideoRecord(studio, ScreenGif); + stopVideoRecord(studio); return; } From d531774efba27ddfc4abfe66c30228ca91c7b41b Mon Sep 17 00:00:00 2001 From: imsys <911254+imsys@users.noreply.github.com> Date: Mon, 8 Sep 2025 13:51:44 -0300 Subject: [PATCH 3/5] Libretro cursorfix (#2822) * [libretro] draw cursor directly to screen This fixes some bugs where the cursor were not drawn in some games, and also causing a cursor trail in other games. * [libretro] Makes the cursor to start hidden As many games don't use mouse, we keep it hidden while it was not moved. Those wanting to take screenshots of the titlescreen don't risk taking a screenshot of the cursor. --- src/system/libretro/tic80_libretro.c | 84 ++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/src/system/libretro/tic80_libretro.c b/src/system/libretro/tic80_libretro.c index 14462902..fc3fcb1e 100644 --- a/src/system/libretro/tic80_libretro.c +++ b/src/system/libretro/tic80_libretro.c @@ -765,6 +765,49 @@ void tic80_libretro_update_mouse(tic80_mouse* mouse) mouse->y = state->mouseY + TIC80_OFFSET_TOP; } +/** + * Gets the 32-bit color value from the TIC-80 palette. + */ +static u32 get_screen_color(tic_mem* tic, u8 index) +{ + tic_rgb color = tic->ram->vram.palette.colors[index]; + // The core requests RETRO_PIXEL_FORMAT_XRGB8888, so we format the color as 0x00RRGGBB. + return (color.r << 16) | (color.g << 8) | (color.b); +} + +/** + * Draws a single pixel directly to the final screen buffer. + */ +static void draw_pixel_on_screen(u32* screen, s32 x, s32 y, u32 color) +{ + // Bounds check against the visible screen area + if (x < 0 || x >= TIC80_WIDTH || y < 0 || y >= TIC80_HEIGHT) + return; + + s32 full_x = x + TIC80_OFFSET_LEFT; + s32 full_y = y + TIC80_OFFSET_TOP; + + screen[full_y * TIC80_FULLWIDTH + full_x] = color; +} + +/** + * Draws a horizontal line directly to the final screen buffer. + */ +static void draw_hline_on_screen(u32* screen, s32 x1, s32 x2, s32 y, u32 color) +{ + for (s32 x = x1; x <= x2; x++) + draw_pixel_on_screen(screen, x, y, color); +} + +/** + * Draws a vertical line directly to the final screen buffer. + */ +static void draw_vline_on_screen(u32* screen, s32 x, s32 y1, s32 y2, u32 color) +{ + for (s32 y = y1; y <= y2; y++) + draw_pixel_on_screen(screen, x, y, color); +} + /** * Draws a software cursor on the screen where the mouse is. */ @@ -777,26 +820,45 @@ void tic80_libretro_mousecursor(tic80* game, tic80_mouse* mouse, enum mouse_curs return; } - tic_mem* tic = (tic_mem*)state->tic; + tic_mem* tic = (tic_mem*)game; + u32* screen = game->screen; + s32 mx = state->mouseX; + s32 my = state->mouseY; - // Draw the cursor. - // TODO: Fix the cursor not being drawn on the screen by possibly modifing game->screen directly. + // Calculate the final 32-bit color value + u32 cursor_color = get_screen_color(tic, state->mouseCursorColor); + + // Draw the cursor directly to the screen buffer. switch (cursortype) { case MOUSE_CURSOR_NONE: // Nothing. break; case MOUSE_CURSOR_DOT: - tic_api_pix(tic, state->mouseX, state->mouseY, state->mouseCursorColor, false); + draw_pixel_on_screen(screen, mx, my, cursor_color); break; case MOUSE_CURSOR_CROSS: - tic_api_line(tic, state->mouseX - 4, state->mouseY, state->mouseX - 2, state->mouseY, state->mouseCursorColor); - tic_api_line(tic, state->mouseX + 2, state->mouseY, state->mouseX + 4, state->mouseY, state->mouseCursorColor); - tic_api_line(tic, state->mouseX, state->mouseY - 4, state->mouseX, state->mouseY - 2, state->mouseCursorColor); - tic_api_line(tic, state->mouseX, state->mouseY + 2, state->mouseX, state->mouseY + 4, state->mouseCursorColor); + draw_hline_on_screen(screen, mx - 4, mx - 2, my, cursor_color); + draw_hline_on_screen(screen, mx + 2, mx + 4, my, cursor_color); + draw_vline_on_screen(screen, mx, my - 4, my - 2, cursor_color); + draw_vline_on_screen(screen, mx, my + 2, my + 4, cursor_color); break; case MOUSE_CURSOR_ARROW: - tic_api_tri(tic, state->mouseX, state->mouseY, state->mouseX + 3, state->mouseY, state->mouseX, state->mouseY + 3, state->mouseCursorColor); - tic_api_line(tic, state->mouseX + 3, state->mouseY, state->mouseX, state->mouseY + 3, tic_color_black); + { + // Calculate black for the outline. + u32 black_color = get_screen_color(tic, tic_color_black); + + // Draw the filled triangle part of the arrow + for (int y = 0; y <= 2; y++) { + for (int x = 0; x <= 2 - y; x++) { + draw_pixel_on_screen(screen, mx + x, my + y, cursor_color); + } + } + // Draw the black outline (hypotenuse of the triangle) + draw_pixel_on_screen(screen, mx + 3, my, black_color); + draw_pixel_on_screen(screen, mx + 2, my + 1, black_color); + draw_pixel_on_screen(screen, mx + 1, my + 2, black_color); + draw_pixel_on_screen(screen, mx, my + 3, black_color); + } break; } } @@ -972,7 +1034,7 @@ void tic80_libretro_variables(bool startup) state->mouseHideTimerStart = atoi(var.value); if (state->mouseHideTimerStart > 0) { state->mouseHideTimerStart = state->mouseHideTimerStart * TIC80_FRAMERATE; - state->mouseHideTimer = state->mouseHideTimerStart; + state->mouseHideTimer = 0; // Cursor starts hidden } else { state->mouseHideTimerStart = 0; From a4c75d3092517322d255e4b161fb05ed6385e73c Mon Sep 17 00:00:00 2001 From: Softkandi Date: Mon, 8 Sep 2025 18:54:19 +0200 Subject: [PATCH 4/5] Fix saving options to JSON (#2790) Remove the JSON macro, which doesn't work as intended because it literally pastes preprocessor code into the options file, corrupting it. --- src/studio/config.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/studio/config.c b/src/studio/config.c index a7078bdf..1879d8e7 100644 --- a/src/studio/config.c +++ b/src/studio/config.c @@ -37,8 +37,6 @@ #define INTEGER_SCALE_DEFAULT true #endif -#define JSON(...) #__VA_ARGS__ - static void readConfig(Config* config) { const char* json = config->cart->code.data; @@ -249,24 +247,24 @@ static void saveOptions(Config* config) const struct StudioOptions* options = &config->data.options; string buf; - sprintf(buf.data, JSON( - { + sprintf(buf.data, + "{ " #if defined(CRT_SHADER_SUPPORT) - "crt":%s, + "\"crt\":%s, " #endif - "fullscreen":%s, - "vsync":%s, - "integerScale":%s, - "volume":%i, - "autosave":%s, - "mapping":"%s" + "\"fullscreen\":%s, " + "\"vsync\":%s, " + "\"integerScale\":%s, " + "\"volume\":%i, " + "\"autosave\":%s, " + "\"mapping\":\"%s\"" #if defined(BUILD_EDITORS) - , - "keybindMode":%i, - "tabMode":%i, - "tabSize":%i + ", " + "\"keybindMode\":%i, " + "\"tabMode\":%i, " + "\"tabSize\":%i" #endif - }) + " }" , #if defined(CRT_SHADER_SUPPORT) bool2str(options->crt), From 1f7f7ae71884882d058a33f4ca8bab27d07139c7 Mon Sep 17 00:00:00 2001 From: Will Green Date: Mon, 8 Sep 2025 18:55:33 +0200 Subject: [PATCH 5/5] Update macOS build instructions (#2788) 1. DCMAKE_POLICY_VERSION_MINIMUM=3.5 required with brew version of cmake 2. Correct binary name for chmod +x (binary is called tic80) 3. Renamed app from TIC80DEV to tic80dev for consistency with release name --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4fd246d3..5603d702 100644 --- a/README.md +++ b/README.md @@ -337,20 +337,20 @@ run the following commands in the Terminal ``` brew install git cmake git clone --recursive https://github.com/nesbox/TIC-80 && cd TIC-80/build -cmake -DBUILD_WITH_ALL=On .. +cmake -DBUILD_WITH_ALL=On -DCMAKE_POLICY_VERSION_MINIMUM=3.5 .. make -j4 ``` to create application icon for development version ``` -mkdir -p ~/Applications/TIC80dev.app/Contents/{MacOS,Resources} -cp -f macosx/tic80.plist ~/Applications/TIC80dev.app/Contents/Info.plist -cp -f macosx/tic80.icns ~/Applications/TIC80dev.app/Contents/Resources -cat > ~/Applications/TIC80dev.app/Contents/MacOS/tic80 < ~/Applications/tic80dev.app/Contents/MacOS/tic80 </dev/null +exec /Users/nesbox/projects/TIC-80/build/bin/tic80 --skip >/dev/null EOF -chmod +x ~/Applications/TIC80dev.app/Contents/MacOS/TIC80dev +chmod +x ~/Applications/tic80dev.app/Contents/MacOS/tic80 ``` Make sure to update the absolute path to the tic80 binary in the script, or update the launch arguments.