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_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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
104
src/pxl8_cart.c
104
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) {
|
||||
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,15 +460,10 @@ 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) {
|
||||
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;
|
||||
u32 count = 0, capacity = 0;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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
|
||||
|
|
|
|||
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_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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue