refactor: add helpers for file I/O, main script detection, and safe strncpy
This commit is contained in:
parent
a33d4c0068
commit
b7600fc1c9
11 changed files with 250 additions and 306 deletions
4
demo/cart.fnl
Normal file
4
demo/cart.fnl
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{:title "pxl8 demo"
|
||||||
|
:resolution "640x360"
|
||||||
|
:window-size [1280 720]
|
||||||
|
:pixel-mode "indexed"}
|
||||||
116
src/pxl8.c
116
src/pxl8.c
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
104
src/pxl8_cart.c
104
src/pxl8_cart.c
|
|
@ -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);
|
|
||||||
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);
|
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
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,15 +460,10 @@ 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);
|
|
||||||
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);
|
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
char** paths = NULL;
|
char** paths = NULL;
|
||||||
u32 count = 0, capacity = 0;
|
u32 count = 0, capacity = 0;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
137
src/pxl8_rec.c
137
src/pxl8_rec.c
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue