diff --git a/meson.build b/meson.build index 828391be..4d37c11a 100644 --- a/meson.build +++ b/meson.build @@ -372,16 +372,16 @@ cell_dep = declare_dependency( ) # Create core.zip from scripts folder -zip_target = custom_target('core.zip', - output: 'core.zip', - command: ['sh', '-c', 'cd ' + meson.project_source_root() / 'scripts' + ' && zip -r ' + meson.current_build_dir() / 'core.zip' + ' .'], +qop_target = custom_target('core.qop', + output: 'core.qop', + command: ['sh', '-c', 'qopconv -d ' + meson.project_source_root() / 'scripts . core.qop'], build_by_default: true, build_always_stale: true ) # Create final cell executable by appending core.zip to cell_bin cell = custom_target('cell', - input: [cell_bin, zip_target], + input: [cell_bin, qop_target], output: 'cell' + exe_ext, command: [ 'sh', '-c', diff --git a/qopconv.c b/qopconv.c new file mode 100644 index 00000000..3cedc9fa --- /dev/null +++ b/qopconv.c @@ -0,0 +1,491 @@ +/* + +Copyright (c) 2024, Dominic Szablewski - https://phoboslab.org +SPDX-License-Identifier: MIT + + +Command line tool to create and unpack qop archives + +*/ + +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include + +#define QOP_IMPLEMENTATION +#include "qop.h" + +#define MAX_PATH_LEN 1024 +#define BUFFER_SIZE 4096 + +#define UNUSED(x) (void)(x) +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define die(...) \ + printf("Abort at " TOSTRING(__FILE__) " line " TOSTRING(__LINE__) ": " __VA_ARGS__); \ + printf("\n"); \ + exit(1) + +#define error_if(TEST, ...) \ + if (TEST) { \ + die(__VA_ARGS__); \ + } + + +// ----------------------------------------------------------------------------- +// Platform specific file/dir handling + +typedef struct { + char *name; + unsigned char is_dir; + unsigned char is_file; +} pi_dirent; + +#if defined(_WIN32) + #include + + typedef struct { + WIN32_FIND_DATA data; + pi_dirent current; + HANDLE dir; + unsigned char is_first; + } pi_dir; + + pi_dir *pi_dir_open(const char *path) { + char find_str[MAX_PATH_LEN]; + snprintf(find_str, MAX_PATH_LEN, "%s/*", path); + + pi_dir *d = malloc(sizeof(pi_dir)); + d->is_first = 1; + d->dir = FindFirstFile(find_str, &d->data); + if (d->dir == INVALID_HANDLE_VALUE) { + free(d); + return NULL; + } + return d; + } + + pi_dirent *pi_dir_next(pi_dir *d) { + if (!d->is_first) { + if (FindNextFile(d->dir, &d->data) == 0) { + return NULL; + } + } + d->is_first = 0; + d->current.name = d->data.cFileName; + d->current.is_dir = d->data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; + d->current.is_file = !d->current.is_dir; + return &d->current; + } + + void pi_dir_close(pi_dir *d) { + FindClose(d->dir); + free(d); + } + + int pi_mkdir(char *path, int mode) { + UNUSED(mode); + return CreateDirectory(path, NULL) ? 0 : -1; + } +#else + #include + + typedef struct { + DIR *dir; + struct dirent *data; + pi_dirent current; + } pi_dir; + + pi_dir *pi_dir_open(const char *path) { + DIR *dir = opendir(path); + if (!dir) { + return NULL; + } + pi_dir *d = malloc(sizeof(pi_dir)); + d->dir = dir; + return d; + } + + pi_dirent *pi_dir_next(pi_dir *d) { + d->data = readdir(d->dir); + if (!d->data) { + return NULL; + } + d->current.name = d->data->d_name; + d->current.is_dir = d->data->d_type & DT_DIR; + d->current.is_file = d->data->d_type == DT_REG; + return &d->current; + } + + void pi_dir_close(pi_dir *d) { + closedir(d->dir); + free(d); + } + + int pi_mkdir(char *path, int mode) { + return mkdir(path, mode); + } +#endif + + +// ----------------------------------------------------------------------------- +// Unpack + +int create_path(const char *path, const mode_t mode) { + char tmp[MAX_PATH_LEN]; + char *p = NULL; + struct stat sb; + size_t len; + + // copy path + len = strnlen(path, MAX_PATH_LEN); + if (len == 0 || len == MAX_PATH_LEN) { + return -1; + } + memcpy(tmp, path, len); + tmp[len] = '\0'; + + // remove file part + char *last_slash = strrchr(tmp, '/'); + if (last_slash == NULL) { + return 0; + } + *last_slash = '\0'; + + // check if path exists and is a directory + if (stat(tmp, &sb) == 0) { + if (S_ISDIR(sb.st_mode)) { + return 0; + } + } + + // recursive mkdir + for (p = tmp + 1; *p; p++) { + if (*p == '/') { + *p = 0; + if (stat(tmp, &sb) != 0) { + if (pi_mkdir(tmp, mode) < 0) { + return -1; + } + } + else if (!S_ISDIR(sb.st_mode)) { + return -1; + } + *p = '/'; + } + } + if (stat(tmp, &sb) != 0) { + if (pi_mkdir(tmp, mode) < 0) { + return -1; + } + } + else if (!S_ISDIR(sb.st_mode)) { + return -1; + } + return 0; +} + +unsigned int copy_out(FILE *src, unsigned int offset, unsigned int size, const char *dest_path) { + FILE *dest = fopen(dest_path, "wb"); + error_if(!dest, "Could not open file %s for writing", dest_path); + + char buffer[BUFFER_SIZE]; + size_t bytes_read, bytes_written; + unsigned int bytes_total = 0; + unsigned int read_size = size < BUFFER_SIZE ? size : BUFFER_SIZE; + + fseek(src, offset, SEEK_SET); + while (read_size > 0 && (bytes_read = fread(buffer, 1, read_size, src)) > 0) { + bytes_written = fwrite(buffer, 1, bytes_read, dest); + error_if(bytes_written != bytes_read, "Write error"); + bytes_total += bytes_written; + if (bytes_total >= size) { + break; + } + if (size - bytes_total < read_size) { + read_size = size - bytes_total; + } + } + + error_if(ferror(src), "read error for file %s", dest_path); + fclose(dest); + return bytes_total; +} + +void unpack(const char *archive_path, int list_only) { + qop_desc qop; + int archive_size = qop_open(archive_path, &qop); + error_if(archive_size == 0, "Could not open archive %s", archive_path); + + // Read the archive index + int index_len = qop_read_index(&qop, malloc(qop.hashmap_size)); + error_if(index_len == 0, "Could not read index from archive %s", archive_path); + + // Extract all files + for (unsigned int i = 0; i < qop.hashmap_len; i++) { + qop_file *file = &qop.hashmap[i]; + if (file->size == 0) { + continue; + } + error_if(file->path_len >= MAX_PATH_LEN, "Path for file %016llx exceeds %d", file->hash, MAX_PATH_LEN); + char path[MAX_PATH_LEN]; + qop_read_path(&qop, file, path); + + // Integrity check + // error_if(!qop_find(&qop, path), "could not find %s", path); + + printf("%6d %016llx %10d %s\n", i, file->hash, file->size, path); + + if (!list_only) { + error_if(create_path(path, 0755) != 0, "Could not create path %s", path); + copy_out(qop.fh, qop.files_offset + file->offset + file->path_len, file->size, path); + } + } + + free(qop.hashmap); + qop_close(&qop); +} + + +// ----------------------------------------------------------------------------- +// Pack + +typedef struct { + qop_file *files; + int len; + int capacity; + int size; + char dest_path[MAX_PATH_LEN]; +} pack_state; + +int canonicalize_path(const char *path, char *buffer, size_t buffer_len) { +#if defined(_WIN32) + return _fullpath(buffer, path, buffer_len) != NULL; +#else + UNUSED(buffer_len); + return realpath(path, buffer) != NULL; +#endif +} + +void write_16(unsigned int v, FILE *fh) { + unsigned char b[sizeof(unsigned short)]; + b[0] = 0xff & (v ); + b[1] = 0xff & (v >> 8); + int written = fwrite(b, sizeof(unsigned short), 1, fh); + error_if(!written, "Write error"); +} + +void write_32(unsigned int v, FILE *fh) { + unsigned char b[sizeof(unsigned int)]; + b[0] = 0xff & (v ); + b[1] = 0xff & (v >> 8); + b[2] = 0xff & (v >> 16); + b[3] = 0xff & (v >> 24); + int written = fwrite(b, sizeof(unsigned int), 1, fh); + error_if(!written, "Write error"); +} + +void write_64(qop_uint64_t v, FILE *fh) { + unsigned char b[sizeof(qop_uint64_t)]; + b[0] = 0xff & (v ); + b[1] = 0xff & (v >> 8); + b[2] = 0xff & (v >> 16); + b[3] = 0xff & (v >> 24); + b[4] = 0xff & (v >> 32); + b[5] = 0xff & (v >> 40); + b[6] = 0xff & (v >> 48); + b[7] = 0xff & (v >> 56); + int written = fwrite(b, sizeof(qop_uint64_t), 1, fh); + error_if(!written, "Write error"); +} + +unsigned int copy_into(const char *src_path, FILE *dest) { + FILE *src = fopen(src_path, "rb"); + error_if(!src, "Could not open file %s for reading", src_path); + + char buffer[BUFFER_SIZE]; + size_t bytes_read, bytes_written; + unsigned int bytes_total = 0; + + while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src)) > 0) { + bytes_written = fwrite(buffer, 1, bytes_read, dest); + error_if(bytes_written != bytes_read, "Write error"); + bytes_total += bytes_written; + } + + error_if(ferror(src), "read error for file %s", src_path); + fclose(src); + return bytes_total; +} + +void add_file(const char *path, FILE *dest, pack_state *state) { + if (state->dest_path[0]) { + char absolute[MAX_PATH_LEN]; + if (canonicalize_path(path, absolute, MAX_PATH_LEN) && strcmp(absolute, state->dest_path) == 0) { + return; + } + } + + if (state->len >= state->capacity) { + state->capacity *= 2; + state->files = realloc(state->files, state->capacity * sizeof(qop_file)); + } + + // Strip leading "./" from path for archive + const char *archive_path = path; + if (path[0] == '.' && path[1] == '/') { + archive_path = path + 2; + } + + qop_uint64_t hash = qop_hash(archive_path); + + + // Write the path into the archive + int path_len = strlen(archive_path) + 1; + int path_written = fwrite(archive_path, sizeof(char), path_len, dest); + error_if(path_written != path_len, "Write error"); + + // Copy the file into the archive + unsigned int size = copy_into(path, dest); + + printf("%6d %016llx %10d %s\n", state->len, hash, size, archive_path); + + // Collect file info for the index + state->files[state->len] = (qop_file){ + .hash = hash, + .offset = state->size, + .size = size, + .path_len = path_len, + .flags = QOP_FLAG_NONE + }; + state->size += size + path_len; + state->len++; +} + +void add_dir(const char *path, FILE *dest, pack_state *state) { + pi_dir *dir = pi_dir_open(path); + error_if(!dir, "Could not open directory %s for reading", path); + + pi_dirent *entry; + while ((entry = pi_dir_next(dir))) { + if ( + entry->is_dir && + strcmp(entry->name, ".") != 0 && + strcmp(entry->name, "..") != 0 + ) { + char subpath[MAX_PATH_LEN]; + snprintf(subpath, MAX_PATH_LEN, "%s/%s", path, entry->name); + add_dir(subpath, dest, state); + } + else if (entry->is_file) { + char subpath[MAX_PATH_LEN]; + snprintf(subpath, MAX_PATH_LEN, "%s/%s", path, entry->name); + add_file(subpath, dest, state); + } + } + pi_dir_close(dir); +} + +void pack(const char *read_dir, char **sources, int sources_len, const char *archive_path) { + FILE *dest = fopen(archive_path, "wb"); + error_if(!dest, "Could not open file %s for writing", archive_path); + + pack_state state; + state.files = malloc(sizeof(qop_file) * 1024); + state.len = 0; + state.capacity = 1024; + state.size = 0; + state.dest_path[0] = '\0'; + if (!canonicalize_path(archive_path, state.dest_path, MAX_PATH_LEN)) { + state.dest_path[0] = '\0'; + } + + if (read_dir) { + error_if(chdir(read_dir) != 0, "Could not change to directory %s", read_dir); + } + + // Add files/directories + for (int i = 0; i < sources_len; i++) { + struct stat s; + error_if(stat(sources[i], &s) != 0, "Could not stat file %s", sources[i]); + if (S_ISDIR(s.st_mode)) { + add_dir(sources[i], dest, &state); + } + else if (S_ISREG(s.st_mode)) { + add_file(sources[i], dest, &state); + } + else { + die("Path %s is neither a directory nor a regular file", sources[i]); + } + } + + // Write index and header + unsigned int total_size = state.size + QOP_HEADER_SIZE; + for (int i = 0; i < state.len; i++) { + write_64(state.files[i].hash, dest); + write_32(state.files[i].offset, dest); + write_32(state.files[i].size, dest); + write_16(state.files[i].path_len, dest); + write_16(state.files[i].flags, dest); + total_size += 20; + } + + write_32(state.len, dest); + write_32(total_size, dest); + write_32(QOP_MAGIC, dest); + + free(state.files); + fclose(dest); + + printf("files: %d, size: %d bytes\n", state.len, total_size); +} + +void exit_usage(void) { + puts( + "Usage: qopconv [OPTION...] FILE...\n" + "\n" + "Examples:\n" + " qopconv dir1 archive.qop # Create archive.qop from dir1/\n" + " qopconv foo bar archive.qop # Create archive.qop from files foo and bar\n" + " qoponvv -u archive.qop # Unpack archive.qop in current directory\n" + " qopconv -l archive.qop # List files in archive.qop\n" + " qopconv -d dir1 dir2 archive.qop # Use dir1 prefix for reading, create\n" + " archive.qop from files in dir1/dir2/\n" + "\n" + "Options (mutually exclusive):\n" + " -u ... unpack archive\n" + " -l ... list contents of archive\n" + " -d ....... change read dir when creating archives\n" + ); + exit(1); +} + +int main(int argc, char **argv) { + if (argc < 3) { + exit_usage(); + } + + // Unpack + if (strcmp(argv[1], "-u") == 0) { + unpack(argv[2], 0); + } + else if (strcmp(argv[1], "-l") == 0) { + unpack(argv[2], 1); + } + else { + int files_start = 1; + char *read_dir = NULL; + if (strcmp(argv[1], "-d") == 0) { + read_dir = argv[2]; + files_start = 3; + } + if (argc < 2 + files_start) { + exit_usage(); + } + pack(read_dir, argv + files_start, argc - 1 - files_start, argv[argc-1]); + } + return 0; +} \ No newline at end of file diff --git a/scripts/engine.cm b/scripts/engine.cm index b4829f9c..4e3bc19f 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -71,6 +71,7 @@ var use_embed = hidden.use_embed var use_dyn = hidden.use_dyn var enet = hidden.enet var nota = hidden.nota +var fd = use_embed('fd') // Wota decode timing tracking var wota_decode_times = [] @@ -109,16 +110,33 @@ actor_mod.on_exception(disrupt) var js = use_embed('js') var io = use_embed('io') -if (!io.exists('.cell')) { - console_mod.print("No cell directory found. Make one.\n"); +//log.console(json.encode(fd)) +//log.console(fd.fstat) +//log.console(json.encode(fd.fstat('.cell'))) +//log.console(fd.fstat('.cell').isDirectory) + +if (!fd.stat('.cell').isDirectory) { + log.console("No cell directory found. Make one.\n"); os.exit(1); } +function slurp(path) { + var st = fd.stat(path) + if (!st.isFile) return null + + var fd_handle = fd.open(path, 'r') + var content_blob = fd.read(fd_handle, st.size) + fd.close(fd_handle) + + return text(content_blob) +} + var module_alias = {} var use_cache = {} var BASEPATH = 'base' + MOD_EXT -var script = io.slurp(BASEPATH) +var script = slurp(BASEPATH) +log.console(script) var fnname = "base" script = `(function ${fnname}() { ${script}; })` js.eval(BASEPATH, script)() @@ -268,7 +286,9 @@ config.system.__proto__ = default_config ENETSERVICE = config.system.net_service REPLYTIMEOUT = config.system.reply_timeout - + +log.console(`config loaded in ${time.number()-st_now} seconds`) + globalThis.text = use('text') // Load actor-specific configuration @@ -312,6 +332,8 @@ function deepFreeze(object) { return Object.freeze(object); } +log.console(`stone initialized in ${time.number()-st_now} seconds`) + globalThis.stone = deepFreeze stone.p = function(object) { @@ -681,6 +703,8 @@ load_actor_config(cell.args.program) actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer) +log.console(`actor registered in ${time.number()-st_now} seconds`) + if (config.system.actor_memory) js.mem_limit(config.system.actor_memory) @@ -829,6 +853,9 @@ var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var ar // queue up its first turn instead of run immediately var startfn = js.eval(cell.args.program, prog_script); + +log.console(`program compiled in ${time.number()-st_now} seconds`) + $_.clock(_ => { var val = startfn($_, cell.args.arg); @@ -836,6 +863,6 @@ $_.clock(_ => { throw new Error('Program must not return anything'); }) -log.console(`startup took ${time.number()-st_now}`) +log.console(`program executed in ${time.number()-st_now} seconds`) })() \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index f588acba..4390487b 100644 --- a/source/cell.c +++ b/source/cell.c @@ -52,11 +52,15 @@ #elif defined(__linux__) || defined(__GLIBC__) #define _GNU_SOURCE #include +#include #define MALLOC_OVERHEAD 8 #else #define MALLOC_OVERHEAD 0 #endif +#define QOP_IMPLEMENTATION +#include "qop.h" + #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) @@ -76,6 +80,8 @@ static SDL_Mutex *actors_mutex = NULL; static struct { char *key; cell_rt *value; } *actors = NULL; static unsigned char *zip_buffer_global = NULL; static char *prosperon = NULL; +static qop_desc qop_core; +static qop_file *qop_hashmap = NULL; cell_rt *root_cell = NULL; static SDL_AtomicInt shutting_down; @@ -110,6 +116,13 @@ static void exit_handler(void) if (actors_mutex) SDL_DestroyMutex(actors_mutex); + /* Clean up QOP resources */ + if (qop_hashmap) { + free(qop_hashmap); + qop_hashmap = NULL; + } + qop_close(&qop_core); + SDL_Quit(); exit(0); } @@ -348,72 +361,59 @@ static void free_zip(void) zip_buffer_global = NULL; } +int get_executable_path(char *buffer, unsigned int buffer_size) { +#if defined(__linux__) + ssize_t len = readlink("/proc/self/exe", buffer, buffer_size - 1); + if (len == -1) { + return 0; + } + buffer[len] = '\0'; + return len; +#elif defined(__APPLE__) + if (_NSGetExecutablePath(buffer, &buffer_size) == 0) { + return buffer_size; + } +#elif defined(_WIN32) + return GetModuleFileName(NULL, buffer, buffer_size); +#endif + + return 0; +} + int prosperon_mount_core(void) { - size_t size; char exe_path[PATH_MAX]; - - // Get the full path of the executable - const char *base_dir = PHYSFS_getBaseDir(); - if (base_dir) { - snprintf(exe_path, sizeof(exe_path), "%s%s", base_dir, PHYSFS_getDirSeparator()); - - // Extract just the executable name from argv[0] - const char *exe_name = strrchr(prosperon, '/'); - if (!exe_name) exe_name = strrchr(prosperon, '\\'); - if (exe_name) exe_name++; else exe_name = prosperon; - - strncat(exe_path, exe_name, sizeof(exe_path) - strlen(exe_path) - 1); - } else { - strncpy(exe_path, prosperon, sizeof(exe_path) - 1); - exe_path[sizeof(exe_path) - 1] = '\0'; - } - - FILE *f = fopen(exe_path, "rb"); - if (!f) return perror("fopen"), 0; - if (fseek(f, 0, SEEK_END) != 0) return perror("fseek"), fclose(f), 0; - size = ftell(f); - if (size < 0) return perror("ftell"), fclose(f), 0; - zip_buffer_global = malloc(size); - if (!zip_buffer_global) return perror("malloc"), fclose(f), 0; - rewind(f); - if (fread(zip_buffer_global, 1, size, f) != size) { - perror("fread"); - free(zip_buffer_global); - fclose(f); - return 0; - } - fclose(f); - - long max_comment_len = 0xFFFF; - long eocd_search_start = (size > max_comment_len + 22) ? size - (max_comment_len + 22) : 0; - long eocd_pos = -1; - for (long i = size - 22; i >= eocd_search_start; i--) - if (zip_buffer_global[i] == 'P' && zip_buffer_global[i + 1] == 'K' && - zip_buffer_global[i + 2] == 0x05 && zip_buffer_global[i + 3] == 0x06) { - eocd_pos = i; - break; - } - if (eocd_pos < 0) { - free(zip_buffer_global); + int exe_path_len = get_executable_path(exe_path, sizeof(exe_path)); + if (exe_path_len == 0) { + printf("ERROR: Could not get executable path\n"); return 0; } - uint16_t comment_length = zip_buffer_global[eocd_pos + 20] | (zip_buffer_global[eocd_pos + 21] << 8); - int eocd_size = 22 + comment_length; - uint32_t cd_size = zip_buffer_global[eocd_pos + 12] | (zip_buffer_global[eocd_pos + 13] << 8) | - (zip_buffer_global[eocd_pos + 14] << 16) | (zip_buffer_global[eocd_pos + 15] << 24); - uint32_t cd_offset_rel = zip_buffer_global[eocd_pos + 16] | (zip_buffer_global[eocd_pos + 17] << 8) | - (zip_buffer_global[eocd_pos + 18] << 16) | (zip_buffer_global[eocd_pos + 19] << 24); - uint32_t appended_zip_size = cd_offset_rel + cd_size + eocd_size; - long zip_offset = size - appended_zip_size; - if (zip_offset < 0 || zip_offset >= size) return fprintf(stderr, "Invalid zip offset: %ld\n", zip_offset), free(zip_buffer_global), 0; - - int ret = PHYSFS_mountMemory(zip_buffer_global + zip_offset, appended_zip_size, free_zip, "core.zip", NULL, 0); - if (!ret) { - printf("COULD NOT MOUNT! Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + // Open the QOP archive appended to this executable + int archive_size = qop_open(exe_path, &qop_core); + if (archive_size == 0) { + printf("ERROR: Could not open QOP archive\n"); + return 0; } - return ret; + + // Read the archive index + qop_hashmap = malloc(qop_core.hashmap_size); + if (!qop_hashmap) { + printf("ERROR: Could not allocate memory for QOP hashmap\n"); + qop_close(&qop_core); + return 0; + } + + int index_len = qop_read_index(&qop_core, qop_hashmap); + if (index_len == 0) { + printf("ERROR: Could not read QOP index\n"); + free(qop_hashmap); + qop_hashmap = NULL; + qop_close(&qop_core); + return 0; + } + + return 1; } void actor_unneeded(cell_rt *actor, JSValue fn, double seconds) @@ -814,20 +814,29 @@ void script_startup(cell_rt *prt) prt->context = js; ffi_load(js); - PHYSFS_File *eng = PHYSFS_openRead(ENGINE); - if (!eng) { - printf("ERROR: Could not open file %s! %s\n", ENGINE, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + // Find and load engine.cm from QOP archive + qop_file *engine_file = qop_find(&qop_core, ENGINE); + if (!engine_file) { + printf("ERROR: Could not find file %s in QOP archive!\n", ENGINE); return; } - PHYSFS_Stat stat; - PHYSFS_stat(ENGINE, &stat); - char *data = malloc(stat.filesize+1); - PHYSFS_readBytes(eng, data, stat.filesize); - PHYSFS_close(eng); - data[stat.filesize] = 0; + + char *data = malloc(engine_file->size + 1); + if (!data) { + printf("ERROR: Could not allocate memory for %s!\n", ENGINE); + return; + } + + int bytes_read = qop_read(&qop_core, engine_file, (unsigned char *)data); + if (bytes_read != (int)engine_file->size) { + printf("ERROR: Could not read file %s from QOP archive!\n", ENGINE); + free(data); + return; + } + data[engine_file->size] = 0; prt->state = ACTOR_RUNNING; - JSValue v = JS_Eval(js, data, (size_t)stat.filesize, ENGINE, 0); + JSValue v = JS_Eval(js, data, (size_t)engine_file->size, ENGINE, 0); uncaught_exception(js, v); prt->state = ACTOR_IDLE; set_actor_state(prt); @@ -947,43 +956,13 @@ int main(int argc, char **argv) prosperon = argv[0]; PHYSFS_init(argv[0]); - /* Mount core.zip attached to executable - this is now mandatory! */ + /* Load QOP package attached to executable - this is now mandatory! */ int mounted = prosperon_mount_core(); if (!mounted) { - printf("ERROR: Could not mount core. Reason: %s\n", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); + printf("ERROR: Could not load core QOP package.\n"); return 1; } - /* Check for .cell directory in the current directory */ - char *cell_parent_dir = NULL; - struct stat st; - char *current_dir = SDL_GetCurrentDirectory(); - char test_path[PATH_MAX]; - snprintf(test_path, sizeof(test_path), "%s/.cell", current_dir); - if (stat(test_path, &st) == 0 && S_ISDIR(st.st_mode)) { - cell_parent_dir = strdup(current_dir); - } - SDL_free(current_dir); - - if (cell_parent_dir) { - /* 1) Strip any trailing slash from cell_parent_dir (except if it's just "/") */ - size_t proj_len = strlen(cell_parent_dir); - if (proj_len > 1 && cell_parent_dir[proj_len - 1] == '/') - cell_parent_dir[proj_len - 1] = '\0'; - - char cellpath[PATH_MAX]; - snprintf(cellpath, sizeof(cellpath), "%s/.cell/modules", cell_parent_dir); - - PHYSFS_mount(cellpath, NULL, 0); - PHYSFS_mount(cell_parent_dir, NULL, 0); - PHYSFS_setWriteDir(cell_parent_dir); - - free(cell_parent_dir); - } else { - printf("Cell requires a .cell folder to run.\n"); - exit(1); - } - /* Create the initial actor from the command line */ int actor_argc = argc - script_start; char **actor_argv = argv + script_start; diff --git a/source/qjs_fd.c b/source/qjs_fd.c index 0610255e..5cb6ed54 100644 --- a/source/qjs_fd.c +++ b/source/qjs_fd.c @@ -180,7 +180,8 @@ JSC_CCALL(fd_fstat, struct stat st; if (fstat(fd, &st) != 0) return JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno)); - + + printf("fstat on %s\n", argv[0]); JSValue obj = JS_NewObject(js); JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); @@ -213,6 +214,42 @@ JSC_CCALL(fd_fstat, return obj; ) +JSC_SCALL(fd_stat, + struct stat st; + if (stat(str, &st) != 0) + ret = JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno)); + + JSValue obj = JS_NewObject(js); + JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size)); + JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode)); + JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid)); + JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid)); + JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime)); + JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime)); + JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime)); + JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink)); + JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino)); + JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev)); + JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev)); +#ifndef _WIN32 + JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize)); + JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks)); +#else + JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, 4096)); + JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_size / 512)); +#endif + + // Add boolean properties for file type + JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode))); + JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode))); + JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode))); + JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode))); + JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode))); + JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode))); + JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode))); + + return obj; +) static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, open, 2), @@ -224,6 +261,7 @@ static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, mkdir, 1), MIST_FUNC_DEF(fd, fsync, 1), MIST_FUNC_DEF(fd, close, 1), + MIST_FUNC_DEF(fd, stat, 1), MIST_FUNC_DEF(fd, fstat, 1), }; diff --git a/source/qjs_qop.c b/source/qjs_qop.c index 2d3f03b1..37ba32c0 100644 --- a/source/qjs_qop.c +++ b/source/qjs_qop.c @@ -1,5 +1,4 @@ #include "qjs_qop.h" -#define QOP_IMPLEMENTATION #include "qop.h" #include "qjs_blob.h" #include "jsffi.h"