add save and bundle pxl8 with game for standalone game distribution

This commit is contained in:
asrael 2025-11-28 23:42:57 -06:00
parent b1e8525c3e
commit 04d3af11a9
25 changed files with 1173 additions and 346 deletions

View file

@ -73,10 +73,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_game* game = sys->game;
game->color_mode = PXL8_COLOR_MODE_MEGA;
game->resolution = PXL8_RESOLUTION_640x360;
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;
const char* pack_input = NULL;
const char* pack_output = NULL;
@ -84,6 +85,15 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
for (i32 i = 1; i < argc; i++) {
if (strcmp(argv[i], "--repl") == 0) {
game->repl_mode = true;
} else if (strcmp(argv[i], "--bundle") == 0) {
bundle_mode = true;
if (i + 2 < argc) {
pack_input = argv[++i];
pack_output = argv[++i];
} else {
pxl8_error("--bundle requires <folder|.pxc> <output>");
return PXL8_ERROR_INVALID_ARGUMENT;
}
} else if (strcmp(argv[i], "--pack") == 0) {
pack_mode = true;
if (i + 2 < argc) {
@ -98,6 +108,18 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
}
}
if (bundle_mode) {
char exe_path[1024];
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (len == -1) {
pxl8_error("Failed to resolve executable path");
return PXL8_ERROR_SYSTEM_FAILURE;
}
exe_path[len] = '\0';
pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path);
return result;
}
if (pack_mode) {
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
return result;
@ -109,13 +131,13 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_info("Starting up");
sys->platform_data = sys->hal->create(game->color_mode, game->resolution, "pxl8", 1280, 720);
sys->platform_data = sys->hal->create(pixel_mode, resolution, "pxl8", 1280, 720);
if (!sys->platform_data) {
pxl8_error("Failed to create platform context");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, game->color_mode, game->resolution);
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution);
if (!game->gfx) {
pxl8_error("Failed to create graphics context");
return PXL8_ERROR_INITIALIZATION_FAILED;
@ -126,39 +148,58 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->script = pxl8_script_create();
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;
}
const char* cart_path = script_arg ? script_arg : "demo";
bool has_embedded = pxl8_cart_has_embedded(argv[0]);
const char* cart_path = script_arg;
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);
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(sys->cart, cart_path) == PXL8_OK) {
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
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("Loaded cart: %s", pxl8_cart_get_name(sys->cart));
pxl8_info("Running embedded cart");
} else {
pxl8_error("Failed to load cart: %s", cart_path);
pxl8_error("Failed to load embedded cart");
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';
} 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);
@ -245,6 +286,8 @@ pxl8_result pxl8_update(pxl8* sys) {
}
}
pxl8_gfx_update(game->gfx, dt);
if (game->script_loaded) {
pxl8_script_call_function_f32(game->script, "update", dt);
}
@ -268,19 +311,18 @@ pxl8_result pxl8_frame(pxl8* sys) {
} else {
pxl8_clear(game->gfx, 32);
pxl8_size render_size = pxl8_get_resolution_dimensions(game->resolution);
i32 render_w = pxl8_gfx_get_width(game->gfx);
i32 render_h = pxl8_gfx_get_height(game->gfx);
for (i32 y = 0; y < render_size.h; y += 24) {
for (i32 x = 0; x < render_size.w; x += 32) {
for (i32 y = 0; y < render_h; y += 24) {
for (i32 x = 0; x < render_w; x += 32) {
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
pxl8_rect_fill(game->gfx, x, y, 31, 23, color);
}
}
}
pxl8_size render_size = pxl8_get_resolution_dimensions(game->resolution);
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_size.w, render_size.h));
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
pxl8_gfx_upload_framebuffer(game->gfx);
pxl8_gfx_upload_atlas(game->gfx);
pxl8_gfx_present(game->gfx);
@ -343,10 +385,6 @@ pxl8_input_state* pxl8_get_input(const pxl8* sys) {
return (sys && sys->game) ? &sys->game->input : NULL;
}
pxl8_resolution pxl8_get_resolution(const pxl8* sys) {
return (sys && sys->game) ? sys->game->resolution : PXL8_RESOLUTION_640x360;
}
void pxl8_center_cursor(pxl8* sys) {
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
sys->hal->center_cursor(sys->platform_data);
@ -365,17 +403,6 @@ void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) {
}
}
u32 pxl8_get_palette_size(pxl8_color_mode mode) {
switch (mode) {
case PXL8_COLOR_MODE_HICOLOR: return 0;
case PXL8_COLOR_MODE_FAMI: return 64;
case PXL8_COLOR_MODE_MEGA: return 512;
case PXL8_COLOR_MODE_GBA:
case PXL8_COLOR_MODE_SNES: return 32768;
default: return 256;
}
}
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) {
switch (resolution) {
case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160};