add save and bundle pxl8 with game for standalone game distribution
This commit is contained in:
parent
b1e8525c3e
commit
04d3af11a9
25 changed files with 1173 additions and 346 deletions
6
pxl8.sh
6
pxl8.sh
|
|
@ -173,7 +173,7 @@ timestamp() {
|
||||||
update_fennel() {
|
update_fennel() {
|
||||||
print_info "Fetching Fennel"
|
print_info "Fetching Fennel"
|
||||||
|
|
||||||
local version="1.5.3"
|
local version="1.6.0"
|
||||||
|
|
||||||
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
|
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
|
||||||
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
|
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
|
||||||
|
|
@ -217,11 +217,10 @@ update_luajit() {
|
||||||
print_info "Updated LuaJIT (${version})"
|
print_info "Updated LuaJIT (${version})"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
update_miniz() {
|
update_miniz() {
|
||||||
print_info "Fetching miniz"
|
print_info "Fetching miniz"
|
||||||
|
|
||||||
local version="3.0.2"
|
local version="3.1.0"
|
||||||
|
|
||||||
if curl -sL --max-time 5 -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/${version}/miniz-${version}.zip" 2>/dev/null; then
|
if curl -sL --max-time 5 -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/${version}/miniz-${version}.zip" 2>/dev/null; then
|
||||||
unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/ 2>/dev/null
|
unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/ 2>/dev/null
|
||||||
|
|
@ -344,6 +343,7 @@ case "$COMMAND" in
|
||||||
src/pxl8_io.c
|
src/pxl8_io.c
|
||||||
src/pxl8_math.c
|
src/pxl8_math.c
|
||||||
src/pxl8_rec.c
|
src/pxl8_rec.c
|
||||||
|
src/pxl8_save.c
|
||||||
src/pxl8_script.c
|
src/pxl8_script.c
|
||||||
src/pxl8_sdl3.c
|
src/pxl8_sdl3.c
|
||||||
src/pxl8_tilemap.c
|
src/pxl8_tilemap.c
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ pxl8.create_texture = gfx2d.create_texture
|
||||||
pxl8.upload_atlas = gfx2d.upload_atlas
|
pxl8.upload_atlas = gfx2d.upload_atlas
|
||||||
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
||||||
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
||||||
|
pxl8.gfx_cycle_palette = gfx2d.cycle_palette
|
||||||
|
pxl8.add_palette_cycle = gfx2d.add_palette_cycle
|
||||||
|
pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle
|
||||||
|
pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed
|
||||||
|
pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles
|
||||||
|
|
||||||
pxl8.key_down = input.key_down
|
pxl8.key_down = input.key_down
|
||||||
pxl8.key_pressed = input.key_pressed
|
pxl8.key_pressed = input.key_pressed
|
||||||
|
|
|
||||||
|
|
@ -82,4 +82,24 @@ function graphics.fade_palette(start, count, amount, target_color)
|
||||||
C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color)
|
C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function graphics.cycle_palette(start, count, step)
|
||||||
|
C.pxl8_gfx_cycle_palette(core.gfx, start, count, step or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function graphics.add_palette_cycle(start_index, end_index, speed)
|
||||||
|
return C.pxl8_gfx_add_palette_cycle(core.gfx, start_index, end_index, speed or 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function graphics.remove_palette_cycle(cycle_id)
|
||||||
|
C.pxl8_gfx_remove_palette_cycle(core.gfx, cycle_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
function graphics.set_palette_cycle_speed(cycle_id, speed)
|
||||||
|
C.pxl8_gfx_set_palette_cycle_speed(core.gfx, cycle_id, speed)
|
||||||
|
end
|
||||||
|
|
||||||
|
function graphics.clear_palette_cycles()
|
||||||
|
C.pxl8_gfx_clear_palette_cycles(core.gfx)
|
||||||
|
end
|
||||||
|
|
||||||
return graphics
|
return graphics
|
||||||
|
|
|
||||||
109
src/pxl8.c
109
src/pxl8.c
|
|
@ -73,10 +73,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
|
|
||||||
pxl8_game* game = sys->game;
|
pxl8_game* game = sys->game;
|
||||||
|
|
||||||
game->color_mode = PXL8_COLOR_MODE_MEGA;
|
pxl8_pixel_mode pixel_mode = PXL8_PIXEL_INDEXED;
|
||||||
game->resolution = PXL8_RESOLUTION_640x360;
|
pxl8_resolution resolution = PXL8_RESOLUTION_640x360;
|
||||||
|
|
||||||
const char* script_arg = NULL;
|
const char* script_arg = NULL;
|
||||||
|
bool bundle_mode = false;
|
||||||
bool pack_mode = false;
|
bool pack_mode = false;
|
||||||
const char* pack_input = NULL;
|
const char* pack_input = NULL;
|
||||||
const char* pack_output = 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++) {
|
for (i32 i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--repl") == 0) {
|
if (strcmp(argv[i], "--repl") == 0) {
|
||||||
game->repl_mode = true;
|
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) {
|
} else if (strcmp(argv[i], "--pack") == 0) {
|
||||||
pack_mode = true;
|
pack_mode = true;
|
||||||
if (i + 2 < argc) {
|
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) {
|
if (pack_mode) {
|
||||||
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -109,13 +131,13 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
|
|
||||||
pxl8_info("Starting up");
|
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) {
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (!game->gfx) {
|
||||||
pxl8_error("Failed to create graphics context");
|
pxl8_error("Failed to create graphics context");
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
|
|
@ -126,39 +148,58 @@ 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->script = pxl8_script_create(game->repl_mode);
|
||||||
if (!game->script) {
|
if (!game->script) {
|
||||||
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
pxl8_error("Failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
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;
|
if (has_embedded && !script_arg) {
|
||||||
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();
|
sys->cart = pxl8_cart_create();
|
||||||
if (!sys->cart) {
|
if (!sys->cart) {
|
||||||
pxl8_error("Failed to create cart");
|
pxl8_error("Failed to create cart");
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
if (pxl8_cart_load(sys->cart, cart_path) == PXL8_OK) {
|
if (pxl8_cart_load_embedded(sys->cart, argv[0]) == PXL8_OK) {
|
||||||
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
|
|
||||||
pxl8_cart_mount(sys->cart);
|
pxl8_cart_mount(sys->cart);
|
||||||
strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1);
|
strncpy(game->script_path, "main.fnl", sizeof(game->script_path) - 1);
|
||||||
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
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 {
|
} else {
|
||||||
pxl8_error("Failed to load cart: %s", cart_path);
|
pxl8_error("Failed to load embedded cart");
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
free(original_cwd);
|
} else if (cart_path || !has_embedded) {
|
||||||
} else if (script_arg) {
|
if (!cart_path) cart_path = "demo";
|
||||||
strncpy(game->script_path, script_arg, sizeof(game->script_path) - 1);
|
|
||||||
game->script_path[sizeof(game->script_path) - 1] = '\0';
|
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);
|
||||||
|
|
@ -245,6 +286,8 @@ pxl8_result pxl8_update(pxl8* sys) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pxl8_gfx_update(game->gfx, dt);
|
||||||
|
|
||||||
if (game->script_loaded) {
|
if (game->script_loaded) {
|
||||||
pxl8_script_call_function_f32(game->script, "update", dt);
|
pxl8_script_call_function_f32(game->script, "update", dt);
|
||||||
}
|
}
|
||||||
|
|
@ -268,19 +311,18 @@ pxl8_result pxl8_frame(pxl8* sys) {
|
||||||
} else {
|
} else {
|
||||||
pxl8_clear(game->gfx, 32);
|
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 y = 0; y < render_h; y += 24) {
|
||||||
for (i32 x = 0; x < render_size.w; x += 32) {
|
for (i32 x = 0; x < render_w; x += 32) {
|
||||||
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
||||||
pxl8_rect_fill(game->gfx, x, y, 31, 23, color);
|
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, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
|
||||||
|
|
||||||
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_size.w, render_size.h));
|
|
||||||
pxl8_gfx_upload_framebuffer(game->gfx);
|
pxl8_gfx_upload_framebuffer(game->gfx);
|
||||||
pxl8_gfx_upload_atlas(game->gfx);
|
pxl8_gfx_upload_atlas(game->gfx);
|
||||||
pxl8_gfx_present(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;
|
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) {
|
void pxl8_center_cursor(pxl8* sys) {
|
||||||
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
|
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
|
||||||
sys->hal->center_cursor(sys->platform_data);
|
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) {
|
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) {
|
||||||
switch (resolution) {
|
switch (resolution) {
|
||||||
case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160};
|
case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#define MINIZ_NO_ARCHIVE_APIS
|
#define MINIZ_NO_ARCHIVE_APIS
|
||||||
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
||||||
#define MINIZ_NO_DEFLATE_APIS
|
#define MINIZ_NO_DEFLATE_APIS
|
||||||
|
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
|
||||||
|
|
||||||
#include <miniz.h>
|
#include <miniz.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,14 +141,14 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_color_mode color_mode) {
|
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
|
||||||
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas));
|
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas));
|
||||||
if (!atlas) return NULL;
|
if (!atlas) return NULL;
|
||||||
|
|
||||||
atlas->height = height;
|
atlas->height = height;
|
||||||
atlas->width = width;
|
atlas->width = width;
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
|
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||||
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
|
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
|
||||||
if (!atlas->pixels) {
|
if (!atlas->pixels) {
|
||||||
free(atlas);
|
free(atlas);
|
||||||
|
|
@ -199,10 +199,10 @@ void pxl8_atlas_destroy(pxl8_atlas* atlas) {
|
||||||
free(atlas);
|
free(atlas);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode) {
|
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
|
||||||
if (!atlas || atlas->width >= 4096) return false;
|
if (!atlas || atlas->width >= 4096) return false;
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
|
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||||
u32 new_size = atlas->width * 2;
|
u32 new_size = atlas->width * 2;
|
||||||
u32 old_width = atlas->width;
|
u32 old_width = atlas->width;
|
||||||
|
|
||||||
|
|
@ -278,14 +278,14 @@ u32 pxl8_atlas_add_texture(
|
||||||
const u8* pixels,
|
const u8* pixels,
|
||||||
u32 w,
|
u32 w,
|
||||||
u32 h,
|
u32 h,
|
||||||
pxl8_color_mode color_mode
|
pxl8_pixel_mode pixel_mode
|
||||||
) {
|
) {
|
||||||
if (!atlas || !pixels) return UINT32_MAX;
|
if (!atlas || !pixels) return UINT32_MAX;
|
||||||
|
|
||||||
pxl8_skyline_fit fit =
|
pxl8_skyline_fit fit =
|
||||||
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||||
if (!fit.found) {
|
if (!fit.found) {
|
||||||
if (!pxl8_atlas_expand(atlas, color_mode)) {
|
if (!pxl8_atlas_expand(atlas, pixel_mode)) {
|
||||||
return UINT32_MAX;
|
return UINT32_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -319,7 +319,7 @@ u32 pxl8_atlas_add_texture(
|
||||||
entry->w = w;
|
entry->w = w;
|
||||||
entry->h = h;
|
entry->h = h;
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(color_mode);
|
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||||
for (u32 y = 0; y < h; y++) {
|
for (u32 y = 0; y < h; y++) {
|
||||||
for (u32 x = 0; x < w; x++) {
|
for (u32 x = 0; x < w; x++) {
|
||||||
u32 src_idx = y * w + x;
|
u32 src_idx = y * w + x;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ typedef struct pxl8_atlas_entry {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_color_mode color_mode);
|
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
|
||||||
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
||||||
|
|
||||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
||||||
|
|
@ -26,8 +26,8 @@ bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
|
||||||
|
|
||||||
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
|
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
|
||||||
|
|
||||||
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_color_mode color_mode);
|
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
|
||||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_color_mode color_mode);
|
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
609
src/pxl8_cart.c
609
src/pxl8_cart.c
|
|
@ -1,78 +1,58 @@
|
||||||
#include "pxl8_cart.h"
|
#include "pxl8_cart.h"
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <miniz.h>
|
|
||||||
|
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
|
|
||||||
|
#define PXL8_CART_MAGIC 0x43585850
|
||||||
|
#define PXL8_CART_VERSION 1
|
||||||
|
#define PXL8_CART_TRAILER_MAGIC 0x544E4250
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic;
|
||||||
|
u16 version;
|
||||||
|
u16 flags;
|
||||||
|
u32 file_count;
|
||||||
|
u32 toc_size;
|
||||||
|
} pxl8_cart_header;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
u16 path_len;
|
||||||
|
} pxl8_cart_entry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic;
|
||||||
|
u32 cart_offset;
|
||||||
|
u32 cart_size;
|
||||||
|
} pxl8_cart_trailer;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* path;
|
||||||
|
u32 offset;
|
||||||
|
u32 size;
|
||||||
|
} pxl8_cart_file;
|
||||||
|
|
||||||
struct pxl8_cart {
|
struct pxl8_cart {
|
||||||
void* archive_data;
|
u8* data;
|
||||||
size_t archive_size;
|
u32 data_size;
|
||||||
|
pxl8_cart_file* files;
|
||||||
|
u32 file_count;
|
||||||
char* base_path;
|
char* base_path;
|
||||||
|
char* name;
|
||||||
bool is_folder;
|
bool is_folder;
|
||||||
bool is_mounted;
|
bool is_mounted;
|
||||||
char* name;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static pxl8_cart* pxl8_current_cart = NULL;
|
static pxl8_cart* pxl8_current_cart = NULL;
|
||||||
static char* pxl8_original_cwd = NULL;
|
static char* pxl8_original_cwd = NULL;
|
||||||
|
|
||||||
static void pxl8_add_file_recursive(mz_zip_archive* zip, const char* dir_path, const char* prefix) {
|
|
||||||
DIR* dir = opendir(dir_path);
|
|
||||||
if (!dir) return;
|
|
||||||
|
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
|
|
||||||
char full_path[1024];
|
|
||||||
char zip_path[1024];
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
|
||||||
snprintf(zip_path, sizeof(zip_path), "%s%s", prefix, entry->d_name);
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
if (stat(full_path, &st) == 0) {
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
|
||||||
char new_prefix[1025];
|
|
||||||
snprintf(new_prefix, sizeof(new_prefix), "%s/", zip_path);
|
|
||||||
pxl8_add_file_recursive(zip, full_path, new_prefix);
|
|
||||||
} else {
|
|
||||||
pxl8_info("Adding: %s", zip_path);
|
|
||||||
if (!mz_zip_writer_add_file(zip, zip_path, full_path, NULL, 0, MZ_BEST_COMPRESSION)) {
|
|
||||||
pxl8_warn("Failed to add file: %s", zip_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 is_directory(const char* path) {
|
static bool is_directory(const char* path) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||||
|
|
@ -83,9 +63,99 @@ 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) {
|
||||||
|
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 void collect_files_recursive(const char* dir_path, const char* prefix,
|
||||||
|
char*** paths, u32* count, u32* capacity) {
|
||||||
|
DIR* dir = opendir(dir_path);
|
||||||
|
if (!dir) return;
|
||||||
|
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||||
|
|
||||||
|
char full_path[1024];
|
||||||
|
char rel_path[1024];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||||
|
snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full_path, &st) == 0) {
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
char new_prefix[1025];
|
||||||
|
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
|
||||||
|
collect_files_recursive(full_path, new_prefix, paths, count, capacity);
|
||||||
|
} else {
|
||||||
|
if (*count >= *capacity) {
|
||||||
|
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
|
||||||
|
*paths = realloc(*paths, *capacity * sizeof(char*));
|
||||||
|
}
|
||||||
|
(*paths)[(*count)++] = strdup(rel_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
|
||||||
|
if (!cart || !cart->files) return NULL;
|
||||||
|
for (u32 i = 0; i < cart->file_count; i++) {
|
||||||
|
if (strcmp(cart->files[i].path, path) == 0) {
|
||||||
|
return &cart->files[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
|
||||||
|
if (size < sizeof(pxl8_cart_header)) return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
|
||||||
|
const pxl8_cart_header* header = (const pxl8_cart_header*)data;
|
||||||
|
if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
|
||||||
|
cart->data = malloc(size);
|
||||||
|
if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
memcpy(cart->data, data, size);
|
||||||
|
cart->data_size = size;
|
||||||
|
|
||||||
|
cart->file_count = header->file_count;
|
||||||
|
cart->files = calloc(cart->file_count, sizeof(pxl8_cart_file));
|
||||||
|
if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
const u8* toc = cart->data + sizeof(pxl8_cart_header);
|
||||||
|
for (u32 i = 0; i < cart->file_count; i++) {
|
||||||
|
const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc;
|
||||||
|
toc += sizeof(pxl8_cart_entry);
|
||||||
|
|
||||||
|
cart->files[i].path = malloc(entry->path_len + 1);
|
||||||
|
memcpy(cart->files[i].path, toc, entry->path_len);
|
||||||
|
cart->files[i].path[entry->path_len] = '\0';
|
||||||
|
toc += entry->path_len;
|
||||||
|
|
||||||
|
cart->files[i].offset = entry->offset;
|
||||||
|
cart->files[i].size = entry->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
cart->is_folder = false;
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_cart* pxl8_cart_create(void) {
|
pxl8_cart* pxl8_cart_create(void) {
|
||||||
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
|
return calloc(1, sizeof(pxl8_cart));
|
||||||
return cart;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_cart* pxl8_cart_current(void) {
|
pxl8_cart* pxl8_cart_current(void) {
|
||||||
|
|
@ -98,19 +168,9 @@ void pxl8_cart_destroy(pxl8_cart* cart) {
|
||||||
free(cart);
|
free(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
cart->name = get_cart_name(path);
|
||||||
|
|
||||||
if (is_directory(path)) {
|
if (is_directory(path)) {
|
||||||
|
|
@ -138,137 +198,120 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
if (is_pxc_file(path)) {
|
if (is_pxc_file(path)) {
|
||||||
FILE* file = fopen(path, "rb");
|
FILE* file = fopen(path, "rb");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
pxl8_error("Failed to open cart archive: %s", path);
|
pxl8_error("Failed to open cart: %s", path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
fseek(file, 0, SEEK_END);
|
fseek(file, 0, SEEK_END);
|
||||||
cart->archive_size = ftell(file);
|
u32 size = (u32)ftell(file);
|
||||||
fseek(file, 0, SEEK_SET);
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
cart->archive_data = malloc(cart->archive_size);
|
u8* data = malloc(size);
|
||||||
if (!cart->archive_data) {
|
if (!data || fread(data, 1, size, file) != size) {
|
||||||
fclose(file);
|
free(data);
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fread(cart->archive_data, 1, cart->archive_size, file) != cart->archive_size) {
|
|
||||||
free(cart->archive_data);
|
|
||||||
cart->archive_data = NULL;
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|
||||||
char temp_dir[256];
|
pxl8_result result = load_packed_cart(cart, data, size);
|
||||||
snprintf(temp_dir, sizeof(temp_dir), "/tmp/pxl8_%s_%d", cart->name, getpid());
|
free(data);
|
||||||
|
|
||||||
if (mkdir(temp_dir, 0755) != 0) {
|
if (result == PXL8_OK) {
|
||||||
pxl8_error("Failed to create temp directory: %s", temp_dir);
|
pxl8_info("Loaded cart: %s", cart->name);
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
mz_zip_archive zip = {0};
|
|
||||||
if (!mz_zip_reader_init_mem(&zip, cart->archive_data, cart->archive_size, 0)) {
|
|
||||||
pxl8_error("Failed to open cart archive as zip");
|
|
||||||
return PXL8_ERROR_INVALID_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
i32 num_files = mz_zip_reader_get_num_files(&zip);
|
|
||||||
for (i32 i = 0; i < num_files; i++) {
|
|
||||||
mz_zip_archive_file_stat file_stat;
|
|
||||||
if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
|
|
||||||
|
|
||||||
char extract_path[1024];
|
|
||||||
snprintf(extract_path, sizeof(extract_path), "%s/%s", temp_dir, file_stat.m_filename);
|
|
||||||
|
|
||||||
if (file_stat.m_is_directory) {
|
|
||||||
mkdir(extract_path, 0755);
|
|
||||||
} else {
|
|
||||||
char* last_slash = strrchr(extract_path, '/');
|
|
||||||
if (last_slash) {
|
|
||||||
*last_slash = '\0';
|
|
||||||
mkdir(extract_path, 0755);
|
|
||||||
*last_slash = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mz_zip_reader_extract_to_file(&zip, i, extract_path, 0)) {
|
|
||||||
pxl8_warn("Failed to extract: %s", file_stat.m_filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mz_zip_reader_end(&zip);
|
|
||||||
|
|
||||||
cart->base_path = strdup(temp_dir);
|
|
||||||
cart->is_folder = false;
|
|
||||||
|
|
||||||
pxl8_info("Loaded cart archive: %s", cart->name);
|
|
||||||
return PXL8_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_error("Unknown cart format: %s", path);
|
pxl8_error("Unknown cart format: %s", path);
|
||||||
return PXL8_ERROR_INVALID_FORMAT;
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
|
||||||
|
if (!cart || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
FILE* file = fopen(exe_path, "rb");
|
||||||
|
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||||
|
pxl8_cart_trailer trailer;
|
||||||
|
if (fread(&trailer, sizeof(trailer), 1, file) != 1) {
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trailer.magic != PXL8_CART_TRAILER_MAGIC) {
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, trailer.cart_offset, SEEK_SET);
|
||||||
|
u8* data = malloc(trailer.cart_size);
|
||||||
|
if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) {
|
||||||
|
free(data);
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void pxl8_cart_unload(pxl8_cart* cart) {
|
void pxl8_cart_unload(pxl8_cart* cart) {
|
||||||
if (!cart) return;
|
if (!cart) return;
|
||||||
|
|
||||||
pxl8_cart_unmount(cart);
|
pxl8_cart_unmount(cart);
|
||||||
|
|
||||||
if (!cart->is_folder && cart->base_path) {
|
if (cart->files) {
|
||||||
char cmd[512];
|
for (u32 i = 0; i < cart->file_count; i++) {
|
||||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", cart->base_path);
|
free(cart->files[i].path);
|
||||||
system(cmd);
|
}
|
||||||
|
free(cart->files);
|
||||||
|
cart->files = NULL;
|
||||||
}
|
}
|
||||||
|
cart->file_count = 0;
|
||||||
|
|
||||||
char* cart_name = cart->name ? strdup(cart->name) : NULL;
|
free(cart->data);
|
||||||
|
cart->data = NULL;
|
||||||
if (cart->base_path) {
|
cart->data_size = 0;
|
||||||
free(cart->base_path);
|
|
||||||
cart->base_path = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cart->name) {
|
if (cart->name) {
|
||||||
|
pxl8_info("Unloaded cart: %s", cart->name);
|
||||||
free(cart->name);
|
free(cart->name);
|
||||||
cart->name = NULL;
|
cart->name = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cart->archive_data) {
|
free(cart->base_path);
|
||||||
free(cart->archive_data);
|
cart->base_path = NULL;
|
||||||
cart->archive_data = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
cart->archive_size = 0;
|
|
||||||
cart->is_folder = false;
|
cart->is_folder = false;
|
||||||
cart->is_mounted = false;
|
|
||||||
|
|
||||||
if (cart_name) {
|
|
||||||
pxl8_info("Unloaded cart: %s", cart_name);
|
|
||||||
free(cart_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||||
if (!cart || !cart->base_path) return PXL8_ERROR_NULL_POINTER;
|
if (!cart) return PXL8_ERROR_NULL_POINTER;
|
||||||
if (cart->is_mounted) return PXL8_OK;
|
if (cart->is_mounted) return PXL8_OK;
|
||||||
|
|
||||||
if (pxl8_current_cart) {
|
if (pxl8_current_cart) {
|
||||||
pxl8_cart_unmount(pxl8_current_cart);
|
pxl8_cart_unmount(pxl8_current_cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_original_cwd = getcwd(NULL, 0);
|
if (cart->is_folder) {
|
||||||
if (chdir(cart->base_path) != 0) {
|
pxl8_original_cwd = getcwd(NULL, 0);
|
||||||
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
if (chdir(cart->base_path) != 0) {
|
||||||
free(pxl8_original_cwd);
|
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
||||||
pxl8_original_cwd = NULL;
|
free(pxl8_original_cwd);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
pxl8_original_cwd = NULL;
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cart->is_mounted = true;
|
cart->is_mounted = true;
|
||||||
pxl8_current_cart = cart;
|
pxl8_current_cart = cart;
|
||||||
|
|
||||||
pxl8_info("Mounted cart: %s", cart->name);
|
pxl8_info("Mounted cart: %s", cart->name);
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
@ -286,24 +329,97 @@ void pxl8_cart_unmount(pxl8_cart* cart) {
|
||||||
if (pxl8_current_cart == cart) {
|
if (pxl8_current_cart == cart) {
|
||||||
pxl8_current_cart = NULL;
|
pxl8_current_cart = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_info("Unmounted cart: %s", cart->name);
|
pxl8_info("Unmounted cart: %s", cart->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) {
|
const char* pxl8_cart_get_base_path(const pxl8_cart* cart) {
|
||||||
if (!cart || !cart->base_path || !relative_path || !out_path || out_size == 0) return false;
|
return cart ? cart->base_path : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
|
const char* pxl8_cart_get_name(const pxl8_cart* cart) {
|
||||||
return written >= 0 && (size_t)written < out_size;
|
return cart ? cart->name : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
||||||
|
return cart && !cart->is_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_cart_has_embedded(const char* exe_path) {
|
||||||
|
FILE* file = fopen(exe_path, "rb");
|
||||||
|
if (!file) return false;
|
||||||
|
|
||||||
|
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||||
|
pxl8_cart_trailer trailer;
|
||||||
|
bool has = (fread(&trailer, sizeof(trailer), 1, file) == 1 &&
|
||||||
|
trailer.magic == PXL8_CART_TRAILER_MAGIC);
|
||||||
|
fclose(file);
|
||||||
|
return has;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
||||||
if (!cart || !cart->base_path || !path) return false;
|
if (!cart || !path) return false;
|
||||||
|
|
||||||
char full_path[512];
|
if (cart->is_folder) {
|
||||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
char full_path[512];
|
||||||
|
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
||||||
|
return access(full_path, F_OK) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
return access(full_path, F_OK) == 0;
|
return find_file(cart, path) != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) {
|
||||||
|
if (!cart || !relative_path || !out_path || out_size == 0) return false;
|
||||||
|
|
||||||
|
if (cart->is_folder && cart->base_path) {
|
||||||
|
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
|
||||||
|
return written >= 0 && (size_t)written < out_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 written = snprintf(out_path, out_size, "%s", relative_path);
|
||||||
|
return written >= 0 && (size_t)written < out_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) {
|
||||||
|
if (!cart || !path || !data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
if (cart->is_folder) {
|
||||||
|
char full_path[512];
|
||||||
|
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) {
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* file = fopen(full_path, "rb");
|
||||||
|
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
*size_out = (u32)ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
*data_out = malloc(*size_out);
|
||||||
|
if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) {
|
||||||
|
free(*data_out);
|
||||||
|
*data_out = NULL;
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_cart_file* cf = find_file(cart, path);
|
||||||
|
if (!cf) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
|
*size_out = cf->size;
|
||||||
|
*data_out = malloc(cf->size);
|
||||||
|
if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
memcpy(*data_out, cart->data + cf->offset, cf->size);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_free_file(u8* data) {
|
||||||
|
free(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
|
|
@ -324,22 +440,169 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mz_zip_archive zip = {0};
|
char** paths = NULL;
|
||||||
if (!mz_zip_writer_init_file(&zip, output_path, 0)) {
|
u32 count = 0, capacity = 0;
|
||||||
pxl8_error("Failed to create archive: %s", output_path);
|
collect_files_recursive(folder_path, "", &paths, &count, &capacity);
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
|
if (count == 0) {
|
||||||
|
pxl8_error("No files found in cart folder");
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 toc_size = 0;
|
||||||
|
for (u32 i = 0; i < count; i++) {
|
||||||
|
toc_size += sizeof(pxl8_cart_entry) + strlen(paths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 data_offset = sizeof(pxl8_cart_header) + toc_size;
|
||||||
|
u32 total_size = data_offset;
|
||||||
|
|
||||||
|
u32* file_sizes = malloc(count * sizeof(u32));
|
||||||
|
for (u32 i = 0; i < count; i++) {
|
||||||
|
char full_path[1024];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full_path, &st) == 0) {
|
||||||
|
file_sizes[i] = (u32)st.st_size;
|
||||||
|
total_size += file_sizes[i];
|
||||||
|
} else {
|
||||||
|
file_sizes[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* buffer = calloc(1, total_size);
|
||||||
|
if (!buffer) {
|
||||||
|
free(file_sizes);
|
||||||
|
for (u32 i = 0; i < count; i++) free(paths[i]);
|
||||||
|
free(paths);
|
||||||
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_cart_header* header = (pxl8_cart_header*)buffer;
|
||||||
|
header->magic = PXL8_CART_MAGIC;
|
||||||
|
header->version = PXL8_CART_VERSION;
|
||||||
|
header->flags = 0;
|
||||||
|
header->file_count = count;
|
||||||
|
header->toc_size = toc_size;
|
||||||
|
|
||||||
|
u8* toc = buffer + sizeof(pxl8_cart_header);
|
||||||
|
u32 file_offset = data_offset;
|
||||||
|
|
||||||
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
|
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
|
||||||
pxl8_add_file_recursive(&zip, folder_path, "");
|
|
||||||
|
|
||||||
if (!mz_zip_writer_finalize_archive(&zip)) {
|
for (u32 i = 0; i < count; i++) {
|
||||||
pxl8_error("Failed to finalize archive");
|
pxl8_cart_entry* entry = (pxl8_cart_entry*)toc;
|
||||||
mz_zip_writer_end(&zip);
|
entry->offset = file_offset;
|
||||||
|
entry->size = file_sizes[i];
|
||||||
|
entry->path_len = (u16)strlen(paths[i]);
|
||||||
|
toc += sizeof(pxl8_cart_entry);
|
||||||
|
|
||||||
|
memcpy(toc, paths[i], entry->path_len);
|
||||||
|
toc += entry->path_len;
|
||||||
|
|
||||||
|
char full_path[1024];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||||
|
FILE* file = fopen(full_path, "rb");
|
||||||
|
if (file) {
|
||||||
|
fread(buffer + file_offset, 1, file_sizes[i], file);
|
||||||
|
fclose(file);
|
||||||
|
pxl8_info(" %s (%u bytes)", paths[i], file_sizes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_offset += file_sizes[i];
|
||||||
|
free(paths[i]);
|
||||||
|
}
|
||||||
|
free(paths);
|
||||||
|
free(file_sizes);
|
||||||
|
|
||||||
|
FILE* out = fopen(output_path, "wb");
|
||||||
|
if (!out) {
|
||||||
|
free(buffer);
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
mz_zip_writer_end(&zip);
|
fwrite(buffer, 1, total_size, out);
|
||||||
pxl8_info("Cart packed successfully!");
|
fclose(out);
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
pxl8_info("Cart packed: %u files, %u bytes", count, total_size);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path) {
|
||||||
|
if (!input_path || !output_path || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
u8* cart_data = NULL;
|
||||||
|
u32 cart_size = 0;
|
||||||
|
bool free_cart = false;
|
||||||
|
|
||||||
|
if (is_directory(input_path)) {
|
||||||
|
char temp_pxc[256];
|
||||||
|
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid());
|
||||||
|
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
|
||||||
|
if (result != PXL8_OK) return result;
|
||||||
|
|
||||||
|
FILE* f = fopen(temp_pxc, "rb");
|
||||||
|
if (!f) return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
cart_size = (u32)ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
cart_data = malloc(cart_size);
|
||||||
|
fread(cart_data, 1, cart_size, f);
|
||||||
|
fclose(f);
|
||||||
|
unlink(temp_pxc);
|
||||||
|
free_cart = true;
|
||||||
|
} else if (is_pxc_file(input_path)) {
|
||||||
|
FILE* f = fopen(input_path, "rb");
|
||||||
|
if (!f) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
cart_size = (u32)ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
cart_data = malloc(cart_size);
|
||||||
|
fread(cart_data, 1, cart_size, f);
|
||||||
|
fclose(f);
|
||||||
|
free_cart = true;
|
||||||
|
} else {
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* exe = fopen(exe_path, "rb");
|
||||||
|
if (!exe) {
|
||||||
|
if (free_cart) free(cart_data);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
fseek(exe, 0, SEEK_END);
|
||||||
|
u32 exe_size = (u32)ftell(exe);
|
||||||
|
fseek(exe, 0, SEEK_SET);
|
||||||
|
|
||||||
|
u8* exe_data = malloc(exe_size);
|
||||||
|
fread(exe_data, 1, exe_size, exe);
|
||||||
|
fclose(exe);
|
||||||
|
|
||||||
|
FILE* out = fopen(output_path, "wb");
|
||||||
|
if (!out) {
|
||||||
|
free(exe_data);
|
||||||
|
if (free_cart) free(cart_data);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(exe_data, 1, exe_size, out);
|
||||||
|
free(exe_data);
|
||||||
|
|
||||||
|
u32 cart_offset = exe_size;
|
||||||
|
fwrite(cart_data, 1, cart_size, out);
|
||||||
|
if (free_cart) free(cart_data);
|
||||||
|
|
||||||
|
pxl8_cart_trailer trailer = {
|
||||||
|
.magic = PXL8_CART_TRAILER_MAGIC,
|
||||||
|
.cart_offset = cart_offset,
|
||||||
|
.cart_size = cart_size
|
||||||
|
};
|
||||||
|
fwrite(&trailer, sizeof(trailer), 1, out);
|
||||||
|
fclose(out);
|
||||||
|
|
||||||
|
chmod(output_path, 0755);
|
||||||
|
|
||||||
|
pxl8_info("Bundle created: %s", output_path);
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,25 @@ extern "C" {
|
||||||
pxl8_cart* pxl8_cart_create(void);
|
pxl8_cart* pxl8_cart_create(void);
|
||||||
pxl8_cart* pxl8_cart_current(void);
|
pxl8_cart* pxl8_cart_current(void);
|
||||||
void pxl8_cart_destroy(pxl8_cart* cart);
|
void pxl8_cart_destroy(pxl8_cart* cart);
|
||||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
|
|
||||||
|
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path);
|
||||||
|
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path);
|
||||||
|
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path);
|
||||||
|
void pxl8_cart_unload(pxl8_cart* cart);
|
||||||
|
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_base_path(const pxl8_cart* cart);
|
||||||
const char* pxl8_cart_get_name(const pxl8_cart* cart);
|
const char* pxl8_cart_get_name(const pxl8_cart* cart);
|
||||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path);
|
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
||||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart);
|
bool pxl8_cart_has_embedded(const char* exe_path);
|
||||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);
|
|
||||||
|
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
|
||||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size);
|
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size);
|
||||||
void pxl8_cart_unload(pxl8_cart* cart);
|
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out);
|
||||||
void pxl8_cart_unmount(pxl8_cart* cart);
|
void pxl8_cart_free_file(u8* data);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
static inline i32 pxl8_bytes_per_pixel(pxl8_color_mode mode) {
|
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
|
||||||
return (mode == PXL8_COLOR_MODE_HICOLOR) ? 2 : 1;
|
return (mode == PXL8_PIXEL_HICOLOR) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,7 @@
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
typedef struct pxl8_game {
|
typedef struct pxl8_game {
|
||||||
pxl8_color_mode color_mode;
|
|
||||||
pxl8_gfx* gfx;
|
pxl8_gfx* gfx;
|
||||||
pxl8_resolution resolution;
|
|
||||||
pxl8_script* script;
|
pxl8_script* script;
|
||||||
|
|
||||||
i32 frame_count;
|
i32 frame_count;
|
||||||
|
|
|
||||||
108
src/pxl8_gfx.c
108
src/pxl8_gfx.c
|
|
@ -14,6 +14,19 @@
|
||||||
#include "pxl8_sys.h"
|
#include "pxl8_sys.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
typedef struct pxl8_palette_cycle {
|
||||||
|
bool active;
|
||||||
|
u8 end_index;
|
||||||
|
f32 speed;
|
||||||
|
u8 start_index;
|
||||||
|
f32 timer;
|
||||||
|
} pxl8_palette_cycle;
|
||||||
|
|
||||||
|
typedef struct pxl8_effects {
|
||||||
|
pxl8_palette_cycle palette_cycles[8];
|
||||||
|
f32 time;
|
||||||
|
} pxl8_effects;
|
||||||
|
|
||||||
typedef struct pxl8_sprite_cache_entry {
|
typedef struct pxl8_sprite_cache_entry {
|
||||||
char path[256];
|
char path[256];
|
||||||
u32 sprite_id;
|
u32 sprite_id;
|
||||||
|
|
@ -29,7 +42,7 @@ struct pxl8_gfx {
|
||||||
u32 sprite_cache_capacity;
|
u32 sprite_cache_capacity;
|
||||||
u32 sprite_cache_count;
|
u32 sprite_cache_count;
|
||||||
|
|
||||||
pxl8_color_mode color_mode;
|
pxl8_pixel_mode pixel_mode;
|
||||||
u8* framebuffer;
|
u8* framebuffer;
|
||||||
i32 framebuffer_height;
|
i32 framebuffer_height;
|
||||||
i32 framebuffer_width;
|
i32 framebuffer_width;
|
||||||
|
|
@ -40,6 +53,8 @@ struct pxl8_gfx {
|
||||||
|
|
||||||
pxl8_viewport viewport;
|
pxl8_viewport viewport;
|
||||||
|
|
||||||
|
pxl8_effects effects;
|
||||||
|
|
||||||
bool backface_culling;
|
bool backface_culling;
|
||||||
pxl8_mat4 model;
|
pxl8_mat4 model;
|
||||||
pxl8_mat4 projection;
|
pxl8_mat4 projection;
|
||||||
|
|
@ -66,17 +81,17 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx) {
|
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
|
||||||
return gfx ? gfx->color_mode : PXL8_COLOR_MODE_FAMI;
|
return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED;
|
||||||
}
|
}
|
||||||
|
|
||||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
|
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
|
||||||
if (!gfx || gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return NULL;
|
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
|
||||||
return gfx->framebuffer;
|
return gfx->framebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
|
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
|
||||||
if (!gfx || gfx->color_mode != PXL8_COLOR_MODE_HICOLOR) return NULL;
|
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
|
||||||
return (u16*)gfx->framebuffer;
|
return (u16*)gfx->framebuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +110,7 @@ u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
|
||||||
pxl8_gfx* pxl8_gfx_create(
|
pxl8_gfx* pxl8_gfx_create(
|
||||||
const pxl8_hal* hal,
|
const pxl8_hal* hal,
|
||||||
void* platform_data,
|
void* platform_data,
|
||||||
pxl8_color_mode mode,
|
pxl8_pixel_mode mode,
|
||||||
pxl8_resolution resolution
|
pxl8_resolution resolution
|
||||||
) {
|
) {
|
||||||
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
|
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
|
||||||
|
|
@ -107,7 +122,7 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
gfx->hal = hal;
|
gfx->hal = hal;
|
||||||
gfx->platform_data = platform_data;
|
gfx->platform_data = platform_data;
|
||||||
|
|
||||||
gfx->color_mode = mode;
|
gfx->pixel_mode = mode;
|
||||||
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
||||||
gfx->framebuffer_width = size.w;
|
gfx->framebuffer_width = size.w;
|
||||||
gfx->framebuffer_height = size.h;
|
gfx->framebuffer_height = size.h;
|
||||||
|
|
@ -118,7 +133,7 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(gfx->color_mode);
|
i32 bytes_per_pixel = pxl8_bytes_per_pixel(gfx->pixel_mode);
|
||||||
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
||||||
gfx->framebuffer = (u8*)calloc(1, fb_size);
|
gfx->framebuffer = (u8*)calloc(1, fb_size);
|
||||||
if (!gfx->framebuffer) {
|
if (!gfx->framebuffer) {
|
||||||
|
|
@ -127,7 +142,7 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
gfx->palette_size = pxl8_get_palette_size(mode);
|
gfx->palette_size = (mode == PXL8_PIXEL_HICOLOR) ? 0 : PXL8_MAX_PALETTE_SIZE;
|
||||||
if (gfx->palette_size > 0) {
|
if (gfx->palette_size > 0) {
|
||||||
gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32));
|
gfx->palette = (u32*)calloc(gfx->palette_size, sizeof(u32));
|
||||||
if (!gfx->palette) {
|
if (!gfx->palette) {
|
||||||
|
|
@ -136,8 +151,8 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (u32 i = 0; i < 256 && i < gfx->palette_size; i++) {
|
for (u32 i = 0; i < gfx->palette_size; i++) {
|
||||||
u8 gray = (u8)(i * 255 / 255);
|
u8 gray = (u8)i;
|
||||||
gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,7 +192,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
||||||
|
|
||||||
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
||||||
if (gfx->atlas) return PXL8_OK;
|
if (gfx->atlas) return PXL8_OK;
|
||||||
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->color_mode);
|
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode);
|
||||||
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -187,7 +202,7 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width,
|
||||||
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
||||||
if (result != PXL8_OK) return result;
|
if (result != PXL8_OK) return result;
|
||||||
|
|
||||||
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->color_mode);
|
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode);
|
||||||
if (texture_id == UINT32_MAX) {
|
if (texture_id == UINT32_MAX) {
|
||||||
pxl8_error("Texture doesn't fit in atlas");
|
pxl8_error("Texture doesn't fit in atlas");
|
||||||
return PXL8_ERROR_INVALID_SIZE;
|
return PXL8_ERROR_INVALID_SIZE;
|
||||||
|
|
@ -234,7 +249,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
||||||
ase_file.frames[0].pixels,
|
ase_file.frames[0].pixels,
|
||||||
ase_file.header.width,
|
ase_file.header.width,
|
||||||
ase_file.header.height,
|
ase_file.header.height,
|
||||||
gfx->color_mode
|
gfx->pixel_mode
|
||||||
);
|
);
|
||||||
|
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
|
|
@ -273,7 +288,7 @@ pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
|
||||||
|
|
||||||
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
|
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
|
||||||
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK;
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return PXL8_OK;
|
||||||
|
|
||||||
pxl8_debug("Loading palette from: %s", path);
|
pxl8_debug("Loading palette from: %s", path);
|
||||||
|
|
||||||
|
|
@ -316,7 +331,7 @@ void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
||||||
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
if (!gfx || !gfx->initialized || !gfx->atlas) return;
|
||||||
|
|
||||||
if (gfx->hal && gfx->hal->upload_atlas) {
|
if (gfx->hal && gfx->hal->upload_atlas) {
|
||||||
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->color_mode);
|
gfx->hal->upload_atlas(gfx->platform_data, gfx->atlas, gfx->palette, gfx->pixel_mode);
|
||||||
pxl8_atlas_mark_clean(gfx->atlas);
|
pxl8_atlas_mark_clean(gfx->atlas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +345,7 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
gfx->framebuffer_height,
|
gfx->framebuffer_height,
|
||||||
gfx->palette,
|
gfx->palette,
|
||||||
gfx->color_mode
|
gfx->pixel_mode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,7 +379,7 @@ void pxl8_clear(pxl8_gfx* gfx, u32 color) {
|
||||||
|
|
||||||
i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
|
i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
|
||||||
|
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
u16* fb16 = (u16*)gfx->framebuffer;
|
u16* fb16 = (u16*)gfx->framebuffer;
|
||||||
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
||||||
for (i32 i = 0; i < size; i++) {
|
for (i32 i = 0; i < size; i++) {
|
||||||
|
|
@ -380,7 +395,7 @@ void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
||||||
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return;
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return;
|
||||||
|
|
||||||
i32 idx = y * gfx->framebuffer_width + x;
|
i32 idx = y * gfx->framebuffer_width + x;
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
||||||
} else {
|
} else {
|
||||||
gfx->framebuffer[idx] = color & 0xFF;
|
gfx->framebuffer[idx] = color & 0xFF;
|
||||||
|
|
@ -392,7 +407,7 @@ u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
|
||||||
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0;
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0;
|
||||||
|
|
||||||
i32 idx = y * gfx->framebuffer_width + x;
|
i32 idx = y * gfx->framebuffer_width + x;
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
return pxl8_rgb565_to_rgba32(((u16*)gfx->framebuffer)[idx]);
|
return pxl8_rgb565_to_rgba32(((u16*)gfx->framebuffer)[idx]);
|
||||||
} else {
|
} else {
|
||||||
return gfx->framebuffer[idx];
|
return gfx->framebuffer[idx];
|
||||||
|
|
@ -436,7 +451,7 @@ void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
||||||
|
|
||||||
static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
static inline void pxl8_pixel_unchecked(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
||||||
i32 idx = y * gfx->framebuffer_width + x;
|
i32 idx = y * gfx->framebuffer_width + x;
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
((u16*)gfx->framebuffer)[idx] = pxl8_rgba32_to_rgb565(color);
|
||||||
} else {
|
} else {
|
||||||
gfx->framebuffer[idx] = color & 0xFF;
|
gfx->framebuffer[idx] = color & 0xFF;
|
||||||
|
|
@ -534,7 +549,7 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
||||||
|
|
||||||
if (pixel_bit) {
|
if (pixel_bit) {
|
||||||
i32 fb_idx = py * gfx->framebuffer_width + px;
|
i32 fb_idx = py * gfx->framebuffer_width + px;
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
((u16*)gfx->framebuffer)[fb_idx] = pxl8_rgba32_to_rgb565(color);
|
((u16*)gfx->framebuffer)[fb_idx] = pxl8_rgba32_to_rgb565(color);
|
||||||
} else {
|
} else {
|
||||||
gfx->framebuffer[fb_idx] = (u8)color;
|
gfx->framebuffer[fb_idx] = (u8)color;
|
||||||
|
|
@ -574,7 +589,7 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
||||||
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
||||||
|
|
||||||
if (is_1to1_scale && is_unclipped) {
|
if (is_1to1_scale && is_unclipped) {
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
const u16* sprite_data = (const u16*)atlas_pixels + entry->y * atlas_width + entry->x;
|
const u16* sprite_data = (const u16*)atlas_pixels + entry->y * atlas_width + entry->x;
|
||||||
pxl8_blit_hicolor(
|
pxl8_blit_hicolor(
|
||||||
(u16*)gfx->framebuffer,
|
(u16*)gfx->framebuffer,
|
||||||
|
|
@ -601,7 +616,7 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
|
||||||
i32 src_idx = src_y * atlas_width + src_x;
|
i32 src_idx = src_y * atlas_width + src_x;
|
||||||
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
||||||
|
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
u16 pixel = ((const u16*)atlas_pixels)[src_idx];
|
u16 pixel = ((const u16*)atlas_pixels)[src_idx];
|
||||||
if (pixel != 0) {
|
if (pixel != 0) {
|
||||||
((u16*)gfx->framebuffer)[dest_idx] = pixel;
|
((u16*)gfx->framebuffer)[dest_idx] = pixel;
|
||||||
|
|
@ -709,7 +724,7 @@ void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) {
|
static void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) {
|
||||||
if (!gfx || !effects) return;
|
if (!gfx || !effects) return;
|
||||||
|
|
||||||
effects->time += dt;
|
effects->time += dt;
|
||||||
|
|
@ -727,6 +742,45 @@ void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
|
||||||
|
if (!gfx) return;
|
||||||
|
pxl8_gfx_process_effects(gfx, &gfx->effects, dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed) {
|
||||||
|
if (!gfx) return -1;
|
||||||
|
if (start_index >= end_index) return -1;
|
||||||
|
|
||||||
|
for (i32 i = 0; i < 8; i++) {
|
||||||
|
if (!gfx->effects.palette_cycles[i].active) {
|
||||||
|
gfx->effects.palette_cycles[i].active = true;
|
||||||
|
gfx->effects.palette_cycles[i].start_index = start_index;
|
||||||
|
gfx->effects.palette_cycles[i].end_index = end_index;
|
||||||
|
gfx->effects.palette_cycles[i].speed = speed;
|
||||||
|
gfx->effects.palette_cycles[i].timer = 0.0f;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id) {
|
||||||
|
if (!gfx || cycle_id < 0 || cycle_id >= 8) return;
|
||||||
|
gfx->effects.palette_cycles[cycle_id].active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed) {
|
||||||
|
if (!gfx || cycle_id < 0 || cycle_id >= 8) return;
|
||||||
|
gfx->effects.palette_cycles[cycle_id].speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx) {
|
||||||
|
if (!gfx) return;
|
||||||
|
for (i32 i = 0; i < 8; i++) {
|
||||||
|
gfx->effects.palette_cycles[i].active = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
|
static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
|
||||||
if (gfx->zbuffer) return true;
|
if (gfx->zbuffer) return true;
|
||||||
|
|
||||||
|
|
@ -863,7 +917,7 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32
|
||||||
i32 zbuf_offset = y * gfx->zbuffer_width;
|
i32 zbuf_offset = y * gfx->zbuffer_width;
|
||||||
i32 fb_offset = y * gfx->framebuffer_width;
|
i32 fb_offset = y * gfx->framebuffer_width;
|
||||||
|
|
||||||
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) {
|
||||||
u16* fb = (u16*)gfx->framebuffer;
|
u16* fb = (u16*)gfx->framebuffer;
|
||||||
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
u16 color16 = pxl8_rgba32_to_rgb565(color);
|
||||||
if (width == 0) {
|
if (width == 0) {
|
||||||
|
|
@ -939,7 +993,7 @@ static inline void pxl8_fill_scanline_textured(
|
||||||
i32 tex_mask = tex_w - 1;
|
i32 tex_mask = tex_w - 1;
|
||||||
i32 atlas_x_base = entry->x;
|
i32 atlas_x_base = entry->x;
|
||||||
i32 atlas_y_base = entry->y;
|
i32 atlas_y_base = entry->y;
|
||||||
bool is_hicolor = gfx->color_mode == PXL8_COLOR_MODE_HICOLOR;
|
bool is_hicolor = gfx->pixel_mode == PXL8_PIXEL_HICOLOR;
|
||||||
bool affine = gfx->affine_textures;
|
bool affine = gfx->affine_textures;
|
||||||
|
|
||||||
i32 span = xe - xs;
|
i32 span = xe - xs;
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,6 @@
|
||||||
typedef struct pxl8_atlas pxl8_atlas;
|
typedef struct pxl8_atlas pxl8_atlas;
|
||||||
typedef struct pxl8_gfx pxl8_gfx;
|
typedef struct pxl8_gfx pxl8_gfx;
|
||||||
|
|
||||||
typedef enum pxl8_blend_mode {
|
|
||||||
PXL8_BLEND_ADD,
|
|
||||||
PXL8_BLEND_ALPHA,
|
|
||||||
PXL8_BLEND_MULTIPLY,
|
|
||||||
PXL8_BLEND_NONE
|
|
||||||
} pxl8_blend_mode;
|
|
||||||
|
|
||||||
typedef struct pxl8_palette_cycle {
|
|
||||||
bool active;
|
|
||||||
u8 end_index;
|
|
||||||
f32 speed;
|
|
||||||
u8 start_index;
|
|
||||||
f32 timer;
|
|
||||||
} pxl8_palette_cycle;
|
|
||||||
|
|
||||||
typedef struct pxl8_scanline_effect {
|
|
||||||
bool active;
|
|
||||||
void (*process)(pxl8_gfx* gfx, i32 line, f32 time);
|
|
||||||
} pxl8_scanline_effect;
|
|
||||||
|
|
||||||
typedef struct pxl8_vertex {
|
typedef struct pxl8_vertex {
|
||||||
u32 color;
|
u32 color;
|
||||||
pxl8_vec3 normal;
|
pxl8_vec3 normal;
|
||||||
|
|
@ -34,12 +14,6 @@ typedef struct pxl8_vertex {
|
||||||
f32 u, v;
|
f32 u, v;
|
||||||
} pxl8_vertex;
|
} pxl8_vertex;
|
||||||
|
|
||||||
typedef struct pxl8_effects {
|
|
||||||
pxl8_palette_cycle palette_cycles[8];
|
|
||||||
pxl8_scanline_effect scanline_effects[4];
|
|
||||||
f32 time;
|
|
||||||
} pxl8_effects;
|
|
||||||
|
|
||||||
typedef struct pxl8_triangle {
|
typedef struct pxl8_triangle {
|
||||||
pxl8_vertex v[3];
|
pxl8_vertex v[3];
|
||||||
u32 texture_id;
|
u32 texture_id;
|
||||||
|
|
@ -49,11 +23,11 @@ typedef struct pxl8_triangle {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_color_mode mode, pxl8_resolution resolution);
|
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
|
||||||
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
||||||
|
|
||||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
||||||
pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx);
|
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
|
||||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
|
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
|
||||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
|
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
|
||||||
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
||||||
|
|
@ -67,7 +41,6 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
|
||||||
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
|
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
|
||||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
||||||
void pxl8_gfx_present(pxl8_gfx* gfx);
|
void pxl8_gfx_present(pxl8_gfx* gfx);
|
||||||
void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt);
|
|
||||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
||||||
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
|
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
|
||||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||||
|
|
@ -79,6 +52,12 @@ void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 ta
|
||||||
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t);
|
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t);
|
||||||
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
|
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors);
|
||||||
|
|
||||||
|
i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* gfx, u8 start_index, u8 end_index, f32 speed);
|
||||||
|
void pxl8_gfx_remove_palette_cycle(pxl8_gfx* gfx, i32 cycle_id);
|
||||||
|
void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* gfx, i32 cycle_id, f32 speed);
|
||||||
|
void pxl8_gfx_clear_palette_cycles(pxl8_gfx* gfx);
|
||||||
|
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
|
||||||
|
|
||||||
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||||
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||||
void pxl8_clear(pxl8_gfx* gfx, u32 color);
|
void pxl8_clear(pxl8_gfx* gfx, u32 color);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ typedef struct pxl8_atlas pxl8_atlas;
|
||||||
typedef struct pxl8_game pxl8_game;
|
typedef struct pxl8_game pxl8_game;
|
||||||
|
|
||||||
typedef struct pxl8_hal {
|
typedef struct pxl8_hal {
|
||||||
void* (*create)(pxl8_color_mode mode, pxl8_resolution res,
|
void* (*create)(pxl8_pixel_mode mode, pxl8_resolution res,
|
||||||
const char* title, i32 win_w, i32 win_h);
|
const char* title, i32 win_w, i32 win_h);
|
||||||
void (*destroy)(void* platform_data);
|
void (*destroy)(void* platform_data);
|
||||||
|
|
||||||
|
|
@ -17,8 +17,8 @@ typedef struct pxl8_hal {
|
||||||
void (*set_cursor)(void* platform_data, pxl8_cursor cursor);
|
void (*set_cursor)(void* platform_data, pxl8_cursor cursor);
|
||||||
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
||||||
void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas,
|
void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas,
|
||||||
const u32* palette, pxl8_color_mode mode);
|
const u32* palette, pxl8_pixel_mode mode);
|
||||||
void (*upload_framebuffer)(void* platform_data, const u8* fb,
|
void (*upload_framebuffer)(void* platform_data, const u8* fb,
|
||||||
i32 w, i32 h, const u32* palette,
|
i32 w, i32 h, const u32* palette,
|
||||||
pxl8_color_mode mode);
|
pxl8_pixel_mode mode);
|
||||||
} pxl8_hal;
|
} pxl8_hal;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
static inline char pxl8_to_lower(char c) {
|
static inline char pxl8_to_lower(char c) {
|
||||||
|
|
@ -12,6 +13,23 @@ static inline char pxl8_to_lower(char c) {
|
||||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
||||||
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
pxl8_cart* cart = pxl8_cart_current();
|
||||||
|
if (cart && pxl8_cart_is_packed(cart)) {
|
||||||
|
u8* data = NULL;
|
||||||
|
u32 cart_size = 0;
|
||||||
|
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
|
||||||
|
if (result == PXL8_OK) {
|
||||||
|
*content = realloc(data, cart_size + 1);
|
||||||
|
if (!*content) {
|
||||||
|
pxl8_cart_free_file(data);
|
||||||
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
(*content)[cart_size] = '\0';
|
||||||
|
*size = cart_size;
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FILE* file = fopen(path, "rb");
|
FILE* file = fopen(path, "rb");
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
|
||||||
272
src/pxl8_save.c
Normal file
272
src/pxl8_save.c
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
#include "pxl8_save.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#define PATH_SEP '\\'
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#define PATH_SEP '/'
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "pxl8_macros.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 magic;
|
||||||
|
u32 version;
|
||||||
|
u32 size;
|
||||||
|
u32 checksum;
|
||||||
|
} pxl8_save_header;
|
||||||
|
|
||||||
|
struct pxl8_save {
|
||||||
|
char directory[PXL8_SAVE_MAX_PATH];
|
||||||
|
u32 magic;
|
||||||
|
u32 version;
|
||||||
|
};
|
||||||
|
|
||||||
|
static u32 pxl8_save_checksum(const u8* data, u32 size) {
|
||||||
|
u32 hash = 2166136261u;
|
||||||
|
for (u32 i = 0; i < size; i++) {
|
||||||
|
hash ^= data[i];
|
||||||
|
hash *= 16777619u;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) {
|
||||||
|
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
|
||||||
|
} else {
|
||||||
|
snprintf(path, path_size, "%s%csave%d.sav", save->directory, PATH_SEP, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static pxl8_result pxl8_save_ensure_directory(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(path, &st) == 0) {
|
||||||
|
return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (_mkdir(path) != 0 && errno != EEXIST) {
|
||||||
|
#else
|
||||||
|
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
|
||||||
|
#endif
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||||
|
if (!game_name) return NULL;
|
||||||
|
|
||||||
|
pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save));
|
||||||
|
if (!save) return NULL;
|
||||||
|
|
||||||
|
save->magic = magic;
|
||||||
|
save->version = version;
|
||||||
|
|
||||||
|
char base_dir[PXL8_SAVE_MAX_PATH];
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
|
||||||
|
snprintf(save->directory, sizeof(save->directory),
|
||||||
|
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
|
||||||
|
} else {
|
||||||
|
free(save);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (!home) {
|
||||||
|
struct passwd* pw = getpwuid(getuid());
|
||||||
|
if (pw) home = pw->pw_dir;
|
||||||
|
}
|
||||||
|
if (!home) {
|
||||||
|
free(save);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
|
||||||
|
pxl8_save_ensure_directory(base_dir);
|
||||||
|
|
||||||
|
snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
|
||||||
|
pxl8_save_ensure_directory(base_dir);
|
||||||
|
|
||||||
|
snprintf(save->directory, sizeof(save->directory),
|
||||||
|
"%s/.local/share/pxl8/%s", home, game_name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
|
||||||
|
free(save);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_info("Save system initialized: %s", save->directory);
|
||||||
|
return save;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_save_destroy(pxl8_save* save) {
|
||||||
|
if (save) {
|
||||||
|
free(save);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
|
||||||
|
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (!data || size == 0) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[PXL8_SAVE_MAX_PATH];
|
||||||
|
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||||
|
|
||||||
|
FILE* file = fopen(path, "wb");
|
||||||
|
if (!file) {
|
||||||
|
pxl8_error("Failed to open save file for writing: %s", path);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_save_header header = {
|
||||||
|
.magic = save->magic,
|
||||||
|
.version = save->version,
|
||||||
|
.size = size,
|
||||||
|
.checksum = pxl8_save_checksum(data, size)
|
||||||
|
};
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
if (fwrite(&header, sizeof(header), 1, file) != 1) success = false;
|
||||||
|
if (success && fwrite(data, 1, size, file) != size) success = false;
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
pxl8_error("Failed to write save data");
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
pxl8_debug("Hot reload state saved (%u bytes)", size);
|
||||||
|
} else {
|
||||||
|
pxl8_info("Game saved to slot %d (%u bytes)", slot, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out) {
|
||||||
|
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (!data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data_out = NULL;
|
||||||
|
*size_out = 0;
|
||||||
|
|
||||||
|
char path[PXL8_SAVE_MAX_PATH];
|
||||||
|
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||||
|
|
||||||
|
FILE* file = fopen(path, "rb");
|
||||||
|
if (!file) {
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_save_header header;
|
||||||
|
if (fread(&header, sizeof(header), 1, file) != 1) {
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.magic != save->magic) {
|
||||||
|
fclose(file);
|
||||||
|
pxl8_error("Invalid save file magic");
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.version > save->version) {
|
||||||
|
fclose(file);
|
||||||
|
pxl8_error("Save file version too new: %u", header.version);
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* data = (u8*)malloc(header.size);
|
||||||
|
if (!data) {
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(data, 1, header.size, file) != header.size) {
|
||||||
|
free(data);
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
u32 checksum = pxl8_save_checksum(data, header.size);
|
||||||
|
if (checksum != header.checksum) {
|
||||||
|
free(data);
|
||||||
|
pxl8_error("Save file checksum mismatch");
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
*data_out = data;
|
||||||
|
*size_out = header.size;
|
||||||
|
|
||||||
|
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
pxl8_debug("Hot reload state loaded (%u bytes)", header.size);
|
||||||
|
} else {
|
||||||
|
pxl8_info("Game loaded from slot %d (%u bytes)", slot, header.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_save_free(u8* data) {
|
||||||
|
if (data) {
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
||||||
|
if (!save) return false;
|
||||||
|
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[PXL8_SAVE_MAX_PATH];
|
||||||
|
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
|
||||||
|
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||||
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[PXL8_SAVE_MAX_PATH];
|
||||||
|
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||||
|
|
||||||
|
if (remove(path) != 0 && errno != ENOENT) {
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* pxl8_save_get_directory(pxl8_save* save) {
|
||||||
|
return save ? save->directory : NULL;
|
||||||
|
}
|
||||||
27
src/pxl8_save.h
Normal file
27
src/pxl8_save.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
#define PXL8_SAVE_MAX_SLOTS 10
|
||||||
|
#define PXL8_SAVE_HOTRELOAD_SLOT 255
|
||||||
|
#define PXL8_SAVE_MAX_PATH 512
|
||||||
|
|
||||||
|
typedef struct pxl8_save pxl8_save;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);
|
||||||
|
void pxl8_save_destroy(pxl8_save* save);
|
||||||
|
|
||||||
|
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot);
|
||||||
|
bool pxl8_save_exists(pxl8_save* save, u8 slot);
|
||||||
|
void pxl8_save_free(u8* data);
|
||||||
|
const char* pxl8_save_get_directory(pxl8_save* save);
|
||||||
|
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);
|
||||||
|
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
|
|
||||||
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_embed.h"
|
#include "pxl8_embed.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_gui.h"
|
#include "pxl8_gui.h"
|
||||||
|
|
@ -24,10 +25,91 @@ struct pxl8_script {
|
||||||
char main_path[PXL8_MAX_PATH];
|
char main_path[PXL8_MAX_PATH];
|
||||||
char watch_dir[PXL8_MAX_PATH];
|
char watch_dir[PXL8_MAX_PATH];
|
||||||
time_t latest_mod_time;
|
time_t latest_mod_time;
|
||||||
|
bool repl_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
||||||
|
|
||||||
|
static int pxl8_cart_loader(lua_State* L) {
|
||||||
|
const char* found_path = lua_tostring(L, lua_upvalueindex(1));
|
||||||
|
const char* code = lua_tostring(L, lua_upvalueindex(2));
|
||||||
|
size_t code_len = lua_objlen(L, lua_upvalueindex(2));
|
||||||
|
bool is_fennel = lua_toboolean(L, lua_upvalueindex(3));
|
||||||
|
|
||||||
|
if (is_fennel) {
|
||||||
|
lua_getglobal(L, "fennel");
|
||||||
|
lua_getfield(L, -1, "eval");
|
||||||
|
lua_pushlstring(L, code, code_len);
|
||||||
|
lua_createtable(L, 0, 1);
|
||||||
|
lua_pushstring(L, found_path);
|
||||||
|
lua_setfield(L, -2, "filename");
|
||||||
|
|
||||||
|
if (lua_pcall(L, 2, 1, 0) != 0) {
|
||||||
|
lua_remove(L, -2);
|
||||||
|
return lua_error(L);
|
||||||
|
}
|
||||||
|
lua_remove(L, -2);
|
||||||
|
} else {
|
||||||
|
if (luaL_loadbuffer(L, code, code_len, found_path) != 0) {
|
||||||
|
return lua_error(L);
|
||||||
|
}
|
||||||
|
if (lua_pcall(L, 0, 1, 0) != 0) {
|
||||||
|
return lua_error(L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pxl8_cart_searcher(lua_State* L) {
|
||||||
|
const char* modname = luaL_checkstring(L, 1);
|
||||||
|
|
||||||
|
pxl8_cart* cart = pxl8_cart_current();
|
||||||
|
if (!cart || !pxl8_cart_is_packed(cart)) {
|
||||||
|
lua_pushstring(L, "\n\tno packed cart mounted");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[512];
|
||||||
|
size_t len = strlen(modname);
|
||||||
|
size_t j = 0;
|
||||||
|
for (size_t i = 0; i < len && j < sizeof(path) - 5; i++) {
|
||||||
|
if (modname[i] == '.') {
|
||||||
|
path[j++] = '/';
|
||||||
|
} else {
|
||||||
|
path[j++] = modname[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path[j] = '\0';
|
||||||
|
|
||||||
|
const char* extensions[] = {".fnl", ".lua", "/init.fnl", "/init.lua"};
|
||||||
|
u8* data = NULL;
|
||||||
|
u32 size = 0;
|
||||||
|
char found_path[512];
|
||||||
|
bool is_fennel = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
snprintf(found_path, sizeof(found_path), "%s%s", path, extensions[i]);
|
||||||
|
if (pxl8_cart_read_file(cart, found_path, &data, &size) == PXL8_OK) {
|
||||||
|
is_fennel = (i == 0 || i == 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
lua_pushfstring(L, "\n\tno file '%s' in cart", path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_pushstring(L, found_path);
|
||||||
|
lua_pushlstring(L, (const char*)data, size);
|
||||||
|
lua_pushboolean(L, is_fennel);
|
||||||
|
pxl8_cart_free_file(data);
|
||||||
|
|
||||||
|
lua_pushcclosure(L, pxl8_cart_loader, 3);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
|
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
size_t j = 0;
|
size_t j = 0;
|
||||||
|
|
@ -100,7 +182,13 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"void pxl8_sprite(pxl8_gfx* ctx, i32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n"
|
"void pxl8_sprite(pxl8_gfx* ctx, i32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n"
|
||||||
"void pxl8_text(pxl8_gfx* ctx, const char* str, i32 x, i32 y, u32 color);\n"
|
"void pxl8_text(pxl8_gfx* ctx, const char* str, i32 x, i32 y, u32 color);\n"
|
||||||
"void pxl8_gfx_color_ramp(pxl8_gfx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n"
|
"void pxl8_gfx_color_ramp(pxl8_gfx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n"
|
||||||
|
"void pxl8_gfx_cycle_palette(pxl8_gfx* ctx, u8 start, u8 count, i32 step);\n"
|
||||||
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
|
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
|
||||||
|
"i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* ctx, u8 start_index, u8 end_index, f32 speed);\n"
|
||||||
|
"void pxl8_gfx_remove_palette_cycle(pxl8_gfx* ctx, i32 cycle_id);\n"
|
||||||
|
"void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* ctx, i32 cycle_id, f32 speed);\n"
|
||||||
|
"void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n"
|
||||||
|
"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\n"
|
||||||
"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n"
|
"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n"
|
||||||
"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n"
|
"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n"
|
||||||
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
|
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
|
||||||
|
|
@ -323,7 +411,17 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
||||||
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
||||||
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
||||||
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n";
|
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
|
||||||
|
"\n"
|
||||||
|
"typedef struct pxl8_save pxl8_save;\n"
|
||||||
|
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
|
||||||
|
"void pxl8_save_destroy(pxl8_save* save);\n"
|
||||||
|
"i32 pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);\n"
|
||||||
|
"i32 pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);\n"
|
||||||
|
"void pxl8_save_free(u8* data);\n"
|
||||||
|
"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n"
|
||||||
|
"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n"
|
||||||
|
"const char* pxl8_save_get_directory(pxl8_save* save);\n";
|
||||||
|
|
||||||
void pxl8_lua_info(const char* msg) {
|
void pxl8_lua_info(const char* msg) {
|
||||||
pxl8_info("%s", msg);
|
pxl8_info("%s", msg);
|
||||||
|
|
@ -387,9 +485,10 @@ static void pxl8_install_embed_searcher(lua_State* L) {
|
||||||
lua_pop(L, 2);
|
lua_pop(L, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_script* pxl8_script_create(void) {
|
pxl8_script* pxl8_script_create(bool repl_mode) {
|
||||||
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
|
pxl8_script* script = (pxl8_script*)calloc(1, sizeof(pxl8_script));
|
||||||
if (!script) return NULL;
|
if (!script) return NULL;
|
||||||
|
script->repl_mode = repl_mode;
|
||||||
|
|
||||||
script->L = luaL_newstate();
|
script->L = luaL_newstate();
|
||||||
if (!script->L) {
|
if (!script->L) {
|
||||||
|
|
@ -427,10 +526,42 @@ pxl8_script* pxl8_script_create(void) {
|
||||||
lua_getglobal(script->L, "fennel");
|
lua_getglobal(script->L, "fennel");
|
||||||
lua_getfield(script->L, -1, "install");
|
lua_getfield(script->L, -1, "install");
|
||||||
if (lua_isfunction(script->L, -1)) {
|
if (lua_isfunction(script->L, -1)) {
|
||||||
if (lua_pcall(script->L, 0, 0, 0) != 0) {
|
// Pass options table for fennel.install()
|
||||||
|
// lambdaAsFn: removes arity checking overhead from fn/lambda
|
||||||
|
// correlate: aligns Lua line numbers with Fennel source
|
||||||
|
// useMetadata: enables docstrings (only in REPL mode for perf)
|
||||||
|
lua_newtable(script->L);
|
||||||
|
lua_pushboolean(script->L, 1);
|
||||||
|
lua_setfield(script->L, -2, "lambdaAsFn");
|
||||||
|
lua_pushboolean(script->L, 1);
|
||||||
|
lua_setfield(script->L, -2, "correlate");
|
||||||
|
if (script->repl_mode) {
|
||||||
|
lua_pushboolean(script->L, 1);
|
||||||
|
lua_setfield(script->L, -2, "useMetadata");
|
||||||
|
lua_pushboolean(script->L, 1);
|
||||||
|
lua_setfield(script->L, -2, "assertAsRepl");
|
||||||
|
}
|
||||||
|
if (lua_pcall(script->L, 1, 0, 0) != 0) {
|
||||||
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
|
pxl8_warn("Failed to install fennel searcher: %s", lua_tostring(script->L, -1));
|
||||||
lua_pop(script->L, 1);
|
lua_pop(script->L, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lua_getglobal(script->L, "package");
|
||||||
|
lua_getfield(script->L, -1, "loaders");
|
||||||
|
if (lua_isnil(script->L, -1)) {
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
lua_getfield(script->L, -1, "searchers");
|
||||||
|
}
|
||||||
|
if (lua_istable(script->L, -1)) {
|
||||||
|
int n = (int)lua_objlen(script->L, -1);
|
||||||
|
for (int i = n; i >= 2; i--) {
|
||||||
|
lua_rawgeti(script->L, -1, i);
|
||||||
|
lua_rawseti(script->L, -2, i + 1);
|
||||||
|
}
|
||||||
|
lua_pushcfunction(script->L, pxl8_cart_searcher);
|
||||||
|
lua_rawseti(script->L, -2, 2);
|
||||||
|
}
|
||||||
|
lua_pop(script->L, 2);
|
||||||
} else {
|
} else {
|
||||||
lua_pop(script->L, 1);
|
lua_pop(script->L, 1);
|
||||||
}
|
}
|
||||||
|
|
@ -574,16 +705,45 @@ pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filenam
|
||||||
return PXL8_ERROR_SCRIPT_ERROR;
|
return PXL8_ERROR_SCRIPT_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_getfield(script->L, -1, "dofile");
|
pxl8_cart* cart = pxl8_cart_current();
|
||||||
lua_pushstring(script->L, basename);
|
|
||||||
|
|
||||||
pxl8_result result = PXL8_OK;
|
pxl8_result result = PXL8_OK;
|
||||||
if (lua_pcall(script->L, 1, 0, 0) != 0) {
|
|
||||||
pxl8_script_set_error(script, lua_tostring(script->L, -1));
|
if (cart && pxl8_cart_is_packed(cart)) {
|
||||||
lua_pop(script->L, 1);
|
u8* data = NULL;
|
||||||
result = PXL8_ERROR_SCRIPT_ERROR;
|
u32 size = 0;
|
||||||
|
if (pxl8_cart_read_file(cart, basename, &data, &size) != PXL8_OK) {
|
||||||
|
pxl8_script_set_error(script, "Failed to read script from cart");
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
lua_getfield(script->L, -1, "eval");
|
||||||
|
lua_pushlstring(script->L, (const char*)data, size);
|
||||||
|
|
||||||
|
lua_createtable(script->L, 0, 1);
|
||||||
|
lua_pushstring(script->L, basename);
|
||||||
|
lua_setfield(script->L, -2, "filename");
|
||||||
|
|
||||||
|
pxl8_cart_free_file(data);
|
||||||
|
|
||||||
|
if (lua_pcall(script->L, 2, 0, 0) != 0) {
|
||||||
|
pxl8_script_set_error(script, lua_tostring(script->L, -1));
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
result = PXL8_ERROR_SCRIPT_ERROR;
|
||||||
|
} else {
|
||||||
|
script->last_error[0] = '\0';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
script->last_error[0] = '\0';
|
lua_getfield(script->L, -1, "dofile");
|
||||||
|
lua_pushstring(script->L, basename);
|
||||||
|
|
||||||
|
if (lua_pcall(script->L, 1, 0, 0) != 0) {
|
||||||
|
pxl8_script_set_error(script, lua_tostring(script->L, -1));
|
||||||
|
lua_pop(script->L, 1);
|
||||||
|
result = PXL8_ERROR_SCRIPT_ERROR;
|
||||||
|
} else {
|
||||||
|
script->last_error[0] = '\0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lua_pop(script->L, 1);
|
lua_pop(script->L, 1);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ typedef struct pxl8_script_repl_command pxl8_script_repl_command;
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pxl8_script* pxl8_script_create(void);
|
pxl8_script* pxl8_script_create(bool repl_mode);
|
||||||
void pxl8_script_destroy(pxl8_script* script);
|
void pxl8_script_destroy(pxl8_script* script);
|
||||||
|
|
||||||
const char* pxl8_script_get_last_error(pxl8_script* script);
|
const char* pxl8_script_get_last_error(pxl8_script* script);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ typedef struct pxl8_sdl3_context {
|
||||||
u32 atlas_height;
|
u32 atlas_height;
|
||||||
} pxl8_sdl3_context;
|
} pxl8_sdl3_context;
|
||||||
|
|
||||||
static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution,
|
static void* sdl3_create(pxl8_pixel_mode mode, pxl8_resolution resolution,
|
||||||
const char* title, i32 win_w, i32 win_h) {
|
const char* title, i32 win_w, i32 win_h) {
|
||||||
(void)mode;
|
(void)mode;
|
||||||
|
|
||||||
|
|
@ -120,7 +120,7 @@ static void sdl3_present(void* platform_data) {
|
||||||
|
|
||||||
static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
||||||
i32 w, i32 h, const u32* palette,
|
i32 w, i32 h, const u32* palette,
|
||||||
pxl8_color_mode mode) {
|
pxl8_pixel_mode mode) {
|
||||||
if (!platform_data || !fb) return;
|
if (!platform_data || !fb) return;
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||||
|
|
@ -133,16 +133,15 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
||||||
ctx->rgba_buffer_size = needed_size;
|
ctx->rgba_buffer_size = needed_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (mode == PXL8_PIXEL_HICOLOR) {
|
||||||
const u16* fb16 = (const u16*)fb;
|
const u16* fb16 = (const u16*)fb;
|
||||||
for (i32 i = 0; i < w * h; i++) {
|
for (i32 i = 0; i < w * h; i++) {
|
||||||
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]);
|
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(fb16[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u32 palette_size = pxl8_get_palette_size(mode);
|
|
||||||
for (i32 i = 0; i < w * h; i++) {
|
for (i32 i = 0; i < w * h; i++) {
|
||||||
u8 index = fb[i];
|
u8 index = fb[i];
|
||||||
ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0xFF000000;
|
ctx->rgba_buffer[i] = palette[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +149,7 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
|
static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
|
||||||
const u32* palette, pxl8_color_mode mode) {
|
const u32* palette, pxl8_pixel_mode mode) {
|
||||||
if (!platform_data || !atlas) return;
|
if (!platform_data || !atlas) return;
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||||
|
|
@ -195,17 +194,16 @@ static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas,
|
||||||
ctx->rgba_buffer_size = needed_size;
|
ctx->rgba_buffer_size = needed_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == PXL8_COLOR_MODE_HICOLOR) {
|
if (mode == PXL8_PIXEL_HICOLOR) {
|
||||||
const u16* atlas16 = (const u16*)atlas_pixels;
|
const u16* atlas16 = (const u16*)atlas_pixels;
|
||||||
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
||||||
u16 pixel = atlas16[i];
|
u16 pixel = atlas16[i];
|
||||||
ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000;
|
ctx->rgba_buffer[i] = (pixel != 0) ? pxl8_rgb565_to_rgba32(pixel) : 0x00000000;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
u32 palette_size = pxl8_get_palette_size(mode);
|
|
||||||
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
for (u32 i = 0; i < atlas_w * atlas_h; i++) {
|
||||||
u8 index = atlas_pixels[i];
|
u8 index = atlas_pixels[i];
|
||||||
ctx->rgba_buffer[i] = (index < palette_size) ? palette[index] : 0x00000000;
|
ctx->rgba_buffer[i] = palette[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,11 +329,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
||||||
i32 window_width, window_height;
|
i32 window_width, window_height;
|
||||||
SDL_GetWindowSize(window, &window_width, &window_height);
|
SDL_GetWindowSize(window, &window_width, &window_height);
|
||||||
|
|
||||||
pxl8_resolution resolution = pxl8_get_resolution(sys);
|
i32 render_w = pxl8_gfx_get_width(gfx);
|
||||||
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
i32 render_h = pxl8_gfx_get_height(gfx);
|
||||||
|
|
||||||
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
|
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
|
||||||
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_size.w, render_size.h);
|
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_w, render_h);
|
||||||
|
|
||||||
input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
||||||
input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ void pxl8_destroy(pxl8* sys);
|
||||||
f32 pxl8_get_fps(const pxl8* sys);
|
f32 pxl8_get_fps(const pxl8* sys);
|
||||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
|
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
|
||||||
pxl8_input_state* pxl8_get_input(const pxl8* sys);
|
pxl8_input_state* pxl8_get_input(const pxl8* sys);
|
||||||
u32 pxl8_get_palette_size(pxl8_color_mode mode);
|
|
||||||
pxl8_resolution pxl8_get_resolution(const pxl8* sys);
|
|
||||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
|
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
|
||||||
bool pxl8_is_running(const pxl8* sys);
|
bool pxl8_is_running(const pxl8* sys);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ struct pxl8_tilesheet {
|
||||||
u32 tiles_per_row;
|
u32 tiles_per_row;
|
||||||
u32 total_tiles;
|
u32 total_tiles;
|
||||||
u32 width;
|
u32 width;
|
||||||
pxl8_color_mode color_mode;
|
pxl8_pixel_mode pixel_mode;
|
||||||
u32 ref_count;
|
u32 ref_count;
|
||||||
pxl8_tile_animation* animations;
|
pxl8_tile_animation* animations;
|
||||||
u32 animation_count;
|
u32 animation_count;
|
||||||
|
|
@ -545,7 +545,7 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
|
||||||
tilemap->tilesheet->height = tilesheet_height;
|
tilemap->tilesheet->height = tilesheet_height;
|
||||||
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
||||||
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
||||||
tilemap->tilesheet->color_mode = PXL8_COLOR_MODE_MEGA;
|
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||||
|
|
||||||
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid);
|
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid);
|
||||||
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool));
|
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool));
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ struct pxl8_tilesheet {
|
||||||
u32 total_tiles;
|
u32 total_tiles;
|
||||||
u32 width;
|
u32 width;
|
||||||
|
|
||||||
pxl8_color_mode color_mode;
|
pxl8_pixel_mode pixel_mode;
|
||||||
u32 ref_count;
|
u32 ref_count;
|
||||||
|
|
||||||
pxl8_tile_animation* animations;
|
pxl8_tile_animation* animations;
|
||||||
|
|
@ -104,13 +104,13 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
tilesheet->height = height;
|
tilesheet->height = height;
|
||||||
tilesheet->tiles_per_row = width / tilesheet->tile_size;
|
tilesheet->tiles_per_row = width / tilesheet->tile_size;
|
||||||
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
|
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
|
||||||
tilesheet->color_mode = pxl8_gfx_get_color_mode(gfx);
|
tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||||
|
|
||||||
u32 pixel_count = width * height;
|
u32 pixel_count = width * height;
|
||||||
u16 ase_depth = ase_file.header.color_depth;
|
u16 ase_depth = ase_file.header.color_depth;
|
||||||
bool gfx_hicolor = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR);
|
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
||||||
|
|
||||||
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->color_mode);
|
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||||
tilesheet->data = malloc(data_size);
|
tilesheet->data = malloc(data_size);
|
||||||
if (!tilesheet->data) {
|
if (!tilesheet->data) {
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
|
|
@ -136,7 +136,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
}
|
}
|
||||||
} else if (ase_depth == 8 && gfx_hicolor) {
|
} else if (ase_depth == 8 && gfx_hicolor) {
|
||||||
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
|
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
|
||||||
tilesheet->color_mode = PXL8_COLOR_MODE_FAMI;
|
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||||
u8* new_data = realloc(tilesheet->data, pixel_count);
|
u8* new_data = realloc(tilesheet->data, pixel_count);
|
||||||
if (!new_data) {
|
if (!new_data) {
|
||||||
free(tilesheet->data);
|
free(tilesheet->data);
|
||||||
|
|
@ -164,7 +164,7 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 valid_tiles = 0;
|
u32 valid_tiles = 0;
|
||||||
bool is_hicolor = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR);
|
bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
||||||
|
|
||||||
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
|
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
|
||||||
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||||
|
|
@ -310,7 +310,7 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
|
||||||
|
|
||||||
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
||||||
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
||||||
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->color_mode);
|
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||||
|
|
||||||
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
||||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||||
|
|
|
||||||
|
|
@ -168,8 +168,8 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
|
||||||
i32 block_size = (i32)(max_block_size * progress);
|
i32 block_size = (i32)(max_block_size * progress);
|
||||||
if (block_size < 1) block_size = 1;
|
if (block_size < 1) block_size = 1;
|
||||||
|
|
||||||
pxl8_color_mode mode = pxl8_gfx_get_color_mode(gfx);
|
pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||||
bool has_fb = (mode == PXL8_COLOR_MODE_HICOLOR)
|
bool has_fb = (mode == PXL8_PIXEL_HICOLOR)
|
||||||
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
|
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
|
||||||
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
|
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
|
||||||
if (!has_fb) break;
|
if (!has_fb) break;
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,10 @@ typedef __int128_t i128;
|
||||||
typedef __uint128_t u128;
|
typedef __uint128_t u128;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum pxl8_color_mode {
|
typedef enum pxl8_pixel_mode {
|
||||||
PXL8_COLOR_MODE_FAMI,
|
PXL8_PIXEL_INDEXED,
|
||||||
PXL8_COLOR_MODE_GBA,
|
PXL8_PIXEL_HICOLOR
|
||||||
PXL8_COLOR_MODE_HICOLOR,
|
} pxl8_pixel_mode;
|
||||||
PXL8_COLOR_MODE_MEGA,
|
|
||||||
PXL8_COLOR_MODE_SNES
|
|
||||||
} pxl8_color_mode;
|
|
||||||
|
|
||||||
typedef enum pxl8_cursor {
|
typedef enum pxl8_cursor {
|
||||||
PXL8_CURSOR_ARROW,
|
PXL8_CURSOR_ARROW,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue