add a byte stream: pxl8_stream

This commit is contained in:
asrael 2025-10-12 05:02:19 -05:00
parent ede16ca7de
commit f07c00c251
7 changed files with 349 additions and 272 deletions

View file

@ -46,11 +46,11 @@
(when (pxl8.key_pressed "8") (when (pxl8.key_pressed "8")
(set current-effect 8)) (set current-effect 8))
(when (pxl8.key_pressed "9") (when (pxl8.key_pressed "9")
(set use-nes-palette (not use-nes-palette))
(local palette-path (if use-nes-palette "res/palettes/nes.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path))
(when (pxl8.key_pressed "0")
(set current-effect 0)) (set current-effect 0))
(when (pxl8.key_pressed "0")
(set use-nes-palette (not use-nes-palette))
(local palette-path (if use-nes-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path))
(case current-effect (case current-effect
1 (do 1 (do

Binary file not shown.

Binary file not shown.

View file

@ -14,35 +14,24 @@
#include "pxl8_io.h" #include "pxl8_io.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
static u16 read_u16_le(const u8* data) { static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
return data[0] | (data[1] << 8); header->file_size = pxl8_read_u32(stream);
} header->magic = pxl8_read_u16(stream);
header->frames = pxl8_read_u16(stream);
static u32 read_u32_le(const u8* data) { header->width = pxl8_read_u16(stream);
return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); header->height = pxl8_read_u16(stream);
} header->color_depth = pxl8_read_u16(stream);
header->flags = pxl8_read_u32(stream);
static i16 read_i16_le(const u8* data) { header->speed = pxl8_read_u16(stream);
return (i16)read_u16_le(data); pxl8_skip_bytes(stream, 4);
} header->transparent_index = pxl8_read_u32(stream);
header->n_colors = pxl8_read_u8(stream);
static pxl8_result parse_ase_header(const u8* data, pxl8_ase_header* header) { header->pixel_width = pxl8_read_u8(stream);
header->file_size = read_u32_le(data); header->pixel_height = pxl8_read_u8(stream);
header->magic = read_u16_le(data + 4); header->grid_x = pxl8_read_i16(stream);
header->frames = read_u16_le(data + 6); header->grid_y = pxl8_read_i16(stream);
header->width = read_u16_le(data + 8); header->grid_width = pxl8_read_u16(stream);
header->height = read_u16_le(data + 10); header->grid_height = pxl8_read_u16(stream);
header->color_depth = read_u16_le(data + 12);
header->flags = read_u32_le(data + 14);
header->speed = read_u16_le(data + 18);
header->transparent_index = read_u32_le(data + 24);
header->n_colors = data[28];
header->pixel_width = data[29];
header->pixel_height = data[30];
header->grid_x = read_i16_le(data + 31);
header->grid_y = read_i16_le(data + 33);
header->grid_width = read_u16_le(data + 35);
header->grid_height = read_u16_le(data + 37);
if (header->magic != PXL8_ASE_MAGIC) { if (header->magic != PXL8_ASE_MAGIC) {
pxl8_error("Invalid ASE file magic: 0x%04X", header->magic); pxl8_error("Invalid ASE file magic: 0x%04X", header->magic);
@ -52,20 +41,19 @@ static pxl8_result parse_ase_header(const u8* data, pxl8_ase_header* header) {
return PXL8_OK; return PXL8_OK;
} }
static pxl8_result parse_old_palette_chunk(const u8* data, pxl8_ase_palette* palette) { static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
u16 packet_count = read_u16_le(data); u16 packet_count = pxl8_read_u16(stream);
const u8* packet_data = data + 2;
u32 total_colors = 0; u32 total_colors = 0;
const u8* temp_data = packet_data; u32 temp_pos = pxl8_stream_position(stream);
for (u16 packet = 0; packet < packet_count; packet++) { for (u16 packet = 0; packet < packet_count; packet++) {
u8 skip_colors = temp_data[0]; u8 skip_colors = pxl8_read_u8(stream);
u8 colors_in_packet = temp_data[1]; u8 colors_in_packet = pxl8_read_u8(stream);
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet; u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
total_colors = skip_colors + actual_colors; total_colors += skip_colors + actual_colors;
temp_data += 2 + (actual_colors * 3); pxl8_skip_bytes(stream, actual_colors * 3);
} }
palette->entry_count = total_colors; palette->entry_count = total_colors;
palette->first_color = 0; palette->first_color = 0;
palette->last_color = total_colors - 1; palette->last_color = total_colors - 1;
@ -73,119 +61,127 @@ static pxl8_result parse_old_palette_chunk(const u8* data, pxl8_ase_palette* pal
if (!palette->colors) { if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
pxl8_stream_seek(stream, temp_pos);
u32 color_index = 0; u32 color_index = 0;
for (u16 packet = 0; packet < packet_count; packet++) { for (u16 packet = 0; packet < packet_count; packet++) {
u8 skip_colors = packet_data[0]; u8 skip_colors = pxl8_read_u8(stream);
u8 colors_in_packet = packet_data[1]; u8 colors_in_packet = pxl8_read_u8(stream);
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet; u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
for (u32 i = 0; i < skip_colors && color_index < total_colors; i++, color_index++) {
palette->colors[color_index] = 0xFF000000;
}
packet_data += 2;
for (u32 i = 0; i < actual_colors && color_index < total_colors; i++, color_index++) {
u8 r = packet_data[0];
u8 g = packet_data[1];
u8 b = packet_data[2];
palette->colors[color_index] = 0xFF000000 | (b << 16) | (g << 8) | r; for (u32 skip = 0; skip < skip_colors && color_index < total_colors; skip++) {
packet_data += 3; palette->colors[color_index++] = 0xFF000000;
}
for (u32 color = 0; color < actual_colors && color_index < total_colors; color++) {
u8 r = pxl8_read_u8(stream);
u8 g = pxl8_read_u8(stream);
u8 b = pxl8_read_u8(stream);
palette->colors[color_index++] = 0xFF000000 | (b << 16) | (g << 8) | r;
} }
} }
return PXL8_OK; return PXL8_OK;
} }
static pxl8_result parse_layer_chunk(const u8* data, pxl8_ase_layer* layer) { static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer) {
layer->flags = read_u16_le(data); layer->flags = pxl8_read_u16(stream);
layer->layer_type = read_u16_le(data + 2); layer->layer_type = pxl8_read_u16(stream);
layer->child_level = read_u16_le(data + 4); layer->child_level = pxl8_read_u16(stream);
layer->blend_mode = read_u16_le(data + 10); pxl8_skip_bytes(stream, 4);
layer->opacity = data[12]; layer->blend_mode = pxl8_read_u16(stream);
layer->opacity = pxl8_read_u8(stream);
pxl8_skip_bytes(stream, 3);
u16 name_len = read_u16_le(data + 16); u16 name_len = pxl8_read_u16(stream);
if (name_len > 0) { if (name_len > 0) {
layer->name = (char*)SDL_malloc(name_len + 1); layer->name = (char*)SDL_malloc(name_len + 1);
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY; if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
memcpy(layer->name, data + 18, name_len); pxl8_read_bytes(stream, layer->name, name_len);
layer->name[name_len] = '\0'; layer->name[name_len] = '\0';
} else { } else {
layer->name = NULL; layer->name = NULL;
} }
return PXL8_OK; return PXL8_OK;
} }
static pxl8_result parse_palette_chunk(const u8* data, pxl8_ase_palette* palette) { static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
palette->entry_count = read_u32_le(data); pxl8_skip_bytes(stream, 4);
palette->first_color = read_u32_le(data + 4); palette->first_color = pxl8_read_u32(stream);
palette->last_color = read_u32_le(data + 8); palette->last_color = pxl8_read_u32(stream);
pxl8_skip_bytes(stream, 8);
u32 color_count = palette->entry_count;
u32 color_count = palette->last_color - palette->first_color + 1;
if (color_count == 0 || color_count > 65536) {
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
}
palette->colors = (u32*)SDL_malloc(color_count * sizeof(u32)); palette->colors = (u32*)SDL_malloc(color_count * sizeof(u32));
if (!palette->colors) { if (!palette->colors) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
const u8* color_data = data + 20; palette->entry_count = color_count;
for (u32 i = 0; i < color_count; i++) { for (u32 i = 0; i < color_count; i++) {
u16 flags = read_u16_le(color_data); u16 flags = pxl8_read_u16(stream);
u8 r = color_data[2]; u8 r = pxl8_read_u8(stream);
u8 g = color_data[3]; u8 g = pxl8_read_u8(stream);
u8 b = color_data[4]; u8 b = pxl8_read_u8(stream);
u8 a = color_data[5]; u8 a = pxl8_read_u8(stream);
palette->colors[i] = (a << 24) | (b << 16) | (g << 8) | r; palette->colors[i] = (a << 24) | (b << 16) | (g << 8) | r;
color_data += 6;
if (flags & 1) { if (flags & 1) {
u16 name_len = read_u16_le(color_data); u16 name_len = pxl8_read_u16(stream);
color_data += 2 + name_len; pxl8_skip_bytes(stream, name_len);
} }
} }
return PXL8_OK; return PXL8_OK;
} }
static pxl8_result parse_cel_chunk(const u8* data, u32 chunk_size, pxl8_ase_cel* cel) { static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase_cel* cel) {
if (chunk_size < 9) { if (chunk_size < 9) {
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
cel->layer_index = read_u16_le(data); cel->layer_index = pxl8_read_u16(stream);
cel->x = read_i16_le(data + 2); cel->x = pxl8_read_i16(stream);
cel->y = read_i16_le(data + 4); cel->y = pxl8_read_i16(stream);
cel->opacity = data[6]; cel->opacity = pxl8_read_u8(stream);
cel->cel_type = read_u16_le(data + 7); cel->cel_type = pxl8_read_u16(stream);
pxl8_skip_bytes(stream, 7);
if (cel->cel_type == 2) { if (cel->cel_type == 2) {
if (chunk_size < 20) { if (chunk_size < 20) {
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
cel->width = read_u16_le(data + 16); cel->width = pxl8_read_u16(stream);
cel->height = read_u16_le(data + 18); cel->height = pxl8_read_u16(stream);
u32 pixel_data_size = cel->width * cel->height; u32 pixel_data_size = cel->width * cel->height;
u32 compressed_data_size = chunk_size - 20; u32 compressed_data_size = chunk_size - 20;
cel->pixel_data = (u8*)SDL_malloc(pixel_data_size); cel->pixel_data = (u8*)SDL_malloc(pixel_data_size);
if (!cel->pixel_data) { if (!cel->pixel_data) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
const u8* compressed_data = pxl8_read_ptr(stream, compressed_data_size);
mz_ulong dest_len = pixel_data_size; mz_ulong dest_len = pixel_data_size;
i32 result = mz_uncompress(cel->pixel_data, &dest_len, data + 20, compressed_data_size); i32 result = mz_uncompress(cel->pixel_data, &dest_len, compressed_data, compressed_data_size);
if (result != MZ_OK) { if (result != MZ_OK) {
pxl8_error("Failed to decompress cel data: miniz error %d", result); pxl8_error("Failed to decompress cel data: miniz error %d", result);
SDL_free(cel->pixel_data); SDL_free(cel->pixel_data);
cel->pixel_data = NULL; cel->pixel_data = NULL;
return PXL8_ERROR_ASE_MALFORMED_CHUNK; return PXL8_ERROR_ASE_MALFORMED_CHUNK;
} }
if (dest_len != pixel_data_size) { if (dest_len != pixel_data_size) {
pxl8_warn("Decompressed size mismatch: expected %u, got %lu", pixel_data_size, dest_len); pxl8_warn("Decompressed size mismatch: expected %u, got %lu", pixel_data_size, dest_len);
} }
@ -213,7 +209,9 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
return PXL8_ERROR_ASE_TRUNCATED_FILE; return PXL8_ERROR_ASE_TRUNCATED_FILE;
} }
result = parse_ase_header(file_data, &ase_file->header); pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
result = parse_ase_header(&stream, &ase_file->header);
if (result != PXL8_OK) { if (result != PXL8_OK) {
pxl8_io_free_binary_data(file_data); pxl8_io_free_binary_data(file_data);
return result; return result;
@ -226,13 +224,17 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
return PXL8_ERROR_OUT_OF_MEMORY; return PXL8_ERROR_OUT_OF_MEMORY;
} }
const u8* frame_data = file_data + 128; pxl8_stream_seek(&stream, 128);
for (u16 frame_idx = 0; frame_idx < ase_file->header.frames; frame_idx++) { for (u16 frame_idx = 0; frame_idx < ase_file->header.frames; frame_idx++) {
u32 frame_start = pxl8_stream_position(&stream);
pxl8_ase_frame_header frame_header; pxl8_ase_frame_header frame_header;
frame_header.frame_bytes = read_u32_le(frame_data); frame_header.frame_bytes = pxl8_read_u32(&stream);
frame_header.magic = read_u16_le(frame_data + 4); frame_header.magic = pxl8_read_u16(&stream);
frame_header.chunks = read_u16_le(frame_data + 6); u16 old_chunks = pxl8_read_u16(&stream);
frame_header.duration = read_u16_le(frame_data + 8); frame_header.duration = pxl8_read_u16(&stream);
pxl8_skip_bytes(&stream, 2);
if (frame_header.magic != PXL8_ASE_FRAME_MAGIC) { if (frame_header.magic != PXL8_ASE_FRAME_MAGIC) {
pxl8_error("Invalid frame magic: 0x%04X", frame_header.magic); pxl8_error("Invalid frame magic: 0x%04X", frame_header.magic);
@ -240,12 +242,19 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
break; break;
} }
if (old_chunks == 0xFFFF || old_chunks == 0xFFFE) {
frame_header.chunks = pxl8_read_u32(&stream);
} else {
frame_header.chunks = old_chunks;
pxl8_skip_bytes(&stream, 4);
}
pxl8_ase_frame* frame = &ase_file->frames[frame_idx]; pxl8_ase_frame* frame = &ase_file->frames[frame_idx];
frame->frame_id = frame_idx; frame->frame_id = frame_idx;
frame->width = ase_file->header.width; frame->width = ase_file->header.width;
frame->height = ase_file->header.height; frame->height = ase_file->header.height;
frame->duration = frame_header.duration; frame->duration = frame_header.duration;
u32 pixel_count = frame->width * frame->height; u32 pixel_count = frame->width * frame->height;
frame->pixels = (u8*)SDL_calloc(pixel_count, sizeof(u8)); frame->pixels = (u8*)SDL_calloc(pixel_count, sizeof(u8));
if (!frame->pixels) { if (!frame->pixels) {
@ -253,22 +262,28 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
break; break;
} }
const u8* chunk_data = frame_data + 16;
for (u16 chunk_idx = 0; chunk_idx < frame_header.chunks; chunk_idx++) { for (u16 chunk_idx = 0; chunk_idx < frame_header.chunks; chunk_idx++) {
pxl8_ase_chunk_header chunk_header; u32 chunk_start = pxl8_stream_position(&stream);
chunk_header.chunk_size = read_u32_le(chunk_data);
chunk_header.chunk_type = read_u16_le(chunk_data + 4);
const u8* chunk_payload = chunk_data + 6; pxl8_ase_chunk_header chunk_header;
chunk_header.chunk_size = pxl8_read_u32(&stream);
chunk_header.chunk_type = pxl8_read_u16(&stream);
if (chunk_header.chunk_size < 6 || chunk_header.chunk_size > frame_header.frame_bytes) {
pxl8_error("Invalid chunk size %u in ASE file (frame_bytes=%u)",
chunk_header.chunk_size, frame_header.frame_bytes);
result = PXL8_ERROR_ASE_MALFORMED_CHUNK;
break;
}
switch (chunk_header.chunk_type) { switch (chunk_header.chunk_type) {
case PXL8_ASE_CHUNK_OLD_PALETTE: // 0x0004 case PXL8_ASE_CHUNK_OLD_PALETTE:
if (!ase_file->palette.colors) { if (!ase_file->palette.colors) {
result = parse_old_palette_chunk(chunk_payload, &ase_file->palette); result = parse_old_palette_chunk(&stream, &ase_file->palette);
} }
break; break;
case PXL8_ASE_CHUNK_LAYER: { // 0x2004 case PXL8_ASE_CHUNK_LAYER: {
ase_file->layers = ase_file->layers =
(pxl8_ase_layer*)SDL_realloc(ase_file->layers, (pxl8_ase_layer*)SDL_realloc(ase_file->layers,
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer)); (ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
@ -276,17 +291,17 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
result = PXL8_ERROR_OUT_OF_MEMORY; result = PXL8_ERROR_OUT_OF_MEMORY;
break; break;
} }
result = parse_layer_chunk(chunk_payload, &ase_file->layers[ase_file->layer_count]); result = parse_layer_chunk(&stream, &ase_file->layers[ase_file->layer_count]);
if (result == PXL8_OK) { if (result == PXL8_OK) {
ase_file->layer_count++; ase_file->layer_count++;
} }
break; break;
} }
case PXL8_ASE_CHUNK_CEL: { // 0x2005 case PXL8_ASE_CHUNK_CEL: {
pxl8_ase_cel cel = {0}; pxl8_ase_cel cel = {0};
result = parse_cel_chunk(chunk_payload, chunk_header.chunk_size - 6, &cel); result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
if (result == PXL8_OK && cel.pixel_data) { if (result == PXL8_OK && cel.pixel_data) {
u32 copy_width = (cel.width < frame->width) ? cel.width : frame->width; u32 copy_width = (cel.width < frame->width) ? cel.width : frame->width;
u32 copy_height = (cel.height < frame->height) ? cel.height : frame->height; u32 copy_height = (cel.height < frame->height) ? cel.height : frame->height;
@ -314,26 +329,27 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
} }
break; break;
} }
case PXL8_ASE_CHUNK_PALETTE: // 0x2019 case PXL8_ASE_CHUNK_PALETTE:
if (ase_file->palette.colors) { if (ase_file->palette.colors) {
SDL_free(ase_file->palette.colors); SDL_free(ase_file->palette.colors);
ase_file->palette.colors = NULL;
} }
result = parse_palette_chunk(chunk_payload, &ase_file->palette); result = parse_palette_chunk(&stream, &ase_file->palette);
break; break;
default: default:
break; break;
} }
if (result != PXL8_OK) break; if (result != PXL8_OK) break;
chunk_data += chunk_header.chunk_size; pxl8_stream_seek(&stream, chunk_start + chunk_header.chunk_size);
} }
if (result != PXL8_OK) break; if (result != PXL8_OK) break;
frame_data += frame_header.frame_bytes; pxl8_stream_seek(&stream, frame_start + frame_header.frame_bytes);
} }
pxl8_io_free_binary_data(file_data); pxl8_io_free_binary_data(file_data);

View file

@ -4,6 +4,7 @@
#include "pxl8_bsp.h" #include "pxl8_bsp.h"
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_io.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#define BSP_VERSION 29 #define BSP_VERSION 29
@ -37,27 +38,20 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT]; pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header; } pxl8_bsp_header;
static u16 read_u16(const u8* data) { static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
return (u16)data[0] | ((u16)data[1] << 8); pxl8_vec3 v;
v.x = pxl8_read_f32(stream);
v.y = pxl8_read_f32(stream);
v.z = pxl8_read_f32(stream);
return v;
} }
static u32 read_u32(const u8* data) { static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
return (u32)data[0] | ((u32)data[1] << 8) | ((u32)data[2] << 16) | ((u32)data[3] << 24); if (chunk->size == 0) return true;
} if (chunk->offset >= file_size) return false;
if (chunk->offset + chunk->size > file_size) return false;
static i16 read_i16(const u8* data) { if (chunk->size % element_size != 0) return false;
return (i16)read_u16(data); return true;
}
static i32 read_i32(const u8* data) {
return (i32)read_u32(data);
}
static f32 read_f32(const u8* data) {
u32 val = read_u32(data);
f32 result;
memcpy(&result, &val, sizeof(f32));
return result;
} }
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
@ -78,8 +72,10 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
pxl8_bsp_header header; pxl8_bsp_header header;
header.version = read_u32(file_data); header.version = pxl8_read_u32(&stream);
if (header.version != BSP_VERSION) { if (header.version != BSP_VERSION) {
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION); pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
@ -88,184 +84,169 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
} }
for (i32 i = 0; i < CHUNK_COUNT; i++) { for (i32 i = 0; i < CHUNK_COUNT; i++) {
header.chunks[i].offset = read_u32(file_data + 4 + i * 8); header.chunks[i].offset = pxl8_read_u32(&stream);
header.chunks[i].size = read_u32(file_data + 4 + i * 8 + 4); header.chunks[i].size = pxl8_read_u32(&stream);
} }
pxl8_bsp_chunk* vertices_chunk = &header.chunks[CHUNK_VERTICES]; pxl8_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
bsp->num_vertices = vertices_chunk->size / 12; if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
bsp->num_vertices = chunk->size / 12;
if (bsp->num_vertices > 0) { if (bsp->num_vertices > 0) {
bsp->vertices = (pxl8_bsp_vertex*)SDL_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex)); bsp->vertices = SDL_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
const u8* data = file_data + vertices_chunk->offset; if (!bsp->vertices) goto error_cleanup;
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_vertices; i++) { for (u32 i = 0; i < bsp->num_vertices; i++) {
bsp->vertices[i].position.x = read_f32(data + i * 12); bsp->vertices[i].position = read_vec3(&stream);
bsp->vertices[i].position.y = read_f32(data + i * 12 + 4);
bsp->vertices[i].position.z = read_f32(data + i * 12 + 8);
} }
} }
pxl8_bsp_chunk* edges_chunk = &header.chunks[CHUNK_EDGES]; chunk = &header.chunks[CHUNK_EDGES];
bsp->num_edges = edges_chunk->size / 4; if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_edges = chunk->size / 4;
if (bsp->num_edges > 0) { if (bsp->num_edges > 0) {
bsp->edges = (pxl8_bsp_edge*)SDL_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge)); bsp->edges = SDL_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
const u8* data = file_data + edges_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_edges; i++) { for (u32 i = 0; i < bsp->num_edges; i++) {
bsp->edges[i].vertex[0] = read_u16(data + i * 4); bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
bsp->edges[i].vertex[1] = read_u16(data + i * 4 + 2); bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
} }
} }
pxl8_bsp_chunk* surfedges_chunk = &header.chunks[CHUNK_SURFEDGES]; chunk = &header.chunks[CHUNK_SURFEDGES];
bsp->num_surfedges = surfedges_chunk->size / 4; if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_surfedges = chunk->size / 4;
if (bsp->num_surfedges > 0) { if (bsp->num_surfedges > 0) {
bsp->surfedges = (i32*)SDL_calloc(bsp->num_surfedges, sizeof(i32)); bsp->surfedges = SDL_calloc(bsp->num_surfedges, sizeof(i32));
const u8* data = file_data + surfedges_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_surfedges; i++) { for (u32 i = 0; i < bsp->num_surfedges; i++) {
bsp->surfedges[i] = read_i32(data + i * 4); bsp->surfedges[i] = pxl8_read_i32(&stream);
} }
} }
pxl8_bsp_chunk* planes_chunk = &header.chunks[CHUNK_PLANES]; chunk = &header.chunks[CHUNK_PLANES];
bsp->num_planes = planes_chunk->size / 20; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_planes = chunk->size / 20;
if (bsp->num_planes > 0) { if (bsp->num_planes > 0) {
bsp->planes = (pxl8_bsp_plane*)SDL_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane)); bsp->planes = SDL_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
const u8* data = file_data + planes_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_planes; i++) { for (u32 i = 0; i < bsp->num_planes; i++) {
bsp->planes[i].normal.x = read_f32(data + i * 20); bsp->planes[i].normal = read_vec3(&stream);
bsp->planes[i].normal.y = read_f32(data + i * 20 + 4); bsp->planes[i].dist = pxl8_read_f32(&stream);
bsp->planes[i].normal.z = read_f32(data + i * 20 + 8); bsp->planes[i].type = pxl8_read_i32(&stream);
bsp->planes[i].dist = read_f32(data + i * 20 + 12);
bsp->planes[i].type = read_i32(data + i * 20 + 16);
} }
} }
pxl8_bsp_chunk* texinfo_chunk = &header.chunks[CHUNK_TEXINFO]; chunk = &header.chunks[CHUNK_TEXINFO];
bsp->num_texinfo = texinfo_chunk->size / 40; if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
bsp->num_texinfo = chunk->size / 40;
if (bsp->num_texinfo > 0) { if (bsp->num_texinfo > 0) {
bsp->texinfo = (pxl8_bsp_texinfo*)SDL_calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); bsp->texinfo = SDL_calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo));
const u8* data = file_data + texinfo_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_texinfo; i++) { for (u32 i = 0; i < bsp->num_texinfo; i++) {
bsp->texinfo[i].u_axis.x = read_f32(data + i * 40); bsp->texinfo[i].u_axis = read_vec3(&stream);
bsp->texinfo[i].u_axis.y = read_f32(data + i * 40 + 4); bsp->texinfo[i].u_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].u_axis.z = read_f32(data + i * 40 + 8); bsp->texinfo[i].v_axis = read_vec3(&stream);
bsp->texinfo[i].u_offset = read_f32(data + i * 40 + 12); bsp->texinfo[i].v_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].v_axis.x = read_f32(data + i * 40 + 16); bsp->texinfo[i].miptex = pxl8_read_u32(&stream);
bsp->texinfo[i].v_axis.y = read_f32(data + i * 40 + 20);
bsp->texinfo[i].v_axis.z = read_f32(data + i * 40 + 24);
bsp->texinfo[i].v_offset = read_f32(data + i * 40 + 28);
bsp->texinfo[i].miptex = read_u32(data + i * 40 + 32);
} }
} }
pxl8_bsp_chunk* faces_chunk = &header.chunks[CHUNK_FACES]; chunk = &header.chunks[CHUNK_FACES];
bsp->num_faces = faces_chunk->size / 20; if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_faces = chunk->size / 20;
if (bsp->num_faces > 0) { if (bsp->num_faces > 0) {
bsp->faces = (pxl8_bsp_face*)SDL_calloc(bsp->num_faces, sizeof(pxl8_bsp_face)); bsp->faces = SDL_calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
const u8* data = file_data + faces_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_faces; i++) { for (u32 i = 0; i < bsp->num_faces; i++) {
bsp->faces[i].plane_id = read_u16(data + i * 20); bsp->faces[i].plane_id = pxl8_read_u16(&stream);
bsp->faces[i].side = read_u16(data + i * 20 + 2); bsp->faces[i].side = pxl8_read_u16(&stream);
bsp->faces[i].first_edge = read_u32(data + i * 20 + 4); bsp->faces[i].first_edge = pxl8_read_u32(&stream);
bsp->faces[i].num_edges = read_u16(data + i * 20 + 8); bsp->faces[i].num_edges = pxl8_read_u16(&stream);
bsp->faces[i].texinfo_id = read_u16(data + i * 20 + 10); bsp->faces[i].texinfo_id = pxl8_read_u16(&stream);
bsp->faces[i].styles[0] = data[i * 20 + 12]; bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
bsp->faces[i].styles[1] = data[i * 20 + 13]; bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
bsp->faces[i].styles[2] = data[i * 20 + 14]; bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
bsp->faces[i].styles[3] = data[i * 20 + 15]; bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
bsp->faces[i].lightmap_offset = read_u32(data + i * 20 + 16); bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
} }
} }
pxl8_bsp_chunk* nodes_chunk = &header.chunks[CHUNK_NODES]; chunk = &header.chunks[CHUNK_NODES];
bsp->num_nodes = nodes_chunk->size / 24; if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
bsp->num_nodes = chunk->size / 24;
if (bsp->num_nodes > 0) { if (bsp->num_nodes > 0) {
bsp->nodes = (pxl8_bsp_node*)SDL_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node)); bsp->nodes = SDL_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
const u8* data = file_data + nodes_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_nodes; i++) { for (u32 i = 0; i < bsp->num_nodes; i++) {
bsp->nodes[i].plane_id = read_u32(data + i * 24); bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
bsp->nodes[i].children[0] = read_i16(data + i * 24 + 4); bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
bsp->nodes[i].children[1] = read_i16(data + i * 24 + 6); bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
bsp->nodes[i].mins[0] = read_i16(data + i * 24 + 8); for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream);
bsp->nodes[i].mins[1] = read_i16(data + i * 24 + 10); for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream);
bsp->nodes[i].mins[2] = read_i16(data + i * 24 + 12); bsp->nodes[i].first_face = pxl8_read_u16(&stream);
bsp->nodes[i].maxs[0] = read_i16(data + i * 24 + 14); bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
bsp->nodes[i].maxs[1] = read_i16(data + i * 24 + 16);
bsp->nodes[i].maxs[2] = read_i16(data + i * 24 + 18);
bsp->nodes[i].first_face = read_u16(data + i * 24 + 20);
bsp->nodes[i].num_faces = read_u16(data + i * 24 + 22);
} }
} }
pxl8_bsp_chunk* leafs_chunk = &header.chunks[CHUNK_LEAFS]; chunk = &header.chunks[CHUNK_LEAFS];
bsp->num_leafs = leafs_chunk->size / 28; if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
bsp->num_leafs = chunk->size / 28;
if (bsp->num_leafs > 0) { if (bsp->num_leafs > 0) {
bsp->leafs = (pxl8_bsp_leaf*)SDL_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf)); bsp->leafs = SDL_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
const u8* data = file_data + leafs_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_leafs; i++) { for (u32 i = 0; i < bsp->num_leafs; i++) {
bsp->leafs[i].contents = read_i32(data + i * 28); bsp->leafs[i].contents = pxl8_read_i32(&stream);
bsp->leafs[i].visofs = read_i32(data + i * 28 + 4); bsp->leafs[i].visofs = pxl8_read_i32(&stream);
bsp->leafs[i].mins[0] = read_i16(data + i * 28 + 8); for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream);
bsp->leafs[i].mins[1] = read_i16(data + i * 28 + 10); for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream);
bsp->leafs[i].mins[2] = read_i16(data + i * 28 + 12); bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
bsp->leafs[i].maxs[0] = read_i16(data + i * 28 + 14); bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
bsp->leafs[i].maxs[1] = read_i16(data + i * 28 + 16); for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
bsp->leafs[i].maxs[2] = read_i16(data + i * 28 + 18);
bsp->leafs[i].first_marksurface = read_u16(data + i * 28 + 20);
bsp->leafs[i].num_marksurfaces = read_u16(data + i * 28 + 22);
bsp->leafs[i].ambient_level[0] = data[i * 28 + 24];
bsp->leafs[i].ambient_level[1] = data[i * 28 + 25];
bsp->leafs[i].ambient_level[2] = data[i * 28 + 26];
bsp->leafs[i].ambient_level[3] = data[i * 28 + 27];
} }
} }
pxl8_bsp_chunk* marksurfaces_chunk = &header.chunks[CHUNK_MARKSURFACES]; chunk = &header.chunks[CHUNK_MARKSURFACES];
bsp->num_marksurfaces = marksurfaces_chunk->size / 2; if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
bsp->num_marksurfaces = chunk->size / 2;
if (bsp->num_marksurfaces > 0) { if (bsp->num_marksurfaces > 0) {
bsp->marksurfaces = (u16*)SDL_calloc(bsp->num_marksurfaces, sizeof(u16)); bsp->marksurfaces = SDL_calloc(bsp->num_marksurfaces, sizeof(u16));
const u8* data = file_data + marksurfaces_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_marksurfaces; i++) { for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
bsp->marksurfaces[i] = read_u16(data + i * 2); bsp->marksurfaces[i] = pxl8_read_u16(&stream);
} }
} }
pxl8_bsp_chunk* models_chunk = &header.chunks[CHUNK_MODELS]; chunk = &header.chunks[CHUNK_MODELS];
bsp->num_models = models_chunk->size / 64; if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
bsp->num_models = chunk->size / 64;
if (bsp->num_models > 0) { if (bsp->num_models > 0) {
bsp->models = (pxl8_bsp_model*)SDL_calloc(bsp->num_models, sizeof(pxl8_bsp_model)); bsp->models = SDL_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
const u8* data = file_data + models_chunk->offset; pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) { for (u32 i = 0; i < bsp->num_models; i++) {
bsp->models[i].mins[0] = read_f32(data + i * 64); for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream);
bsp->models[i].mins[1] = read_f32(data + i * 64 + 4); for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream);
bsp->models[i].mins[2] = read_f32(data + i * 64 + 8); bsp->models[i].origin = read_vec3(&stream);
bsp->models[i].maxs[0] = read_f32(data + i * 64 + 12); for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
bsp->models[i].maxs[1] = read_f32(data + i * 64 + 16); bsp->models[i].visleafs = pxl8_read_i32(&stream);
bsp->models[i].maxs[2] = read_f32(data + i * 64 + 20); bsp->models[i].first_face = pxl8_read_i32(&stream);
bsp->models[i].origin.x = read_f32(data + i * 64 + 24); bsp->models[i].num_faces = pxl8_read_i32(&stream);
bsp->models[i].origin.y = read_f32(data + i * 64 + 28);
bsp->models[i].origin.z = read_f32(data + i * 64 + 32);
bsp->models[i].headnode[0] = read_i32(data + i * 64 + 36);
bsp->models[i].headnode[1] = read_i32(data + i * 64 + 40);
bsp->models[i].headnode[2] = read_i32(data + i * 64 + 44);
bsp->models[i].headnode[3] = read_i32(data + i * 64 + 48);
bsp->models[i].visleafs = read_i32(data + i * 64 + 52);
bsp->models[i].first_face = read_i32(data + i * 64 + 56);
bsp->models[i].num_faces = read_i32(data + i * 64 + 60);
} }
} }
pxl8_bsp_chunk* vis_chunk = &header.chunks[CHUNK_VISIBILITY]; chunk = &header.chunks[CHUNK_VISIBILITY];
bsp->visdata_size = vis_chunk->size; if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->visdata_size = chunk->size;
if (bsp->visdata_size > 0) { if (bsp->visdata_size > 0) {
bsp->visdata = (u8*)SDL_malloc(bsp->visdata_size); bsp->visdata = SDL_malloc(bsp->visdata_size);
memcpy(bsp->visdata, file_data + vis_chunk->offset, bsp->visdata_size); memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
} }
pxl8_bsp_chunk* light_chunk = &header.chunks[CHUNK_LIGHTING]; chunk = &header.chunks[CHUNK_LIGHTING];
bsp->lightdata_size = light_chunk->size; if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->lightdata_size = chunk->size;
if (bsp->lightdata_size > 0) { if (bsp->lightdata_size > 0) {
bsp->lightdata = (u8*)SDL_malloc(bsp->lightdata_size); bsp->lightdata = SDL_malloc(bsp->lightdata_size);
memcpy(bsp->lightdata, file_data + light_chunk->offset, bsp->lightdata_size); memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
} }
SDL_free(file_data); SDL_free(file_data);
@ -274,6 +255,12 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs); bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
return PXL8_OK; return PXL8_OK;
error_cleanup:
pxl8_error("BSP chunk validation failed: %s", path);
SDL_free(file_data);
pxl8_bsp_destroy(bsp);
return PXL8_ERROR_INVALID_FORMAT;
} }
void pxl8_bsp_destroy(pxl8_bsp* bsp) { void pxl8_bsp_destroy(pxl8_bsp* bsp) {

View file

@ -722,20 +722,20 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK; if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK;
pxl8_debug("Loading palette from: %s", path); pxl8_debug("Loading palette from: %s", path);
pxl8_ase_file ase_file; pxl8_ase_file ase_file;
pxl8_result result = pxl8_ase_load(path, &ase_file); pxl8_result result = pxl8_ase_load(path, &ase_file);
if (result != PXL8_OK) { if (result != PXL8_OK) {
pxl8_error("Failed to load ASE file for palette: %s", path); pxl8_error("Failed to load ASE file for palette: %s", path);
return result; return result;
} }
if (ase_file.palette.entry_count == 0) { if (ase_file.palette.entry_count == 0 || !ase_file.palette.colors) {
pxl8_error("No palette data in ASE file"); pxl8_error("No palette data in ASE file");
pxl8_ase_destroy(&ase_file); pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_INVALID_FORMAT; return PXL8_ERROR_INVALID_FORMAT;
} }
u32 copy_size = ase_file.palette.entry_count < gfx->palette_size u32 copy_size = ase_file.palette.entry_count < gfx->palette_size
? ase_file.palette.entry_count ? ase_file.palette.entry_count
: gfx->palette_size; : gfx->palette_size;

View file

@ -5,6 +5,80 @@
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct {
const u8* bytes;
u32 offset;
u32 size;
} pxl8_stream;
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
return (pxl8_stream){
.bytes = bytes,
.offset = 0,
.size = size
};
}
static inline bool pxl8_stream_can_read(const pxl8_stream* stream, u32 count) {
return stream->offset + count <= stream->size;
}
static inline void pxl8_stream_seek(pxl8_stream* stream, u32 offset) {
stream->offset = offset;
}
static inline u32 pxl8_stream_position(const pxl8_stream* stream) {
return stream->offset;
}
static inline u8 pxl8_read_u8(pxl8_stream* stream) {
return stream->bytes[stream->offset++];
}
static inline u16 pxl8_read_u16(pxl8_stream* stream) {
u16 val = (u16)stream->bytes[stream->offset] | ((u16)stream->bytes[stream->offset + 1] << 8);
stream->offset += 2;
return val;
}
static inline u32 pxl8_read_u32(pxl8_stream* stream) {
u32 val = (u32)stream->bytes[stream->offset] |
((u32)stream->bytes[stream->offset + 1] << 8) |
((u32)stream->bytes[stream->offset + 2] << 16) |
((u32)stream->bytes[stream->offset + 3] << 24);
stream->offset += 4;
return val;
}
static inline i16 pxl8_read_i16(pxl8_stream* stream) {
return (i16)pxl8_read_u16(stream);
}
static inline i32 pxl8_read_i32(pxl8_stream* stream) {
return (i32)pxl8_read_u32(stream);
}
static inline f32 pxl8_read_f32(pxl8_stream* stream) {
u32 val = pxl8_read_u32(stream);
return *(f32*)&val;
}
static inline void pxl8_read_bytes(pxl8_stream* stream, void* dest, u32 count) {
for (u32 i = 0; i < count; i++) {
((u8*)dest)[i] = stream->bytes[stream->offset++];
}
}
static inline void pxl8_skip_bytes(pxl8_stream* stream, u32 count) {
stream->offset += count;
}
static inline const u8* pxl8_read_ptr(pxl8_stream* stream, u32 count) {
const u8* ptr = &stream->bytes[stream->offset];
stream->offset += count;
return ptr;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif