fetch
This commit is contained in:
@@ -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! */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
145
source/qop.h
145
source/qop.h
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user