zip side load; 9 slice working

This commit is contained in:
John Alanbrook 2024-10-19 18:37:27 -05:00
parent f823e6b839
commit 90eba900e3
13 changed files with 871 additions and 117 deletions

View file

@ -199,10 +199,10 @@ ifneq ($(MAKECMDGOALS), clean)
endif
.DEFAULT_GOAL := all
all: $(NAME)
all: $(NAME) core.zip
cp -f $(NAME) $(APP)$(EXT)
$(APP): $(NAME)
$(APP): $(NAME) core.zip
cp -f $(NAME) $(APP)
$(NAME): $(OBJS) $(DEPS)
@ -228,14 +228,10 @@ packer: tools/packer.c source/engine/miniz.c
@echo Making packer
$(CC) -O2 $^ -Isource/engine -o packer
core.cdb: packer $(CORE)
@echo Packing core.cdb
core.zip: packer $(CORE)
@echo Packing core.zip
./packer $@ $(CORE)
core.cdb.h: core.cdb
@echo Making $@
xxd -i $< > $@
ICNSIZE = 16 32 128 256 512 1024
ICNNAME := $(addsuffix .png, $(ICNSIZE))
ICON = icons/moon.gif
@ -280,7 +276,7 @@ crossweb:
clean:
@echo Cleaning project
rm -f core.cdb jso cdb packer TAGS source/engine/core.cdb.h tools/libcdb.a $(APP)* *.icns *.ico
rm -f core.zip jso cdb packer TAGS $(APP)* *.icns *.ico
find . -type f -name "*.[oad]" -delete
rm -rf Prosperon.app

View file

@ -239,7 +239,7 @@ layout.draw_commands = function(cmds, pos = [0,0])
}
if (config.background_image)
if (render.slice)
if (config.slice)
render.slice9(config.background_image, cmd.boundingbox, config.slice, config.background_color);
else
render.image(config.background_image, cmd.boundingbox, 0, config.color);

View file

@ -223,7 +223,6 @@ var sheetsize = 1024;
function pack_into_sheet(images)
{
return;
if (!Array.isArray(images)) images = [images];
if (images[0].texture.width > 300 && images[0].texture.height > 300) return;
sheet_frames = sheet_frames.concat(images);
@ -308,7 +307,6 @@ game.texture = function (path) {
}
if (ext === 'gif') {
console.info(path);
anim = os.make_gif(path);
if (!anim) return;
if (anim.frames.length === 1) {
@ -316,9 +314,7 @@ game.texture = function (path) {
anim.rect = anim.frames[0].rect;
}
game.texture.cache[path] = anim;
console.info("LOADING INTO GPU");
anim.frames[0].texture.load_gpu();
console.info(json.encode(anim));
return anim;
}

View file

@ -1005,6 +1005,11 @@ render.mask = function mask(image, pos, scale, rotation = 0, ref = 1)
render.draw(shape.quad);
}
function calc_image_size(img)
{
return [img.texture.width*img.rect.width, img.texture.height*img.rect.height];
}
render.image = function image(image, rect = [0,0], rotation = 0, color = Color.white) {
if (typeof image === "string")
image = game.texture(image);
@ -1012,7 +1017,7 @@ render.image = function image(image, rect = [0,0], rotation = 0, color = Color.w
var tex = image.texture;
if (!tex) return;
var image_size = [image.rect.width*tex.width, image.rect.height*tex.height];
var image_size = calc_image_size(image);
var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y];
@ -1028,34 +1033,36 @@ render.image = function image(image, rect = [0,0], rotation = 0, color = Color.w
var e = img_e();
var pos = [rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y]));
e.transform.trs(pos, undefined, size.div(image_size));
e.transform.trs(pos, undefined, size);
e.image = image;
e.shade = color;
return;
};
var slice9_t = os.make_transform();
// pos is the lower left corner, scale is the width and height
// slice is given in pixels
render.slice9 = function (image, rect = [0,0], slice = 0, color = Color.white) {
if (typeof image === 'string')
image = game.texture(image);
var tex = image.texture;
var size = [rect.width ? rect.width : tex.width, rect.height ? rect.height : tex.height];
var t = os.make_transform();
t.pos = pos;
t.scale = size.div([tex.width,tex.height]);
slice = clay.normalizeSpacing(slice);
var border = [slice.l / tex.width, slice.b / tex.height, slice.r / tex.width, slice.t / tex.height];
var tex = image.texture;
var image_size = calc_image_size(image);
var size = [rect.width ? rect.width : image_size.x, rect.height ? rect.height : image_size.y];
slice9_t.trs([rect.x,rect.y].sub(size.scale([rect.anchor_x, rect.anchor_y])), undefined, size);
slice = clay.normalizeSpacing(slice);
var border = [slice.l/image_size.x, slice.b/image_size.y, slice.r/image_size.x, slice.t/image_size.y];
render.use_shader(slice9shader);
set_model(t);
set_model(slice9_t);
render.use_mat({
shade: color,
diffuse: tex,
rect: [0, 0, 1, 1],
win_tex_scale: size.div(image_size),
rect: [image.rect.x, image.rect.y,image.rect.width,image.rect.height],
frag_rect: [image.rect.x, image.rect.y,image.rect.width,image.rect.height],
border: border,
scale: [size.x / tex.width, size.y / tex.height],
});
render.draw(shape.quad);
@ -1373,6 +1380,7 @@ prosperon.render = function () {
l:0
}, false);
prosperon.app();
render.forceflush();
profile.report("imgui");
if (debug.show) imgui_fn();

View file

@ -1,32 +1,40 @@
@block vert
uniform vec4 rect;
uniform vec2 diffuse_size;
uniform vec4 rect; // uv rect
void vert()
{
pos *= vec3(diffuse_size*rect.zw, 1);
uv = (uv*rect.zw)+rect.xy;
}
@end
@block frag
uniform vec4 border;
uniform vec2 scale;
uniform vec4 border; // border given as [left,down,right,top], in uv coordinates of texture, so (pixel)border.x/texture.x, etc
uniform vec2 win_tex_scale; // size of the drawing area over the size of the tex
uniform vec4 shade;
uniform vec4 frag_rect;
// border given as [left,down,right,top], in scaled coordinates
float map(float value, float originalMin, float originalMax, float newMin, float newMax) {
return (value - originalMin) / (originalMax - originalMin) * (newMax - newMin) + newMin;
}
vec2 uv9slice(vec2 uv, vec2 s, vec4 b)
{
vec2 t = clamp((s * uv - b.xy) / (s - b.xy - b.zw), 0., 1.);
return mix(uv * s, 1. - s * (1. - uv), t);
return mix(uv * s, 1. - s * (1. - uv), t);
}
void frag()
{
vec2 ruv = uv9slice(uv, scale, border);
color = texture(sampler2D(diffuse,smp), ruv);
if (color.a < 0.1) discard;
vec2 guv;
guv.x = map(uv.x, frag_rect.x, frag_rect.x+frag_rect.z, 0, 1);
guv.y = map(uv.y, frag_rect.y, frag_rect.y+frag_rect.w, 0, 1);
vec2 nuv = uv9slice(guv, win_tex_scale, border);
nuv.x = map(nuv.x, 0, 1, frag_rect.x, frag_rect.x+frag_rect.z);
nuv.y = map(nuv.y, 0, 1, frag_rect.y, frag_rect.y+frag_rect.w);
color = texture(sampler2D(diffuse,smp), nuv);
}
@end
#include <base.cg>
#include <base.cg>

View file

@ -33,6 +33,7 @@ out vec4 color;
#define PI 3.141592
texture2D diffuse;
@sampler_type smp nonfiltering
sampler smp;
@include_block frag

View file

@ -24,7 +24,6 @@ void main()
sprite s = sprites[int(baseinstance)+gl_InstanceIndex];
pos = a_pos;
uv = a_uv;
pos *= vec3(s.rect.zw,1);
uv = (uv*s.rect.zw)+s.rect.xy;
gl_Position = vp * s.model * vec4(pos, 1.0);
shade = s.shade;

View file

@ -67,3 +67,6 @@
#define QOI_IMPLEMENTATION
#include "qoi.h"
#define QOP_IMPLEMENTATION
#include "qop.h"

View file

@ -1199,27 +1199,11 @@ JSC_CCALL(render_make_sprite_ssbo,
JSValue sub = js_getpropidx(array,i);
transform *tr = js2transform(js_getpropstr(sub, "transform"));
HMM_Vec2 pos = js2vec2(js_getpropstr(sub, "pos"));
JSValue image = js_getpropstr(sub, "image");
texture *t = js2texture(js_getpropstr(image, "texture"));
HMM_Vec3 tscale;
if (t) {
tscale.x = t->width;
tscale.y = t->height;
tscale.z = 1;
tr->scale = HMM_MulV3(tr->scale, tscale);
}
tr->pos.xy = HMM_AddV2(tr->pos.xy, pos);
ms[i].model = transform2mat(tr);
ms[i].rect = js2rect(js_getpropstr(image,"rect"));
ms[i].shade = js2vec4(js_getpropstr(sub,"shade"));
if (t)
tr->scale = HMM_DivV3(tr->scale, tscale);
tr->pos.xy = HMM_SubV2(tr->pos.xy, pos);
}
int offset = sg_append_buffer(*b, (&(sg_range){
@ -1227,7 +1211,7 @@ JSC_CCALL(render_make_sprite_ssbo,
.size = size
}));
ret = number2js(offset/96);
ret = number2js(offset/96); // 96 size of a sprite struct
)
JSC_CCALL(render_make_t_ssbo,
@ -3389,6 +3373,9 @@ JSC_SCALL(os_make_gif,
JSValue gif = JS_NewObject(js);
JSValue delay_arr = JS_NewArray(js);
JSValue jstex = texture2js(tex);
float yslice = 1.0/frames;
for (int i = 0; i < frames; i++) {
JSValue frame = JS_NewObject(js);
@ -3399,11 +3386,12 @@ JSC_SCALL(os_make_gif,
.w = 1,
.h = yslice
}));
js_setpropstr(frame, "texture", texture2js(tex));
js_setpropstr(frame, "texture", JS_DupValue(js,jstex));
js_setprop_num(delay_arr, i, frame);
}
js_setpropstr(gif, "frames", delay_arr);
JS_FreeValue(js,jstex);
free(delays);

282
source/engine/qop.h Normal file
View file

@ -0,0 +1,282 @@
/*
Copyright (c) 2024, Dominic Szablewski - https://phoboslab.org
SPDX-License-Identifier: MIT
QOP - The Quite OK Package Format for bare bones file packages
// Define `QOP_IMPLEMENTATION` in *one* C/C++ file before including this
// library to create the implementation.
#define QOP_IMPLEMENTATION
#include "qop.h"
-- File format description (pseudo code)
struct {
// Path string and data of all files in this archive
struct {
uint8_t path[path_len];
uint8_t bytes[size];
} file_data[];
// The index, with a list of files
struct {
uint64_t hash;
uint32_t offset;
uint32_t size;
uint16_t path_len;
uint16_t flags;
} qop_file[];
// The number of files in the index
uint32_t index_len;
// The size of the whole archive, including the header
uint32_t archive_size;
// Magic bytes "qopf"
uint32_t magic;
} qop;
*/
/* -----------------------------------------------------------------------------
Header - Public functions */
#ifndef QOP_H
#define QOP_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <string.h>
#define QOP_FLAG_NONE 0
#define QOP_FLAG_COMPRESSED_ZSTD (1 << 0)
#define QOP_FLAG_COMPRESSED_DEFLATE (1 << 1)
#define QOP_FLAG_ENCRYPTED (1 << 8)
typedef struct {
unsigned long long hash;
unsigned int offset;
unsigned int size;
unsigned short path_len;
unsigned short flags;
} qop_file;
typedef struct {
FILE *fh;
qop_file *hashmap;
unsigned int files_offset;
unsigned int index_offset;
unsigned int index_len;
unsigned int hashmap_len;
unsigned int hashmap_size;
} qop_desc;
// Open an archive at path. The supplied qop_desc will be filled with the
// information from the file header. Returns the size of the archvie or 0 on
// failure
int qop_open(const char *path, 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
// need to free() it yourself after qop_close();
// Returns the number of files in the archive or 0 on error.
int qop_read_index(qop_desc *qop, void *buffer);
// Close the archive
void qop_close(qop_desc *qop);
// Find a file with the supplied path. Returns NULL if the file is not found
qop_file *qop_find(qop_desc *qop, const char *path);
// Copy the path of the file into dest. The dest buffer must be at least
// file->path_len bytes long. The path is null terminated.
// Returns the path length (including the null terminater) or 0 on error.
int qop_read_path(qop_desc *qop, qop_file *file, char *dest);
// Read the whole file into dest. The dest buffer must be at least file->size
// bytes long.
// Returns the number of bytes read
int qop_read(qop_desc *qop, qop_file *file, unsigned char *dest);
// Read part of a file into dest. The dest buffer must be at least len bytes
// long.
// Returns the number of bytes read.
int qop_read_ex(qop_desc *qop, qop_file *file, unsigned char *dest, unsigned int start, unsigned int len);
#ifdef __cplusplus
}
#endif
#endif /* QOP_H */
/* -----------------------------------------------------------------------------
Implementation */
#ifdef QOP_IMPLEMENTATION
typedef unsigned long long qop_uint64_t;
#define QOP_MAGIC \
(((unsigned int)'q') << 0 | ((unsigned int)'o') << 8 | \
((unsigned int)'p') << 16 | ((unsigned int)'f') << 24)
#define QOP_HEADER_SIZE 12
#define QOP_INDEX_SIZE 20
// MurmurOAAT64
static inline qop_uint64_t qop_hash(const char *key) {
qop_uint64_t h = 525201411107845655ull;
for (;*key;++key) {
h ^= (unsigned char)*key;
h *= 0x5bd1e9955bd1e995ull;
h ^= h >> 47;
}
return h;
}
static unsigned short qop_read_16(FILE *fh) {
unsigned char b[sizeof(unsigned short)] = {0};
if (fread(b, sizeof(unsigned short), 1, fh) != 1) {
return 0;
}
return (b[1] << 8) | b[0];
}
static unsigned int qop_read_32(FILE *fh) {
unsigned char b[sizeof(unsigned int)] = {0};
if (fread(b, sizeof(unsigned int), 1, fh) != 1) {
return 0;
}
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
}
static qop_uint64_t qop_read_64(FILE *fh) {
unsigned char b[sizeof(qop_uint64_t)] = {0};
if (fread(b, sizeof(qop_uint64_t), 1, fh) != 1) {
return 0;
}
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]);
}
int qop_open(const char *path, qop_desc *qop) {
FILE *fh = fopen(path, "rb");
if (!fh) {
return 0;
}
fseek(fh, 0, SEEK_END);
int size = ftell(fh);
if (size <= QOP_HEADER_SIZE || fseek(fh, size - QOP_HEADER_SIZE, SEEK_SET) != 0) {
fclose(fh);
return 0;
}
qop->fh = fh;
qop->hashmap = NULL;
unsigned int index_len = qop_read_32(fh);
unsigned int archive_size = qop_read_32(fh);
unsigned int magic = qop_read_32(fh);
// Check magic, make sure index_len is possible with the file size
if (
magic != QOP_MAGIC ||
index_len * QOP_INDEX_SIZE > (unsigned int)(size - QOP_HEADER_SIZE)
) {
fclose(fh);
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 = size - archive_size;
qop->index_len = index_len;
qop->index_offset = 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 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);
for (unsigned int i = 0; i < qop->index_len; i++) {
qop_uint64_t hash = qop_read_64(qop->fh);
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);
}
return qop->index_len;
}
void qop_close(qop_desc *qop) {
fclose(qop->fh);
}
qop_file *qop_find(qop_desc *qop, const char *path) {
if (qop->hashmap == NULL) {
return NULL;
}
int mask = qop->hashmap_len - 1;
qop_uint64_t hash = qop_hash(path);
int idx = hash & mask;
while (qop->hashmap[idx].size > 0) {
if (qop->hashmap[idx].hash == hash) {
return &qop->hashmap[idx];
}
idx = (idx + 1) & mask;
}
return NULL;
}
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);
}
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);
}
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);
}
#endif /* QOP_IMPLEMENTATION */

View file

@ -15,6 +15,7 @@
#include "font.h"
#include <fcntl.h>
#include "miniz.h"
#ifndef __EMSCRIPTEN__
@ -24,8 +25,6 @@
#include "sokol/sokol_fetch.h"
#include "stb_ds.h"
#include "core.cdb.h"
#if defined(_WIN32)
#include <direct.h>
#define mkdir(x,y) _mkdir(x)
@ -38,8 +37,7 @@ struct dirent *c_dirent = NULL;
char pathbuf[MAXPATH + 1];
static mz_zip_archive corecdb;
static mz_zip_archive game_cdb;
static mz_zip_archive core_res;
int LOADED_GAME = 0;
uint8_t *gamebuf;
@ -47,6 +45,28 @@ void *zipbuf;
sfetch_handle_t game_h;
void *os_slurp(const char *file, size_t *size)
{
FILE *f;
jump:
f = fopen(file, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
size_t fsize = ftell(f);
rewind(f);
void *slurp = malloc(fsize);
fread(slurp, fsize, 1, f);
fclose(f);
if (size) *size = fsize;
return slurp;
}
#define MAXGAMESIZE 15*1024*1024
static void response_cb(const sfetch_response_t *r)
@ -55,7 +75,6 @@ static void response_cb(const sfetch_response_t *r)
YughInfo("FINISHED FETCH\n");
zipbuf = malloc(r->data.size);
memcpy(zipbuf, r->data.ptr, r->data.size);
mz_zip_reader_init_mem(&game_cdb, zipbuf, r->data.size,0);
}
if (r->finished) {
@ -68,6 +87,33 @@ static void response_cb(const sfetch_response_t *r)
}
}
#if defined(__linux__)
#include <unistd.h>
#include <stdio.h>
#elif defined(__APPLE__)
#include <mach-o/dyld.h>
#elif defined(_WIN32)
#include <windows.h>
#endif
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;
}
void *gamedata;
void resources_init() {
@ -77,11 +123,13 @@ void resources_init() {
.num_lanes = 8,
.logger = { .func = sg_logging },
});
mz_zip_reader_init_mem(&corecdb, core_cdb, core_cdb_len, 0);
size_t coresize;
void *core = os_slurp("core.zip", &coresize);
mz_zip_reader_init_mem(&core_res, core, coresize, 0);
#ifdef __EMSCRIPTEN__
gamebuf = malloc(MAXGAMESIZE);
YughInfo("GRABBING GAME.ZIP\n");
game_h = sfetch_send(&(sfetch_request_t){
.path="game.zip",
.callback = response_cb,
@ -90,14 +138,6 @@ void resources_init() {
.size = MAXGAMESIZE
}
});
#else
size_t gamesize;
gamebuf = slurp_file("game.zip", &gamesize);
if (gamebuf) {
mz_zip_reader_init_mem(&game_cdb, gamebuf, gamesize, 0);
free(gamebuf);
return;
}
#endif
}
@ -159,17 +199,14 @@ static int ls_ftw(const char *path, const struct stat *sb, int typeflag)
time_t file_mod_secs(const char *file) {
struct stat attr;
mz_uint index;
mz_zip_archive_file_stat pstat;
if (!stat(file,&attr))
return attr.st_mtime;
else if ((index = mz_zip_reader_locate_file(&game_cdb, file, NULL, 0)) != -1) {
mz_zip_reader_file_stat(&game_cdb, index,&pstat);
return pstat.m_time;
}
else if ((index = mz_zip_reader_locate_file(&corecdb, file, NULL, 0)) != -1) {
mz_zip_reader_file_stat(&corecdb, index, &pstat);
else if ((index = mz_zip_reader_locate_file(&core_res, file, NULL, 0)) != -1) {
mz_zip_reader_file_stat(&core_res, index, &pstat);
return pstat.m_time;
}
@ -190,24 +227,22 @@ char **ls(const char *path)
return ls_paths;
}
static mz_zip_archive ar;
void pack_start(const char *name)
{
memset(&ar, 0, sizeof(ar));
int status = mz_zip_writer_init_file(&ar, name, 0);
// memset(&ar, 0, sizeof(ar));
// int status = mz_zip_writer_init_file(&ar, name, 0);
}
void pack_add(const char *path)
{
mz_zip_writer_add_file(&ar, path, path, NULL, 0, MZ_BEST_COMPRESSION);
// mz_zip_writer_add_file(&ar, path, path, NULL, 0, MZ_BEST_COMPRESSION);
}
void pack_end()
{
mz_zip_writer_finalize_archive(&ar);
mz_zip_writer_end(&ar);
// mz_zip_writer_finalize_archive(&ar);
// mz_zip_writer_end(&ar);
}
#else
@ -233,45 +268,19 @@ char *str_replace_ext(const char *s, const char *newext) {
int fexists(const char *path)
{
int len = strlen(path);
if (mz_zip_reader_locate_file(&game_cdb, path, NULL, 0) != -1) return 1;
else if (mz_zip_reader_locate_file(&corecdb, path, NULL, 0) != -1) return 1;
if (mz_zip_reader_locate_file(&core_res, path, NULL, 0) != -1) return 1;
else if (!access(path, R_OK)) return 1;
return 0;
}
void *os_slurp(const char *file, size_t *size)
{
FILE *f;
jump:
f = fopen(file, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
size_t fsize = ftell(f);
rewind(f);
void *slurp = malloc(fsize);
fread(slurp, fsize, 1, f);
fclose(f);
if (size) *size = fsize;
return slurp;
}
void *slurp_file(const char *filename, size_t *size)
{
void *ret;
if (!access(filename, R_OK))
return os_slurp(filename, size);
else if ((ret = mz_zip_reader_extract_file_to_heap(&game_cdb, filename, size, 0)))
return ret;
else if ((ret = mz_zip_reader_extract_file_to_heap(&corecdb, filename, size, 0)))
return ret;
return NULL;
else
return mz_zip_reader_extract_file_to_heap(&core_res, filename, size, 0);
}
char *slurp_text(const char *filename, size_t *size)

View file

@ -57,7 +57,6 @@ void js_stacktrace() {
#endif
}
void script_evalf(const char *format, ...)
{
JSValue obj;

465
tools/qopconv.c Normal file
View file

@ -0,0 +1,465 @@
/*
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;
} pack_state;
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->len >= state->capacity) {
state->capacity *= 2;
state->files = realloc(state->files, state->capacity * sizeof(qop_file));
}
qop_uint64_t hash = qop_hash(path);
// Write the path into the archive
int path_len = strlen(path) + 1;
int path_written = fwrite(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, 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 = {
.files = malloc(sizeof(qop_file) * 1024),
.len = 0,
.capacity = 1024,
.size = 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;
}