This commit is contained in:
2025-11-24 23:08:40 -06:00
parent b613c7b6fa
commit 8bc31e3ac6
25 changed files with 1358 additions and 879 deletions

View File

@@ -61,6 +61,9 @@
#define QOP_IMPLEMENTATION
#include "qop.h"
#define JS_BLOB_IMPLEMENTATION
#include "qjs_blob.h"
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
@@ -79,7 +82,6 @@ static SDL_SpinLock main_queue_lock = 0;
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;
@@ -121,6 +123,10 @@ static void exit_handler(void)
free(qop_hashmap);
qop_hashmap = NULL;
}
if (qop_core.data) {
free(qop_core.data);
qop_core.data = NULL;
}
qop_close(&qop_core);
SDL_Quit();
@@ -355,12 +361,6 @@ static const JSMallocFunctions mimalloc_funcs = {
};
#endif
static void free_zip(void)
{
free(zip_buffer_global);
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);
@@ -389,10 +389,38 @@ int prosperon_mount_core(void)
return 0;
}
// Open the QOP archive appended to this executable
int archive_size = qop_open(exe_path, &qop_core);
// Load the entire executable into memory
FILE *fh = fopen(exe_path, "rb");
if (!fh) {
printf("ERROR: Could not open executable\n");
return 0;
}
fseek(fh, 0, SEEK_END);
long file_size = ftell(fh);
fseek(fh, 0, SEEK_SET);
unsigned char *buf = malloc(file_size);
if (!buf) {
printf("ERROR: Could not allocate memory for executable\n");
fclose(fh);
return 0;
}
if (fread(buf, 1, file_size, fh) != (size_t)file_size) {
printf("ERROR: Could not read executable\n");
free(buf);
fclose(fh);
return 0;
}
fclose(fh);
// Open the QOP archive from the in-memory data
int archive_size = qop_open_data(buf, file_size, &qop_core);
if (archive_size == 0) {
printf("ERROR: Could not open QOP archive\n");
free(buf);
return 0;
}
@@ -401,6 +429,7 @@ int prosperon_mount_core(void)
if (!qop_hashmap) {
printf("ERROR: Could not allocate memory for QOP hashmap\n");
qop_close(&qop_core);
free(buf);
return 0;
}
@@ -410,6 +439,7 @@ int prosperon_mount_core(void)
free(qop_hashmap);
qop_hashmap = NULL;
qop_close(&qop_core);
free(buf);
return 0;
}
@@ -813,7 +843,18 @@ void script_startup(cell_rt *prt)
JS_SetContextOpaque(js, prt);
prt->context = js;
ffi_load(js);
// Add core QOP blob to hidden
JSValue globalThis = JS_GetGlobalObject(js);
JSValue prosp = JS_GetPropertyStr(js, globalThis, "prosperon");
JSValue hidden = JS_GetPropertyStr(js, prosp, "hidden");
size_t archive_size = qop_core.data_size - qop_core.files_offset;
JSValue blob = js_new_blob_stoned_copy(js, qop_core.data + qop_core.files_offset, archive_size);
JS_SetPropertyStr(js, hidden, "core_qop_blob", blob);
JS_FreeValue(js, hidden);
JS_FreeValue(js, prosp);
JS_FreeValue(js, globalThis);
// Find and load engine.cm from QOP archive
qop_file *engine_file = qop_find(&qop_core, ENGINE);
if (!engine_file) {
@@ -852,7 +893,7 @@ int uncaught_exception(JSContext *js, JSValue v)
SDL_UnlockMutex(rt->mutex);
return 1;
}
JSValue exp = JS_GetException(js);
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
JS_FreeValue(js,ret);
@@ -915,7 +956,6 @@ static void add_runners(int n)
static void loop()
{
int msgs = 0;
while (!SDL_GetAtomicInt(&shutting_down)) {
SDL_WaitSemaphore(main_sem);
SDL_LockSpinlock(&main_queue_lock);
@@ -925,9 +965,7 @@ static void loop()
arrdel(main_queue, 0);
}
SDL_UnlockSpinlock(&main_queue_lock);
msgs++;
actor_turn(actor);
continue;
}
}
@@ -953,7 +991,6 @@ int main(int argc, char **argv)
tracy_profiling_enabled = profile_enabled;
#endif
prosperon = argv[0];
PHYSFS_init(argv[0]);
/* Load QOP package attached to executable - this is now mandatory! */

View File

@@ -121,7 +121,14 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
return JS_ThrowOutOfMemory(ctx);
}
return blob2js(ctx, bd);
JSValue ret = blob2js(ctx, bd);
// Ensure the returned object's prototype is set correctly for instanceof
JSValue ctor_proto = JS_GetPropertyStr(ctx, new_target, "prototype");
if (!JS_IsException(ctor_proto)) {
JS_SetPrototype(ctx, ret, ctor_proto);
}
JS_FreeValue(ctx, ctor_proto);
return ret;
}
// blob.write_bit(logical)
@@ -504,8 +511,10 @@ JSValue js_blob_use(JSContext *js) {
// Set the prototype on the constructor
JSValue proto = JS_GetClassProto(js, js_blob_id);
JS_SetConstructor(js, ctor, proto);
// Explicitly set the prototype property to ensure instanceof works
JS_SetPropertyStr(js, ctor, "__prototype__", JS_DupValue(js, proto));
JS_FreeValue(js, proto);
return ctor;
}

View File

@@ -12,6 +12,7 @@
#ifdef _WIN32
#include <io.h>
#include <direct.h>
#include <windows.h>
#define mkdir(path, mode) _mkdir(path)
#define rmdir _rmdir
#define getcwd _getcwd
@@ -22,6 +23,7 @@
#else
#include <unistd.h>
#include <dirent.h>
#include <sys/mman.h>
#endif
// Helper to convert JS value to file descriptor
@@ -115,6 +117,57 @@ JSC_CCALL(fd_read,
return ret;
)
JSC_SCALL(fd_slurp,
struct stat st;
if (stat(str, &st) != 0)
return JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno));
if (!S_ISREG(st.st_mode))
return JS_ThrowTypeError(js, "path is not a regular file");
size_t size = st.st_size;
if (size == 0)
return js_new_blob_stoned_copy(js, NULL, 0);
#ifndef _WIN32
int fd = open(str, O_RDONLY);
if (fd < 0)
return JS_ThrowReferenceError(js, "open failed: %s", strerror(errno));
void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return JS_ThrowReferenceError(js, "mmap failed: %s", strerror(errno));
}
ret = js_new_blob_stoned_copy(js, data, size);
munmap(data, size);
close(fd);
#else
// Windows: use memory mapping for optimal performance
HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return JS_ThrowReferenceError(js, "CreateFile failed: %lu", GetLastError());
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMapping == NULL) {
CloseHandle(hFile);
return JS_ThrowReferenceError(js, "CreateFileMapping failed: %lu", GetLastError());
}
void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (data == NULL) {
CloseHandle(hMapping);
CloseHandle(hFile);
return JS_ThrowReferenceError(js, "MapViewOfFile failed: %lu", GetLastError());
}
ret = js_new_blob_stoned_copy(js, data, size);
UnmapViewOfFile(data);
CloseHandle(hMapping);
CloseHandle(hFile);
#endif
)
JSC_CCALL(fd_lseek,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
@@ -217,7 +270,7 @@ JSC_CCALL(fd_fstat,
JSC_SCALL(fd_stat,
struct stat st;
if (stat(str, &st) != 0)
ret = JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno));
return JS_NewObject(js);
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
@@ -255,6 +308,7 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, open, 2),
MIST_FUNC_DEF(fd, write, 2),
MIST_FUNC_DEF(fd, read, 2),
MIST_FUNC_DEF(fd, slurp, 1),
MIST_FUNC_DEF(fd, lseek, 3),
MIST_FUNC_DEF(fd, getcwd, 0),
MIST_FUNC_DEF(fd, rmdir, 1),

View File

@@ -38,18 +38,25 @@ static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
}
JSC_SCALL(qop_open,
qop_desc *qop = js_malloc(js, sizeof(qop_desc));
if (!qop)
ret = JS_ThrowOutOfMemory(js);
int size = qop_open(str, qop);
if (size == 0) {
js_free(js, qop);
ret = JS_ThrowReferenceError(js, "Failed to open QOP archive: %s", str);
} else {
JSValue obj = JS_NewObjectClass(js, js_qop_archive_class_id);
JS_SetOpaque(obj, qop);
ret = obj;
size_t len;
void *data = js_get_blob_data(js, &len, argv[0]);
if (!data)
ret = JS_ThrowReferenceError(js, "Could not get blob data.\n");
else {
qop_desc *qop = js_malloc(js, sizeof(qop_desc));
if (!qop)
ret = JS_ThrowOutOfMemory(js);
else {
int size = qop_open_data((const unsigned char *)data, len, qop);
if (size == 0) {
js_free(js, qop);
ret = JS_ThrowReferenceError(js, "Failed to open QOP archive from blob");
} else {
JSValue obj = JS_NewObjectClass(js, js_qop_archive_class_id);
JS_SetOpaque(obj, qop);
ret = obj;
}
}
}
)

View File

@@ -58,6 +58,7 @@ extern "C" {
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#define QOP_FLAG_NONE 0
#define QOP_FLAG_COMPRESSED_ZSTD (1 << 0)
@@ -74,6 +75,9 @@ typedef struct {
typedef struct {
FILE *fh;
const unsigned char *data;
size_t data_size;
size_t data_pos;
qop_file *hashmap;
unsigned int files_offset;
unsigned int index_offset;
@@ -87,6 +91,11 @@ typedef struct {
// failure.
int qop_open(const char *path, qop_desc *qop);
// Open an archive from memory data. The supplied qop_desc will be filled with the
// information from the data header. Returns the size of the archive or 0 on
// failure.
int qop_open_data(const unsigned char *data, size_t data_size, qop_desc *qop);
// Read the index from an opened archive. The supplied buffer will be filled
// with the index data and must be at least qop->hashmap_size bytes long.
// No ownership is taken of the buffer; if you allocated it with malloc() you
@@ -175,6 +184,72 @@ static qop_uint64_t qop_read_64(FILE *fh) {
((qop_uint64_t)b[1] << 8) | ((qop_uint64_t)b[0]);
}
static void qop_seek(qop_desc *qop, long offset, int whence) {
if (qop->fh) {
fseek(qop->fh, offset, whence);
} else {
if (whence == SEEK_SET) {
qop->data_pos = offset;
} else if (whence == SEEK_END) {
qop->data_pos = qop->data_size + offset;
} else if (whence == SEEK_CUR) {
qop->data_pos += offset;
}
}
}
static unsigned short qop_read_16_desc(qop_desc *qop) {
if (qop->fh) {
return qop_read_16(qop->fh);
} else {
if (qop->data_pos + sizeof(unsigned short) > qop->data_size) return 0;
unsigned char b[sizeof(unsigned short)];
memcpy(b, qop->data + qop->data_pos, sizeof(unsigned short));
qop->data_pos += sizeof(unsigned short);
return (b[1] << 8) | b[0];
}
}
static unsigned int qop_read_32_desc(qop_desc *qop) {
if (qop->fh) {
return qop_read_32(qop->fh);
} else {
if (qop->data_pos + sizeof(unsigned int) > qop->data_size) return 0;
unsigned char b[sizeof(unsigned int)];
memcpy(b, qop->data + qop->data_pos, sizeof(unsigned int));
qop->data_pos += sizeof(unsigned int);
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
}
}
static qop_uint64_t qop_read_64_desc(qop_desc *qop) {
if (qop->fh) {
return qop_read_64(qop->fh);
} else {
if (qop->data_pos + sizeof(qop_uint64_t) > qop->data_size) return 0;
unsigned char b[sizeof(qop_uint64_t)];
memcpy(b, qop->data + qop->data_pos, sizeof(qop_uint64_t));
qop->data_pos += sizeof(qop_uint64_t);
return
((qop_uint64_t)b[7] << 56) | ((qop_uint64_t)b[6] << 48) |
((qop_uint64_t)b[5] << 40) | ((qop_uint64_t)b[4] << 32) |
((qop_uint64_t)b[3] << 24) | ((qop_uint64_t)b[2] << 16) |
((qop_uint64_t)b[1] << 8) | ((qop_uint64_t)b[0]);
}
}
static size_t qop_fread(qop_desc *qop, void *buf, size_t size, size_t nmemb) {
if (qop->fh) {
return fread(buf, size, nmemb, qop->fh);
} else {
size_t total = size * nmemb;
if (qop->data_pos + total > qop->data_size) return 0;
memcpy(buf, qop->data + qop->data_pos, total);
qop->data_pos += total;
return nmemb;
}
}
int qop_open(const char *path, qop_desc *qop) {
FILE *fh = fopen(path, "rb");
if (!fh) {
@@ -189,6 +264,9 @@ int qop_open(const char *path, qop_desc *qop) {
}
qop->fh = fh;
qop->data = NULL;
qop->data_size = 0;
qop->data_pos = 0;
qop->hashmap = NULL;
unsigned int index_len = qop_read_32(fh);
unsigned int archive_size = qop_read_32(fh);
@@ -218,31 +296,72 @@ int qop_open(const char *path, qop_desc *qop) {
return size;
}
int qop_open_data(const unsigned char *data, size_t data_size, qop_desc *qop) {
if (!data || data_size <= QOP_HEADER_SIZE) {
return 0;
}
qop->fh = NULL;
qop->data = data;
qop->data_size = data_size;
qop->data_pos = 0;
qop->hashmap = NULL;
qop_seek(qop, data_size - QOP_HEADER_SIZE, SEEK_SET);
unsigned int index_len = qop_read_32_desc(qop);
unsigned int archive_size = qop_read_32_desc(qop);
unsigned int magic = qop_read_32_desc(qop);
// Check magic, make sure index_len is possible with the data size
if (
magic != QOP_MAGIC ||
index_len * QOP_INDEX_SIZE > (unsigned int)(data_size - QOP_HEADER_SIZE)
) {
return 0;
}
// Find a good size for the hashmap: power of 2, at least 1.5x num entries
unsigned int hashmap_len = 1;
unsigned int min_hashmap_len = index_len * 1.5;
while (hashmap_len < min_hashmap_len) {
hashmap_len <<= 1;
}
qop->files_offset = data_size - archive_size;
qop->index_len = index_len;
qop->index_offset = data_size - qop->index_len * QOP_INDEX_SIZE - QOP_HEADER_SIZE;
qop->hashmap_len = hashmap_len;
qop->hashmap_size = qop->hashmap_len * sizeof(qop_file);
return data_size;
}
int qop_read_index(qop_desc *qop, void *buffer) {
qop->hashmap = buffer;
int mask = qop->hashmap_len - 1;
memset(qop->hashmap, 0, qop->hashmap_size);
fseek(qop->fh, qop->index_offset, SEEK_SET);
qop_seek(qop, qop->index_offset, SEEK_SET);
for (unsigned int i = 0; i < qop->index_len; i++) {
qop_uint64_t hash = qop_read_64(qop->fh);
qop_uint64_t hash = qop_read_64_desc(qop);
int idx = hash & mask;
while (qop->hashmap[idx].size > 0) {
idx = (idx + 1) & mask;
}
qop->hashmap[idx].hash = hash;
qop->hashmap[idx].offset = qop_read_32(qop->fh);
qop->hashmap[idx].size = qop_read_32(qop->fh);
qop->hashmap[idx].path_len = qop_read_16(qop->fh);
qop->hashmap[idx].flags = qop_read_16(qop->fh);
qop->hashmap[idx].offset = qop_read_32_desc(qop);
qop->hashmap[idx].size = qop_read_32_desc(qop);
qop->hashmap[idx].path_len = qop_read_16_desc(qop);
qop->hashmap[idx].flags = qop_read_16_desc(qop);
}
return qop->index_len;
}
void qop_close(qop_desc *qop) {
fclose(qop->fh);
if (qop->fh) {
fclose(qop->fh);
}
}
qop_file *qop_find(qop_desc *qop, const char *path) {
@@ -264,18 +383,18 @@ qop_file *qop_find(qop_desc *qop, const char *path) {
}
int qop_read_path(qop_desc *qop, qop_file *file, char *dest) {
fseek(qop->fh, qop->files_offset + file->offset, SEEK_SET);
return fread(dest, 1, file->path_len, qop->fh);
qop_seek(qop, qop->files_offset + file->offset, SEEK_SET);
return qop_fread(qop, dest, 1, file->path_len);
}
int qop_read(qop_desc *qop, qop_file *file, unsigned char *dest) {
fseek(qop->fh, qop->files_offset + file->offset + file->path_len, SEEK_SET);
return fread(dest, 1, file->size, qop->fh);
qop_seek(qop, qop->files_offset + file->offset + file->path_len, SEEK_SET);
return qop_fread(qop, dest, 1, file->size);
}
int qop_read_ex(qop_desc *qop, qop_file *file, unsigned char *dest, unsigned int start, unsigned int len) {
fseek(qop->fh, qop->files_offset + file->offset + file->path_len + start, SEEK_SET);
return fread(dest, 1, len, qop->fh);
qop_seek(qop, qop->files_offset + file->offset + file->path_len + start, SEEK_SET);
return qop_fread(qop, dest, 1, len);
}