add networking, 3d improvements, reorganize src structure

This commit is contained in:
asrael 2026-01-17 22:52:36 -06:00
parent 39b604b333
commit 415d424057
122 changed files with 5358 additions and 721 deletions

272
src/asset/pxl8_save.c Normal file
View 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_log.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;
}