Files
cell/source/qjs_qop.c
2025-11-23 11:20:59 -06:00

205 lines
5.8 KiB
C

#include "qjs_qop.h"
#define QOP_IMPLEMENTATION
#include "qop.h"
#include "qjs_blob.h"
#include "jsffi.h"
static JSClassID js_qop_archive_class_id;
static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
if (qop) {
if (qop->hashmap) {
js_free_rt(rt, qop->hashmap);
}
qop_close(qop);
js_free_rt(rt, qop);
}
}
static JSClassDef js_qop_archive_class = {
"qop archive",
.finalizer = js_qop_archive_finalizer,
};
static qop_desc *js2qop(JSContext *js, JSValue v) {
return JS_GetOpaque(v, js_qop_archive_class_id);
}
static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
if (qop->hashmap != NULL) return 1;
void *buffer = js_malloc(js, qop->hashmap_size);
if (!buffer) return 0;
int num = qop_read_index(qop, buffer);
if (num == 0) {
js_free(js, buffer);
return 0;
}
return 1;
}
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;
}
)
static JSValue js_qop_close(JSContext *js, JSValue self, int argc, JSValue *argv) {
qop_desc *qop = js2qop(js, self);
if (!qop)
return JS_ThrowInternalError(js, "Invalid QOP archive");
qop_close(qop);
JS_SetOpaque(self, NULL); // Prevent double free
return JS_NULL;
}
static JSValue js_qop_read(JSContext *js, JSValue self, int argc, JSValue *argv) {
qop_desc *qop = js2qop(js, self);
if (!qop)
return JS_ThrowInternalError(js, "Invalid QOP archive");
const char *path = JS_ToCString(js, argv[0]);
if (!path)
return JS_EXCEPTION;
if (!js_qop_ensure_index(js, qop)) {
JS_FreeCString(js, path);
return JS_ThrowReferenceError(js, "Failed to read QOP index");
}
qop_file *file = qop_find(qop, path);
JS_FreeCString(js, path);
if (!file)
return JS_NULL;
unsigned char *dest = js_malloc(js, file->size);
if (!dest)
return JS_ThrowOutOfMemory(js);
int bytes = qop_read(qop, file, dest);
if (bytes == 0) {
js_free(js, dest);
return JS_ThrowReferenceError(js, "Failed to read file");
}
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
js_free(js, dest);
return blob;
}
static JSValue js_qop_read_ex(JSContext *js, JSValue self, int argc, JSValue *argv) {
qop_desc *qop = js2qop(js, self);
if (!qop)
return JS_ThrowInternalError(js, "Invalid QOP archive");
const char *path = JS_ToCString(js, argv[0]);
if (!path)
return JS_EXCEPTION;
if (!js_qop_ensure_index(js, qop)) {
JS_FreeCString(js, path);
return JS_ThrowReferenceError(js, "Failed to read QOP index");
}
qop_file *file = qop_find(qop, path);
JS_FreeCString(js, path);
if (!file)
return JS_NULL;
unsigned int start;
unsigned int len;
if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0)
return JS_ThrowTypeError(js, "Invalid start or len");
unsigned char *dest = js_malloc(js, len);
if (!dest)
return JS_ThrowOutOfMemory(js);
int bytes = qop_read_ex(qop, file, dest, start, len);
if (bytes == 0) {
js_free(js, dest);
return JS_ThrowReferenceError(js, "Failed to read file part");
}
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes);
js_free(js, dest);
return blob;
}
static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv) {
qop_desc *qop = js2qop(js, self);
if (!qop)
return JS_ThrowInternalError(js, "Invalid QOP archive");
if (!js_qop_ensure_index(js, qop)) {
return JS_ThrowReferenceError(js, "Failed to read QOP index");
}
JSValue arr = JS_NewArray(js);
int count = 0;
for (unsigned int i = 0; i < qop->hashmap_len; i++) {
qop_file *file = &qop->hashmap[i];
if (file->size == 0) continue; // empty slot
char *path = js_malloc(js, file->path_len);
if (!path) {
return JS_ThrowOutOfMemory(js);
}
int len = qop_read_path(qop, file, path);
if (len == 0) {
js_free(js, path);
continue; // skip on error
}
JSValue str = JS_NewStringLen(js, path, len - 1); // -1 for null terminator
js_free(js, path);
JS_SetPropertyUint32(js, arr, count++, str);
}
return arr;
}
static const JSCFunctionListEntry js_qop_archive_funcs[] = {
JS_CFUNC_DEF("close", 0, js_qop_close),
JS_CFUNC_DEF("list", 0, js_qop_list),
JS_CFUNC_DEF("read", 1, js_qop_read),
JS_CFUNC_DEF("read_ex", 3, js_qop_read_ex),
};
static const JSCFunctionListEntry js_qop_funcs[] = {
MIST_FUNC_DEF(qop, open, 1),
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE),
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE),
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE),
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, JS_PROP_ENUMERABLE),
};
JSValue js_qop_use(JSContext *js) {
JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(JS_GetRuntime(js), js_qop_archive_class_id, &js_qop_archive_class);
JSValue archive_proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
return mod;
}