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. diff --git a/cmake/sdl.cmake b/cmake/sdl.cmake index 1ea42223..01b311ae 100644 --- a/cmake/sdl.cmake +++ b/cmake/sdl.cmake @@ -30,6 +30,18 @@ if(BUILD_SDL AND NOT EMSCRIPTEN AND NOT RPI AND NOT PREFER_SYSTEM_LIBRARIES) 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/src/studio/studio.c b/src/studio/studio.c index b9430911..4259b8cb 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 @@ -235,13 +244,6 @@ struct Studio void *userdata; }; -#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) @@ -1674,18 +1676,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)) @@ -1707,7 +1772,7 @@ static void startVideoRecord(Studio* studio) { if(studio->video.record) { - stopVideoRecord(studio, VideoGif); + stopVideoRecord(studio); } else { @@ -2044,7 +2109,7 @@ static void recordFrame(Studio* studio, u32* pixels) if(studio->video.screenshot) { studio->video.screenshot = false; - stopVideoRecord(studio, ScreenGif); + stopVideoRecord(studio); return; } 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; 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