#include #include #include #include #include #include #include #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; }