pxl8/src/pxl8_cart.c
2025-11-01 15:19:57 -05:00

343 lines
9.8 KiB
C

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <miniz.h>
#include "pxl8_cart.h"
#include "pxl8_macros.h"
struct pxl8_cart {
void* archive_data;
size_t archive_size;
char* base_path;
bool is_folder;
bool is_mounted;
char* name;
};
static pxl8_cart* __pxl8_current_cart = 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) {
struct stat st;
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
}
static bool is_pxc_file(const char* path) {
size_t len = strlen(path);
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
}
pxl8_cart* pxl8_cart_create(void) {
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
return cart;
}
pxl8_cart* pxl8_cart_current(void) {
return __pxl8_current_cart;
}
void pxl8_cart_destroy(pxl8_cart* cart) {
if (!cart) return;
pxl8_cart_unload(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) {
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
pxl8_cart_unload(cart);
cart->name = get_cart_name(path);
if (is_directory(path)) {
cart->base_path = realpath(path, NULL);
if (!cart->base_path) {
pxl8_error("Failed to resolve cart path: %s", path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
cart->is_folder = true;
char main_path[512];
snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path);
if (access(main_path, F_OK) != 0) {
snprintf(main_path, sizeof(main_path), "%s/main.lua", cart->base_path);
if (access(main_path, F_OK) != 0) {
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
}
pxl8_info("Loaded cart folder: %s", cart->name);
return PXL8_OK;
}
if (is_pxc_file(path)) {
FILE* file = fopen(path, "rb");
if (!file) {
pxl8_error("Failed to open cart archive: %s", path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
fseek(file, 0, SEEK_END);
cart->archive_size = ftell(file);
fseek(file, 0, SEEK_SET);
cart->archive_data = malloc(cart->archive_size);
if (!cart->archive_data) {
fclose(file);
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);
return PXL8_ERROR_SYSTEM_FAILURE;
}
fclose(file);
char temp_dir[256];
snprintf(temp_dir, sizeof(temp_dir), "/tmp/pxl8_%s_%d", cart->name, getpid());
if (mkdir(temp_dir, 0755) != 0) {
pxl8_error("Failed to create temp directory: %s", temp_dir);
return PXL8_ERROR_SYSTEM_FAILURE;
}
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);
return PXL8_ERROR_INVALID_FORMAT;
}
void pxl8_cart_unload(pxl8_cart* cart) {
if (!cart) return;
pxl8_cart_unmount(cart);
if (!cart->is_folder && cart->base_path) {
char cmd[512];
snprintf(cmd, sizeof(cmd), "rm -rf %s", cart->base_path);
system(cmd);
}
char* cart_name = cart->name ? strdup(cart->name) : NULL;
if (cart->base_path) {
free(cart->base_path);
cart->base_path = NULL;
}
if (cart->name) {
free(cart->name);
cart->name = NULL;
}
if (cart->archive_data) {
free(cart->archive_data);
cart->archive_data = NULL;
}
cart->archive_size = 0;
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) {
if (!cart || !cart->base_path) return PXL8_ERROR_NULL_POINTER;
if (cart->is_mounted) return PXL8_OK;
if (__pxl8_current_cart) {
pxl8_cart_unmount(__pxl8_current_cart);
}
__pxl8_original_cwd = getcwd(NULL, 0);
if (chdir(cart->base_path) != 0) {
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
free(__pxl8_original_cwd);
__pxl8_original_cwd = NULL;
return PXL8_ERROR_FILE_NOT_FOUND;
}
cart->is_mounted = true;
__pxl8_current_cart = cart;
pxl8_info("Mounted cart: %s", cart->name);
return PXL8_OK;
}
void pxl8_cart_unmount(pxl8_cart* cart) {
if (!cart || !cart->is_mounted) return;
if (__pxl8_original_cwd) {
chdir(__pxl8_original_cwd);
free(__pxl8_original_cwd);
__pxl8_original_cwd = NULL;
}
cart->is_mounted = false;
if (__pxl8_current_cart == cart) {
__pxl8_current_cart = NULL;
}
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) {
if (!cart || !cart->base_path || !relative_path || !out_path || out_size == 0) return false;
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
return written >= 0 && (size_t)written < out_size;
}
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
if (!cart || !cart->base_path || !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;
}
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
if (!is_directory(folder_path)) {
pxl8_error("Cart folder not found: %s", folder_path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
char main_path[512];
snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path);
if (access(main_path, F_OK) != 0) {
snprintf(main_path, sizeof(main_path), "%s/main.lua", folder_path);
if (access(main_path, F_OK) != 0) {
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
}
mz_zip_archive zip = {0};
if (!mz_zip_writer_init_file(&zip, output_path, 0)) {
pxl8_error("Failed to create archive: %s", output_path);
return PXL8_ERROR_SYSTEM_FAILURE;
}
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
pxl8_add_file_recursive(&zip, folder_path, "");
if (!mz_zip_writer_finalize_archive(&zip)) {
pxl8_error("Failed to finalize archive");
mz_zip_writer_end(&zip);
return PXL8_ERROR_SYSTEM_FAILURE;
}
mz_zip_writer_end(&zip);
pxl8_info("Cart packed successfully!");
return PXL8_OK;
}