2025-09-27 11:03:36 -05:00
|
|
|
#include <dirent.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <unistd.h>
|
2025-10-01 12:56:13 -05:00
|
|
|
|
|
|
|
|
#include <miniz.h>
|
|
|
|
|
|
|
|
|
|
#include "pxl8_cart.h"
|
|
|
|
|
#include "pxl8_macros.h"
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
static pxl8_cart* __pxl8_current_cart = NULL;
|
|
|
|
|
static char* __pxl8_original_cwd = NULL;
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
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;
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
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);
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
static bool is_directory(const char* path) {
|
|
|
|
|
struct stat st;
|
|
|
|
|
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
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_current(void) {
|
|
|
|
|
return __pxl8_current_cart;
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int num_files = mz_zip_reader_get_num_files(&zip);
|
|
|
|
|
for (int i = 0; i < num_files; i++) {
|
|
|
|
|
mz_zip_archive_file_stat file_stat;
|
|
|
|
|
if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
char extract_path[1024];
|
2025-09-27 11:03:36 -05:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
char* cart_name = cart->name ? strdup(cart->name) : NULL;
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
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;
|
2025-09-28 13:10:29 -05:00
|
|
|
|
|
|
|
|
if (cart_name) {
|
|
|
|
|
pxl8_info("Unloaded cart: %s", cart_name);
|
|
|
|
|
free(cart_name);
|
|
|
|
|
}
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
if (__pxl8_current_cart) {
|
|
|
|
|
pxl8_cart_unmount(__pxl8_current_cart);
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
__pxl8_original_cwd = getcwd(NULL, 0);
|
2025-09-27 11:03:36 -05:00
|
|
|
if (chdir(cart->base_path) != 0) {
|
|
|
|
|
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
2025-09-28 13:10:29 -05:00
|
|
|
free(__pxl8_original_cwd);
|
|
|
|
|
__pxl8_original_cwd = NULL;
|
2025-09-27 11:03:36 -05:00
|
|
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cart->is_mounted = true;
|
2025-09-28 13:10:29 -05:00
|
|
|
__pxl8_current_cart = cart;
|
2025-09-27 11:03:36 -05:00
|
|
|
|
|
|
|
|
pxl8_info("Mounted cart: %s", cart->name);
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_cart_unmount(pxl8_cart* cart) {
|
|
|
|
|
if (!cart || !cart->is_mounted) return;
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
if (__pxl8_original_cwd) {
|
|
|
|
|
chdir(__pxl8_original_cwd);
|
|
|
|
|
free(__pxl8_original_cwd);
|
|
|
|
|
__pxl8_original_cwd = NULL;
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cart->is_mounted = false;
|
2025-09-28 13:10:29 -05:00
|
|
|
if (__pxl8_current_cart == cart) {
|
|
|
|
|
__pxl8_current_cart = NULL;
|
2025-09-27 11:03:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Unmounted cart: %s", cart->name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char* pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path) {
|
|
|
|
|
if (!cart || !cart->base_path || !relative_path) return NULL;
|
|
|
|
|
|
|
|
|
|
char* full_path = malloc(512);
|
|
|
|
|
if (!full_path) return NULL;
|
|
|
|
|
|
|
|
|
|
snprintf(full_path, 512, "%s/%s", cart->base_path, relative_path);
|
|
|
|
|
return full_path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
|
|
|
|
if (!cart || !cart->base_path || !path) return false;
|
|
|
|
|
|
|
|
|
|
char* full_path = pxl8_cart_resolve_path(cart, path);
|
|
|
|
|
if (!full_path) return false;
|
|
|
|
|
|
|
|
|
|
bool exists = access(full_path, F_OK) == 0;
|
|
|
|
|
free(full_path);
|
|
|
|
|
return exists;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2025-09-28 13:10:29 -05:00
|
|
|
pxl8_add_file_recursive(&zip, folder_path, "");
|
2025-09-27 11:03:36 -05:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|