refactor: add helpers for file I/O, main script detection, and safe strncpy

This commit is contained in:
asrael 2025-12-06 15:04:53 -06:00
parent a33d4c0068
commit b7600fc1c9
No known key found for this signature in database
GPG key ID: 2786557804DFAE24
11 changed files with 250 additions and 306 deletions

4
demo/cart.fnl Normal file
View file

@ -0,0 +1,4 @@
{:title "pxl8 demo"
:resolution "640x360"
:window-size [1280 720]
:pixel-mode "indexed"}

View file

@ -13,11 +13,14 @@
#include "pxl8_game.h" #include "pxl8_game.h"
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_repl.h" #include "pxl8_repl.h"
#include "pxl8_script.h" #include "pxl8_script.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
#include "pxl8_types.h" #include "pxl8_types.h"
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart);
struct pxl8 { struct pxl8 {
pxl8_cart* cart; pxl8_cart* cart;
pxl8_game* game; 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; if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
pxl8_game* game = sys->game; pxl8_game* game = sys->game;
pxl8_pixel_mode pixel_mode = PXL8_PIXEL_INDEXED;
pxl8_resolution resolution = PXL8_RESOLUTION_640x360;
const char* script_arg = NULL; const char* script_arg = NULL;
bool bundle_mode = false; bool bundle_mode = false;
bool pack_mode = false; bool pack_mode = false;
@ -116,13 +117,60 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return result; return result;
} }
if (game->repl_mode) {
pxl8_info("starting in REPL mode with script: %s", game->script_path);
}
pxl8_info("starting up"); 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) { if (!sys->platform_data) {
pxl8_error("failed to create platform context"); pxl8_error("failed to create platform context");
return PXL8_ERROR_INITIALIZATION_FAILED; return PXL8_ERROR_INITIALIZATION_FAILED;
@ -139,58 +187,8 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return PXL8_ERROR_INITIALIZATION_FAILED; return PXL8_ERROR_INITIALIZATION_FAILED;
} }
game->script = pxl8_script_create(game->repl_mode); if (game->repl_mode) {
if (!game->script) { pxl8_info("starting in REPL mode with script: %s", game->script_path);
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';
}
} }
pxl8_script_set_gfx(game->script, game->gfx); pxl8_script_set_gfx(game->script, game->gfx);

View file

@ -98,31 +98,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
memset(bsp, 0, sizeof(*bsp)); memset(bsp, 0, sizeof(*bsp));
FILE* f = fopen(path, "rb"); u8* file_data = NULL;
if (!f) { 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); 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)) { if (file_size < sizeof(pxl8_bsp_header)) {
pxl8_error("BSP file too small: %s", path); pxl8_error("BSP file too small: %s", path);
free(file_data); free(file_data);

View file

@ -45,7 +45,10 @@ struct pxl8_cart {
pxl8_cart_file* files; pxl8_cart_file* files;
u32 file_count; u32 file_count;
char* base_path; char* base_path;
char* name; char* title;
pxl8_resolution resolution;
pxl8_size window_size;
pxl8_pixel_mode pixel_mode;
bool is_folder; bool is_folder;
bool is_mounted; bool is_mounted;
}; };
@ -63,19 +66,12 @@ static bool is_pxc_file(const char* path) {
return len > 4 && strcmp(path + len - 4, ".pxc") == 0; return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
} }
static char* get_cart_name(const char* path) { static bool has_main_script(const char* base_path) {
char* name = strdup(path); char path[512];
size_t len = strlen(name); snprintf(path, sizeof(path), "%s/main.fnl", base_path);
if (len > 4 && strcmp(name + len - 4, ".pxc") == 0) { if (access(path, F_OK) == 0) return true;
name[len - 4] = '\0'; snprintf(path, sizeof(path), "%s/main.lua", base_path);
} return access(path, F_OK) == 0;
char* last_slash = strrchr(name, '/');
if (last_slash) {
char* result = strdup(last_slash + 1);
free(name);
return result;
}
return name;
} }
static void collect_files_recursive(const char* dir_path, const char* prefix, 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) { 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) { 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) { pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
if (!cart || !path) return PXL8_ERROR_NULL_POINTER; if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
pxl8_cart_unload(cart); pxl8_cart_unload(cart);
cart->name = get_cart_name(path);
if (is_directory(path)) { if (is_directory(path)) {
cart->base_path = realpath(path, NULL); 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; cart->is_folder = true;
char main_path[512]; if (!has_main_script(cart->base_path)) {
snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path); pxl8_error("No main.fnl or main.lua found in cart: %s", path);
if (access(main_path, F_OK) != 0) { return PXL8_ERROR_FILE_NOT_FOUND;
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;
}
} }
pxl8_info("Loaded cart folder: %s", cart->name); pxl8_info("Loaded cart");
return PXL8_OK; return PXL8_OK;
} }
@ -218,7 +214,7 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
free(data); free(data);
if (result == PXL8_OK) { if (result == PXL8_OK) {
pxl8_info("Loaded cart: %s", cart->name); pxl8_info("Loaded cart");
} }
return result; return result;
} }
@ -254,12 +250,11 @@ pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
} }
fclose(file); fclose(file);
cart->name = get_cart_name(exe_path);
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size); pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
free(data); free(data);
if (result == PXL8_OK) { if (result == PXL8_OK) {
pxl8_info("Loaded embedded cart: %s", cart->name); pxl8_info("Loaded embedded cart");
} }
return result; return result;
} }
@ -281,10 +276,12 @@ void pxl8_cart_unload(pxl8_cart* cart) {
cart->data = NULL; cart->data = NULL;
cart->data_size = 0; cart->data_size = 0;
if (cart->name) { if (cart->title) {
pxl8_info("Unloaded cart: %s", cart->name); pxl8_info("Unloaded cart: %s", cart->title);
free(cart->name); free(cart->title);
cart->name = NULL; cart->title = NULL;
} else {
pxl8_info("Unloaded cart");
} }
free(cart->base_path); free(cart->base_path);
@ -312,7 +309,11 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
cart->is_mounted = true; cart->is_mounted = true;
pxl8_current_cart = cart; 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; return PXL8_OK;
} }
@ -335,8 +336,38 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart) {
return cart ? cart->base_path : NULL; return cart ? cart->base_path : NULL;
} }
const char* pxl8_cart_get_name(const pxl8_cart* cart) { const char* pxl8_cart_get_title(const pxl8_cart* cart) {
return cart ? cart->name : NULL; 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) { 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; return PXL8_ERROR_FILE_NOT_FOUND;
} }
char main_path[512]; if (!has_main_script(folder_path)) {
snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path); pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
if (access(main_path, F_OK) != 0) { return PXL8_ERROR_FILE_NOT_FOUND;
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;
}
} }
char** paths = NULL; char** paths = NULL;

View file

@ -22,7 +22,14 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart);
void pxl8_cart_unmount(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_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_is_packed(const pxl8_cart* cart);
bool pxl8_cart_has_embedded(const char* exe_path); bool pxl8_cart_has_embedded(const char* exe_path);

View file

@ -10,6 +10,7 @@
#include "pxl8_font.h" #include "pxl8_font.h"
#include "pxl8_hal.h" #include "pxl8_hal.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
#include "pxl8_types.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++]; pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
entry->active = true; entry->active = true;
entry->sprite_id = sprite_id; entry->sprite_id = sprite_id;
strncpy(entry->path, path, sizeof(entry->path) - 1); pxl8_strncpy(entry->path, path, sizeof(entry->path));
entry->path[sizeof(entry->path) - 1] = '\0';
return sprite_id; return sprite_id;
} }

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <string.h>
#ifndef pxl8_min #ifndef pxl8_min
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b)) #define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
#endif #endif
@ -7,3 +9,10 @@
#ifndef pxl8_max #ifndef pxl8_max
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b)) #define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
#endif #endif
#ifndef pxl8_strncpy
#define pxl8_strncpy(dst, src, size) do { \
strncpy((dst), (src), (size) - 1); \
(dst)[(size) - 1] = '\0'; \
} while (0)
#endif

View file

@ -1,137 +0,0 @@
#include "pxl8_rec.h"
#include "pxl8_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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;
}

View file

@ -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

View file

@ -14,8 +14,9 @@
#include "pxl8_cart.h" #include "pxl8_cart.h"
#include "pxl8_embed.h" #include "pxl8_embed.h"
#include "pxl8_log.h"
#include "pxl8_gui.h" #include "pxl8_gui.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
struct pxl8_script { struct pxl8_script {
lua_State* L; 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) { void pxl8_lua_log(int level, const char* file, int line, const char* msg) {
if (file && (file[0] == '?' || file[0] == '\0')) file = NULL; if (file && (file[0] == '?' || file[0] == '\0')) file = NULL;
switch (level) { switch (level) {
case 0: pxl8_log_write_info("%s", msg); break; case PXL8_LOG_LEVEL_TRACE: pxl8_log_write_trace(file, line, "%s", msg); break;
case 1: pxl8_log_write_warn(file, line, "%s", msg); break; case PXL8_LOG_LEVEL_DEBUG: pxl8_log_write_debug(file, line, "%s", msg); break;
case 2: pxl8_log_write_error(file, line, "%s", msg); break; case PXL8_LOG_LEVEL_INFO: pxl8_log_write_info("%s", msg); break;
case 3: pxl8_log_write_debug(file, line, "%s", msg); break; case PXL8_LOG_LEVEL_WARN: pxl8_log_write_warn(file, line, "%s", msg); break;
case 4: pxl8_log_write_trace(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) { 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]; char filename_copy[PATH_MAX];
strncpy(filename_copy, filename, sizeof(filename_copy) - 1); pxl8_strncpy(filename_copy, filename, sizeof(filename_copy));
filename_copy[sizeof(filename_copy) - 1] = '\0';
char* last_slash = strrchr(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) { pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
if (!script || !path) return PXL8_ERROR_NULL_POINTER; if (!script || !path) return PXL8_ERROR_NULL_POINTER;
strncpy(script->main_path, path, sizeof(script->main_path) - 1); pxl8_strncpy(script->main_path, path, sizeof(script->main_path));
script->main_path[sizeof(script->main_path) - 1] = '\0';
char* last_slash = strrchr(script->main_path, '/'); char* last_slash = strrchr(script->main_path, '/');
if (last_slash) { if (last_slash) {
@ -1006,3 +1005,90 @@ bool pxl8_script_is_incomplete_input(pxl8_script* script) {
if (!script) return false; if (!script) return false;
return pxl8_script_is_incomplete_error(script->last_error); 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;
}

View file

@ -7,7 +7,6 @@
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#include "pxl8_rec.h"
#include "pxl8_sys.h" #include "pxl8_sys.h"
typedef struct pxl8_sdl3_context { typedef struct pxl8_sdl3_context {