qop
This commit is contained in:
@@ -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',
|
||||
|
||||
491
qopconv.c
Normal file
491
qopconv.c
Normal file
@@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#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 <windows.h>
|
||||
|
||||
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 <dirent.h>
|
||||
|
||||
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 <archive> ... unpack archive\n"
|
||||
" -l <archive> ... list contents of archive\n"
|
||||
" -d <dir> ....... 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;
|
||||
}
|
||||
@@ -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`)
|
||||
|
||||
})()
|
||||
181
source/cell.c
181
source/cell.c
@@ -52,11 +52,15 @@
|
||||
#elif defined(__linux__) || defined(__GLIBC__)
|
||||
#define _GNU_SOURCE
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
#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;
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "qjs_qop.h"
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
#include "qjs_blob.h"
|
||||
#include "jsffi.h"
|
||||
|
||||
Reference in New Issue
Block a user