243 lines
7.5 KiB
Plaintext

// create_*...
Create_Package :: struct {
data: String_Builder;
entries: [..] Entry_Info;
pool: Pool; // Stores entry names and user data.
}
Entry_Info :: struct {
name: string;
data: [] u8;
offset_from_start_of_file: s64;
size_of_entry: s64; // Equal to data.count if we hae the data, but we might not have the data if loading Table_Of_Contents only!
}
deinit :: (using package: *Create_Package) {
free_buffers(*data);
array_reset(*entries);
release(*pool);
}
KNOWN_HEADER_SIZE :: 64;
#assert size_of(Package_Header) == KNOWN_HEADER_SIZE;
Package_Header :: struct {
magic: u32;
version: u32;
flags: u32;
reserved0: u32;
table_of_contents_offset: s64;
reserved: [5] u64;
}
Load_Package :: struct {
header: Package_Header;
data: [] u8;
table_of_contents: Table_Of_Contents;
entries: [] Entry_Info;
lookup: Table(string, *Entry_Info);
}
KNOWN_TABLE_OF_CONTENTS_SIZE :: 64;
#assert size_of(Table_Of_Contents) == KNOWN_TABLE_OF_CONTENTS_SIZE;
Table_Of_Contents :: struct {
table_of_contents_magic: u32;
reserved0: u32;
number_of_entries: s64;
reserved: [6] u64;
}
append_struct :: (builder: *String_Builder, to_append: *$T) {
append(builder, cast(*u8) to_append, size_of(T));
}
init_from_memory :: (package: *Load_Package, data: [] u8, name_for_debugging := "") -> bool {
#assert(TARGET_IS_LITTLE_ENDIAN); // We can endian-swap the header later.
if data.count < KNOWN_HEADER_SIZE {
log_error("Package '%' is too small to even contain a header! (It's % bytes).\n", name_for_debugging, data.count);
return false; // Too small!
}
_header := cast(*Package_Header) data.data;
package.header = << _header;
header_success := check_header(*package.header, name_for_debugging, data.count);
if !header_success return false;
offset := package.header.table_of_contents_offset;
toc_start := data;
toc_start.data += offset;
toc_start.count -= offset;
assert(toc_start.count >= 0);
toc_success := load_table_of_contents(package, toc_start, data.data, name_for_debugging, offset);
return toc_success;
}
load_table_of_contents :: (package: *Load_Package, data: [] u8, base_file_data_if_file_is_loaded: *u8, name_for_debugging: string, offset_for_debugging: s64) -> bool {
toc := cast(*Table_Of_Contents) data.data;
if toc.table_of_contents_magic != TABLE_OF_CONTENTS_MAGIC {
log_error("In package '%', we skipped to the supposed table_of_contents at offset %, but did not find the magic number there that we expected to find. (We wanted %, but got %).\n", name_for_debugging, offset_for_debugging, formatInt(TABLE_OF_CONTENTS_MAGIC, base=16), formatInt(toc.table_of_contents_magic, base=16));
return false;
}
entries: [..] Entry_Info;
array_resize(*entries, toc.number_of_entries);
s: string;
s.data = data.data + KNOWN_TABLE_OF_CONTENTS_SIZE;
s.count = data.count - KNOWN_TABLE_OF_CONTENTS_SIZE;
assert(s.count >= 0);
for 0..toc.number_of_entries-1 {
entry := *entries[it];
if s.count < 4 {
log_error("Table_Of_Contents Entry % is too short!\n", it+1);
return false;
}
name_length: u32;
get(*s, *name_length);
entry.name.data = s.data;
entry.name.count = name_length;
if s.count < name_length + 1 + 16 { // The 16 is for the chunk size and offset.
log_error("Table_Of_Contents Entry % is too short!\n", it+1);
return false;
}
advance(*s, name_length + 1);
chunk_size: s64;
chunk_offset: s64;
get(*s, *chunk_size);
get(*s, *chunk_offset);
if chunk_offset >= package.header.table_of_contents_offset {
log_error("The offset_from_start_of_file for Entry % is out of range; it must be before the table_of_contents, which starts at %, but it was %.\n", it+1, package.header.table_of_contents_offset, chunk_offset);
return false;
}
base := base_file_data_if_file_is_loaded;
if base {
entry.data.data = base + chunk_offset;
entry.data.count = chunk_size;
}
entry.size_of_entry = chunk_size;
entry.offset_from_start_of_file = chunk_offset;
table_add(*package.lookup, entry.name, entry);
}
package.entries = entries;
return true;
}
#scope_file
MAGIC :: #run << cast(*u32) "simp".data;
TABLE_OF_CONTENTS_MAGIC :: #run << cast(*u32) "toc!".data;
FILE_VERSION :: 1;
TARGET_IS_LITTLE_ENDIAN :: true; // @Endian: Need a way to change this based on target CPU!
// @Copypasta from the game!
put :: (builder: *String_Builder, x: $T)
#modify {
using Type_Info_Tag;
ti := cast(*Type_Info) T;
if ti.type == INTEGER return true; // Accept integers.
if ti.type == FLOAT return true; // Accept floats.
if ti.type == BOOL return true; // Accept bools.
if ti.type == ENUM return true; // Accept enums.
return false; // Reject anything else.
}
{
ensure_contiguous_space(builder, size_of(T));
#if TARGET_IS_LITTLE_ENDIAN {
// @Speed: Just write the target type!
simple_memcpy(builder, x);
} else {
// @Incomplete: Do a slow-path if we know we are not little-endian. This can be generated by #run_and_insert?
assert(false);
}
}
get :: (s: *string, x: *$T)
#modify { // @Cutnpaste from put
using Type_Info_Tag;
ti := cast(*Type_Info) T;
if ti.type == INTEGER return true; // Accept integers.
if ti.type == FLOAT return true; // Accept floats.
if ti.type == BOOL return true; // Accept bools.
if ti.type == ENUM return true; // Accept enums.
return false; // Reject anything else.
}
{
assert(s.count >= size_of(T));
if TARGET_IS_LITTLE_ENDIAN {
memcpy(x, s.data, size_of(T));
} else {
// @Incomplete: Do a slow-path if we know we are not little-endian. This can be generated by #run_and_insert?
assert(false);
}
s.data += size_of(T);
s.count -= size_of(T);
}
check_header :: (header: *Package_Header, name: string, file_size_in_bytes_or_minus_1: s64) -> bool {
using header;
if magic != MAGIC {
log_error("Package '%' has an incorrect magic number! (Wanted %, got %).\n", name, formatInt(MAGIC, base=16), formatInt(magic, base=16));
return false;
}
if version > FILE_VERSION { // 'version' is unsigned, so, don't check < 0.
log_error("Package '%' has an invalid version number, or a version created by code that is newer than we are, so we don't understand the format. (Its version is %; our highest known version is %).\n", name, version, FILE_VERSION);
return false;
}
if file_size_in_bytes_or_minus_1 >= 0 {
size := file_size_in_bytes_or_minus_1;
if table_of_contents_offset + KNOWN_TABLE_OF_CONTENTS_SIZE >= size {
log_error("Package '%' has an invalid table_of_contents_offset; it claims to be %, but the file is only % bytes long.\n", name, table_of_contents_offset, size);
return false;
}
}
return true;
}
#import "Basic";
#import "Hash_Table";
#import "Pool";