diff --git a/demo/cart.fnl b/demo/cart.fnl new file mode 100644 index 0000000..bb0e721 --- /dev/null +++ b/demo/cart.fnl @@ -0,0 +1,4 @@ +{:title "pxl8 demo" + :resolution "640x360" + :window-size [1280 720] + :pixel-mode "indexed"} diff --git a/src/pxl8.c b/src/pxl8.c index 2057ad6..3da2efd 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -13,11 +13,14 @@ #include "pxl8_game.h" #include "pxl8_hal.h" #include "pxl8_log.h" +#include "pxl8_macros.h" #include "pxl8_repl.h" #include "pxl8_script.h" #include "pxl8_sys.h" #include "pxl8_types.h" +pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart); + struct pxl8 { pxl8_cart* cart; pxl8_game* game; @@ -65,8 +68,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT; pxl8_game* game = sys->game; - pxl8_pixel_mode pixel_mode = PXL8_PIXEL_INDEXED; - pxl8_resolution resolution = PXL8_RESOLUTION_640x360; const char* script_arg = NULL; bool bundle_mode = false; bool pack_mode = false; @@ -116,13 +117,60 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return result; } - if (game->repl_mode) { - pxl8_info("starting in REPL mode with script: %s", game->script_path); - } - pxl8_info("starting up"); - sys->platform_data = sys->hal->create(pixel_mode, resolution, "pxl8", 1280, 720); + game->script = pxl8_script_create(game->repl_mode); + if (!game->script) { + pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); + return PXL8_ERROR_INITIALIZATION_FAILED; + } + + bool has_embedded = pxl8_cart_has_embedded(argv[0]); + const char* cart_path = script_arg; + char* original_cwd = getcwd(NULL, 0); + + bool load_embedded = has_embedded && !script_arg; + bool load_from_path = false; + + if (!load_embedded && (cart_path || !has_embedded)) { + if (!cart_path) cart_path = "demo"; + struct stat st; + load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || + (cart_path && strstr(cart_path, ".pxc")); + } + + if (load_embedded || load_from_path) { + sys->cart = pxl8_cart_create(); + pxl8_result load_result = load_embedded + ? pxl8_cart_load_embedded(sys->cart, argv[0]) + : pxl8_cart_load(sys->cart, cart_path); + + if (!sys->cart || load_result != PXL8_OK) { + pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : ""); + if (sys->cart) pxl8_cart_destroy(sys->cart); + sys->cart = NULL; + free(original_cwd); + return PXL8_ERROR_INITIALIZATION_FAILED; + } + + pxl8_cart_mount(sys->cart); + pxl8_script_load_cart_manifest(game->script, sys->cart); + if (load_from_path) { + pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd); + } + pxl8_strncpy(game->script_path, "main.fnl", sizeof(game->script_path)); + } else if (script_arg) { + pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path)); + } + free(original_cwd); + + const char* window_title = pxl8_cart_get_title(sys->cart); + if (!window_title) window_title = "pxl8"; + pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart); + pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart); + pxl8_size window_size = pxl8_cart_get_window_size(sys->cart); + + sys->platform_data = sys->hal->create(pixel_mode, resolution, window_title, window_size.w, window_size.h); if (!sys->platform_data) { pxl8_error("failed to create platform context"); return PXL8_ERROR_INITIALIZATION_FAILED; @@ -139,58 +187,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) { return PXL8_ERROR_INITIALIZATION_FAILED; } - game->script = pxl8_script_create(game->repl_mode); - if (!game->script) { - pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script)); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - - bool has_embedded = pxl8_cart_has_embedded(argv[0]); - const char* cart_path = script_arg; - - if (has_embedded && !script_arg) { - sys->cart = pxl8_cart_create(); - if (!sys->cart) { - pxl8_error("failed to create cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - if (pxl8_cart_load_embedded(sys->cart, argv[0]) == PXL8_OK) { - pxl8_cart_mount(sys->cart); - strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - pxl8_info("running embedded cart"); - } else { - pxl8_error("failed to load embedded cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - } else if (cart_path || !has_embedded) { - if (!cart_path) cart_path = "demo"; - - struct stat st; - bool is_cart = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) || - (cart_path && strstr(cart_path, ".pxc")); - - if (is_cart) { - char* original_cwd = getcwd(NULL, 0); - sys->cart = pxl8_cart_create(); - if (!sys->cart) { - pxl8_error("failed to create cart"); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - if (pxl8_cart_load(sys->cart, cart_path) == PXL8_OK) { - pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd); - pxl8_cart_mount(sys->cart); - strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - } else { - pxl8_error("failed to load cart: %s", cart_path); - return PXL8_ERROR_INITIALIZATION_FAILED; - } - free(original_cwd); - } else if (script_arg) { - strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1); - game->script_path[sizeof(game->script_path) - 1] = '\0'; - } + if (game->repl_mode) { + pxl8_info("starting in REPL mode with script: %s", game->script_path); } pxl8_script_set_gfx(game->script, game->gfx); diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index f7a3b37..c73e4c2 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -98,31 +98,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { memset(bsp, 0, sizeof(*bsp)); - FILE* f = fopen(path, "rb"); - if (!f) { + u8* file_data = NULL; + size_t file_size = 0; + pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size); + if (result != PXL8_OK) { pxl8_error("Failed to load BSP file: %s", path); - return PXL8_ERROR_FILE_NOT_FOUND; + return result; } - fseek(f, 0, SEEK_END); - size_t file_size = ftell(f); - fseek(f, 0, SEEK_SET); - - u8* file_data = (u8*)malloc(file_size); - if (!file_data) { - fclose(f); - pxl8_error("Failed to allocate memory for BSP file: %s", path); - return PXL8_ERROR_OUT_OF_MEMORY; - } - - if (fread(file_data, 1, file_size, f) != file_size) { - free(file_data); - fclose(f); - pxl8_error("Failed to read BSP file: %s", path); - return PXL8_ERROR_INVALID_FORMAT; - } - fclose(f); - if (file_size < sizeof(pxl8_bsp_header)) { pxl8_error("BSP file too small: %s", path); free(file_data); diff --git a/src/pxl8_cart.c b/src/pxl8_cart.c index c9620ff..682e262 100644 --- a/src/pxl8_cart.c +++ b/src/pxl8_cart.c @@ -45,7 +45,10 @@ struct pxl8_cart { pxl8_cart_file* files; u32 file_count; char* base_path; - char* name; + char* title; + pxl8_resolution resolution; + pxl8_size window_size; + pxl8_pixel_mode pixel_mode; bool is_folder; bool is_mounted; }; @@ -63,19 +66,12 @@ static bool is_pxc_file(const char* path) { return len > 4 && strcmp(path + len - 4, ".pxc") == 0; } -static char* get_cart_name(const char* path) { - char* name = strdup(path); - size_t len = strlen(name); - if (len > 4 && strcmp(name + len - 4, ".pxc") == 0) { - name[len - 4] = '\0'; - } - char* last_slash = strrchr(name, '/'); - if (last_slash) { - char* result = strdup(last_slash + 1); - free(name); - return result; - } - return name; +static bool has_main_script(const char* base_path) { + char path[512]; + snprintf(path, sizeof(path), "%s/main.fnl", base_path); + if (access(path, F_OK) == 0) return true; + snprintf(path, sizeof(path), "%s/main.lua", base_path); + return access(path, F_OK) == 0; } static void collect_files_recursive(const char* dir_path, const char* prefix, @@ -155,7 +151,13 @@ static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) { } pxl8_cart* pxl8_cart_create(void) { - return calloc(1, sizeof(pxl8_cart)); + pxl8_cart* cart = calloc(1, sizeof(pxl8_cart)); + if (cart) { + cart->resolution = PXL8_RESOLUTION_640x360; + cart->window_size = (pxl8_size){1280, 720}; + cart->pixel_mode = PXL8_PIXEL_INDEXED; + } + return cart; } pxl8_cart* pxl8_cart_current(void) { @@ -171,7 +173,6 @@ void pxl8_cart_destroy(pxl8_cart* cart) { pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { if (!cart || !path) return PXL8_ERROR_NULL_POINTER; pxl8_cart_unload(cart); - cart->name = get_cart_name(path); if (is_directory(path)) { cart->base_path = realpath(path, NULL); @@ -181,17 +182,12 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { } cart->is_folder = true; - char main_path[512]; - snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path); - if (access(main_path, F_OK) != 0) { - snprintf(main_path, sizeof(main_path), "%s/main.lua", cart->base_path); - if (access(main_path, F_OK) != 0) { - pxl8_error("No main.fnl or main.lua found in cart: %s", path); - return PXL8_ERROR_FILE_NOT_FOUND; - } + if (!has_main_script(cart->base_path)) { + pxl8_error("No main.fnl or main.lua found in cart: %s", path); + return PXL8_ERROR_FILE_NOT_FOUND; } - pxl8_info("Loaded cart folder: %s", cart->name); + pxl8_info("Loaded cart"); return PXL8_OK; } @@ -218,7 +214,7 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) { free(data); if (result == PXL8_OK) { - pxl8_info("Loaded cart: %s", cart->name); + pxl8_info("Loaded cart"); } return result; } @@ -254,12 +250,11 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) { } fclose(file); - cart->name = get_cart_name(exe_path); pxl8_result result = load_packed_cart(cart, data, trailer.cart_size); free(data); if (result == PXL8_OK) { - pxl8_info("Loaded embedded cart: %s", cart->name); + pxl8_info("Loaded embedded cart"); } return result; } @@ -281,10 +276,12 @@ void pxl8_cart_unload(pxl8_cart* cart) { cart->data = NULL; cart->data_size = 0; - if (cart->name) { - pxl8_info("Unloaded cart: %s", cart->name); - free(cart->name); - cart->name = NULL; + if (cart->title) { + pxl8_info("Unloaded cart: %s", cart->title); + free(cart->title); + cart->title = NULL; + } else { + pxl8_info("Unloaded cart"); } free(cart->base_path); @@ -312,7 +309,11 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) { cart->is_mounted = true; pxl8_current_cart = cart; - pxl8_info("Mounted cart: %s", cart->name); + if (cart->title) { + pxl8_info("Mounted cart: %s", cart->title); + } else { + pxl8_info("Mounted cart"); + } return PXL8_OK; } @@ -335,8 +336,38 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart) { return cart ? cart->base_path : NULL; } -const char* pxl8_cart_get_name(const pxl8_cart* cart) { - return cart ? cart->name : NULL; +const char* pxl8_cart_get_title(const pxl8_cart* cart) { + return cart ? cart->title : NULL; +} + +void pxl8_cart_set_title(pxl8_cart* cart, const char* title) { + if (!cart || !title) return; + free(cart->title); + cart->title = strdup(title); +} + +pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart) { + return cart ? cart->resolution : PXL8_RESOLUTION_640x360; +} + +void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution) { + if (cart) cart->resolution = resolution; +} + +pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart) { + return cart ? cart->window_size : (pxl8_size){1280, 720}; +} + +void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) { + if (cart) cart->window_size = size; +} + +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) { + return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED; +} + +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) { + if (cart) cart->pixel_mode = mode; } bool pxl8_cart_is_packed(const pxl8_cart* cart) { @@ -429,14 +460,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) { return PXL8_ERROR_FILE_NOT_FOUND; } - char main_path[512]; - snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path); - if (access(main_path, F_OK) != 0) { - snprintf(main_path, sizeof(main_path), "%s/main.lua", folder_path); - if (access(main_path, F_OK) != 0) { - pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path); - return PXL8_ERROR_FILE_NOT_FOUND; - } + if (!has_main_script(folder_path)) { + pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path); + return PXL8_ERROR_FILE_NOT_FOUND; } char** paths = NULL; diff --git a/src/pxl8_cart.h b/src/pxl8_cart.h index c9b5f0b..16b5914 100644 --- a/src/pxl8_cart.h +++ b/src/pxl8_cart.h @@ -22,7 +22,14 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart); void pxl8_cart_unmount(pxl8_cart* cart); const char* pxl8_cart_get_base_path(const pxl8_cart* cart); -const char* pxl8_cart_get_name(const pxl8_cart* cart); +const char* pxl8_cart_get_title(const pxl8_cart* cart); +pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart); +pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart); +pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart); +void pxl8_cart_set_title(pxl8_cart* cart, const char* title); +void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution); +void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size); +void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode); bool pxl8_cart_is_packed(const pxl8_cart* cart); bool pxl8_cart_has_embedded(const char* exe_path); diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index c4aba1d..0d405e4 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -10,6 +10,7 @@ #include "pxl8_font.h" #include "pxl8_hal.h" #include "pxl8_log.h" +#include "pxl8_macros.h" #include "pxl8_math.h" #include "pxl8_sys.h" #include "pxl8_types.h" @@ -273,8 +274,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) { pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++]; entry->active = true; entry->sprite_id = sprite_id; - strncpy(entry->path, path, sizeof(entry->path) - 1); - entry->path[sizeof(entry->path) - 1] = '\0'; + pxl8_strncpy(entry->path, path, sizeof(entry->path)); return sprite_id; } diff --git a/src/pxl8_macros.h b/src/pxl8_macros.h index 5fb1532..ed3509d 100644 --- a/src/pxl8_macros.h +++ b/src/pxl8_macros.h @@ -1,5 +1,7 @@ #pragma once +#include + #ifndef pxl8_min #define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) #endif @@ -7,3 +9,10 @@ #ifndef pxl8_max #define pxl8_max(a, b) ((a) > (b) ? (a) : (b)) #endif + +#ifndef pxl8_strncpy +#define pxl8_strncpy(dst, src, size) do { \ + strncpy((dst), (src), (size) - 1); \ + (dst)[(size) - 1] = '\0'; \ +} while (0) +#endif diff --git a/src/pxl8_rec.c b/src/pxl8_rec.c deleted file mode 100644 index 9c750ec..0000000 --- a/src/pxl8_rec.c +++ /dev/null @@ -1,137 +0,0 @@ -#include "pxl8_rec.h" - -#include "pxl8_log.h" - -#include -#include -#include -#include - -struct pxl8_recorder { - i32 height; - i32 width; - - pxl8_recorder_format format; - i32 framerate; - char output_path[512]; - - FILE* ffmpeg_pipe; - u32 frame_count; - bool recording; -}; - -static void generate_default_output_path(pxl8_recorder* rec) { - time_t now = time(NULL); - struct tm* t = localtime(&now); - const char* ext = (rec->format == PXL8_RECORDER_GIF) ? "gif" : "mp4"; - - snprintf(rec->output_path, sizeof(rec->output_path), - "recording_%04d%02d%02d_%02d%02d%02d.%s", - t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, - t->tm_hour, t->tm_min, t->tm_sec, ext); -} - -pxl8_recorder* pxl8_recorder_create(i32 width, i32 height) { - pxl8_recorder* rec = (pxl8_recorder*)calloc(1, sizeof(pxl8_recorder)); - if (!rec) return NULL; - - rec->height = height; - rec->width = width; - - rec->format = PXL8_RECORDER_MP4; - rec->framerate = 60; - rec->output_path[0] = '\0'; - - rec->ffmpeg_pipe = NULL; - rec->frame_count = 0; - rec->recording = false; - - return rec; -} - -void pxl8_recorder_destroy(pxl8_recorder* rec) { - if (!rec) return; - if (rec->recording) pxl8_recorder_stop(rec); - free(rec); -} - -void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format) { - if (rec && !rec->recording) rec->format = format; -} - -void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps) { - if (rec && !rec->recording) rec->framerate = fps > 0 ? fps : 60; -} - -void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path) { - if (rec && !rec->recording && path) strncpy(rec->output_path, path, sizeof(rec->output_path) - 1); -} - -void pxl8_recorder_start(pxl8_recorder* rec) { - if (!rec || rec->recording) return; - if (rec->output_path[0] == '\0') generate_default_output_path(rec); - - char cmd[1024]; - - if (rec->format == PXL8_RECORDER_GIF) { - snprintf(cmd, sizeof(cmd), - "ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - " - "-vf \"split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" " - "\"%s\" 2>/dev/null", - rec->width, rec->height, rec->framerate, rec->output_path); - } else { - snprintf(cmd, sizeof(cmd), - "ffmpeg -y -f rawvideo -pix_fmt rgba -s %dx%d -r %d -i - " - "-c:v libx264 -pix_fmt yuv420p -crf 18 -preset fast " - "\"%s\" 2>/dev/null", - rec->width, rec->height, rec->framerate, rec->output_path); - } - - rec->ffmpeg_pipe = popen(cmd, "w"); - if (!rec->ffmpeg_pipe) { - pxl8_error("Failed to start ffmpeg. Is ffmpeg installed?"); - return; - } - - rec->frame_count = 0; - rec->recording = true; - - pxl8_info("Recording started: %s (%dx%d @ %dfps)", - rec->output_path, rec->width, rec->height, rec->framerate); -} - -void pxl8_recorder_stop(pxl8_recorder* rec) { - if (!rec || !rec->recording) return; - - rec->recording = false; - - if (rec->ffmpeg_pipe) { - pclose(rec->ffmpeg_pipe); - rec->ffmpeg_pipe = NULL; - } - - pxl8_info("Recording stopped: %u frames -> %s", rec->frame_count, rec->output_path); -} - -bool pxl8_recorder_is_recording(pxl8_recorder* rec) { - return rec && rec->recording; -} - -void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels) { - if (!rec || !rec->recording || !rgba_pixels || !rec->ffmpeg_pipe) return; - - size_t written = fwrite(rgba_pixels, 4, rec->width * rec->height, rec->ffmpeg_pipe); - if (written == (size_t)(rec->width * rec->height)) { - rec->frame_count++; - } else { - pxl8_error("Failed to write frame %u", rec->frame_count); - } -} - -u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec) { - return rec ? rec->frame_count : 0; -} - -const char* pxl8_recorder_get_output_path(pxl8_recorder* rec) { - return rec ? rec->output_path : NULL; -} diff --git a/src/pxl8_rec.h b/src/pxl8_rec.h deleted file mode 100644 index 4ba5dca..0000000 --- a/src/pxl8_rec.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "pxl8_types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum pxl8_recorder_format { - PXL8_RECORDER_GIF, - PXL8_RECORDER_MP4 -} pxl8_recorder_format; - -typedef struct pxl8_recorder pxl8_recorder; - -pxl8_recorder* pxl8_recorder_create(i32 width, i32 height); -void pxl8_recorder_destroy(pxl8_recorder* rec); - -void pxl8_recorder_capture_frame(pxl8_recorder* rec, const u32* rgba_pixels); -u32 pxl8_recorder_get_frame_count(pxl8_recorder* rec); -const char* pxl8_recorder_get_output_path(pxl8_recorder* rec); -bool pxl8_recorder_is_recording(pxl8_recorder* rec); -void pxl8_recorder_set_format(pxl8_recorder* rec, pxl8_recorder_format format); -void pxl8_recorder_set_framerate(pxl8_recorder* rec, i32 fps); -void pxl8_recorder_set_output_path(pxl8_recorder* rec, const char* path); -void pxl8_recorder_start(pxl8_recorder* rec); -void pxl8_recorder_stop(pxl8_recorder* rec); - -#ifdef __cplusplus -} -#endif diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 28be5ed..35d9e47 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -14,8 +14,9 @@ #include "pxl8_cart.h" #include "pxl8_embed.h" -#include "pxl8_log.h" #include "pxl8_gui.h" +#include "pxl8_log.h" +#include "pxl8_macros.h" struct pxl8_script { lua_State* L; @@ -422,11 +423,11 @@ static const char* pxl8_ffi_cdefs = void pxl8_lua_log(int level, const char* file, int line, const char* msg) { if (file && (file[0] == '?' || file[0] == '\0')) file = NULL; switch (level) { - case 0: pxl8_log_write_info("%s", msg); break; - case 1: pxl8_log_write_warn(file, line, "%s", msg); break; - case 2: pxl8_log_write_error(file, line, "%s", msg); break; - case 3: pxl8_log_write_debug(file, line, "%s", msg); break; - case 4: pxl8_log_write_trace(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_TRACE: pxl8_log_write_trace(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_DEBUG: pxl8_log_write_debug(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_INFO: pxl8_log_write_info("%s", msg); break; + case PXL8_LOG_LEVEL_WARN: pxl8_log_write_warn(file, line, "%s", msg); break; + case PXL8_LOG_LEVEL_ERROR: pxl8_log_write_error(file, line, "%s", msg); break; } } @@ -610,8 +611,7 @@ void pxl8_script_set_sys(pxl8_script* script, void* sys) { static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) { char filename_copy[PATH_MAX]; - strncpy(filename_copy, filename, sizeof(filename_copy) - 1); - filename_copy[sizeof(filename_copy) - 1] = '\0'; + pxl8_strncpy(filename_copy, filename, sizeof(filename_copy)); char* last_slash = strrchr(filename_copy, '/'); @@ -933,8 +933,7 @@ static time_t get_latest_script_mod_time(const char* dir_path) { pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) { if (!script || !path) return PXL8_ERROR_NULL_POINTER; - strncpy(script->main_path, path, sizeof(script->main_path) - 1); - script->main_path[sizeof(script->main_path) - 1] = '\0'; + pxl8_strncpy(script->main_path, path, sizeof(script->main_path)); char* last_slash = strrchr(script->main_path, '/'); if (last_slash) { @@ -1006,3 +1005,90 @@ bool pxl8_script_is_incomplete_input(pxl8_script* script) { if (!script) return false; return pxl8_script_is_incomplete_error(script->last_error); } + +static pxl8_resolution parse_resolution(const char* str) { + if (strcmp(str, "240x160") == 0) return PXL8_RESOLUTION_240x160; + if (strcmp(str, "320x180") == 0) return PXL8_RESOLUTION_320x180; + if (strcmp(str, "320x240") == 0) return PXL8_RESOLUTION_320x240; + if (strcmp(str, "640x360") == 0) return PXL8_RESOLUTION_640x360; + if (strcmp(str, "640x480") == 0) return PXL8_RESOLUTION_640x480; + if (strcmp(str, "800x600") == 0) return PXL8_RESOLUTION_800x600; + if (strcmp(str, "960x540") == 0) return PXL8_RESOLUTION_960x540; + return PXL8_RESOLUTION_640x360; +} + +static pxl8_pixel_mode parse_pixel_mode(const char* str) { + if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED; + if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR; + return PXL8_PIXEL_INDEXED; +} + +pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) { + if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER; + + if (!pxl8_cart_file_exists(cart, "cart.fnl")) { + return PXL8_OK; + } + + u8* data = NULL; + u32 size = 0; + if (pxl8_cart_read_file(cart, "cart.fnl", &data, &size) != PXL8_OK) { + return PXL8_OK; + } + + lua_getglobal(script->L, "fennel"); + if (lua_isnil(script->L, -1)) { + lua_pop(script->L, 1); + pxl8_cart_free_file(data); + return PXL8_OK; + } + + lua_getfield(script->L, -1, "eval"); + lua_pushlstring(script->L, (const char*)data, size); + lua_createtable(script->L, 0, 1); + lua_pushstring(script->L, "cart.fnl"); + lua_setfield(script->L, -2, "filename"); + + pxl8_cart_free_file(data); + + if (lua_pcall(script->L, 2, 1, 0) != 0) { + pxl8_warn("Failed to load cart.fnl: %s", lua_tostring(script->L, -1)); + lua_pop(script->L, 2); + return PXL8_OK; + } + + if (lua_istable(script->L, -1)) { + lua_getfield(script->L, -1, "title"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_title(cart, lua_tostring(script->L, -1)); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "resolution"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_resolution(cart, parse_resolution(lua_tostring(script->L, -1))); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "pixel-mode"); + if (lua_isstring(script->L, -1)) { + pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1))); + } + lua_pop(script->L, 1); + + lua_getfield(script->L, -1, "window-size"); + if (lua_istable(script->L, -1)) { + lua_rawgeti(script->L, -1, 1); + lua_rawgeti(script->L, -2, 2); + if (lua_isnumber(script->L, -2) && lua_isnumber(script->L, -1)) { + pxl8_size size = {(i32)lua_tonumber(script->L, -2), (i32)lua_tonumber(script->L, -1)}; + pxl8_cart_set_window_size(cart, size); + } + lua_pop(script->L, 2); + } + lua_pop(script->L, 1); + } + + lua_pop(script->L, 2); + return PXL8_OK; +} diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index e009ea2..8374fa4 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -7,7 +7,6 @@ #include "pxl8_atlas.h" #include "pxl8_color.h" #include "pxl8_log.h" -#include "pxl8_rec.h" #include "pxl8_sys.h" typedef struct pxl8_sdl3_context {