zip side load; 9 slice working
This commit is contained in:
parent
f823e6b839
commit
90eba900e3
14
Makefile
14
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 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();
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
@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)
|
||||
{
|
||||
|
@ -23,10 +25,16 @@ vec2 uv9slice(vec2 uv, vec2 s, vec4 b)
|
|||
|
||||
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>
|
|
@ -33,6 +33,7 @@ out vec4 color;
|
|||
#define PI 3.141592
|
||||
|
||||
texture2D diffuse;
|
||||
@sampler_type smp nonfiltering
|
||||
sampler smp;
|
||||
|
||||
@include_block frag
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -67,3 +67,6 @@
|
|||
|
||||
#define QOI_IMPLEMENTATION
|
||||
#include "qoi.h"
|
||||
|
||||
#define QOP_IMPLEMENTATION
|
||||
#include "qop.h"
|
||||
|
|
|
@ -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
282
source/engine/qop.h
Normal 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 */
|
|
@ -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)
|
||||
|
|
|
@ -57,7 +57,6 @@ void js_stacktrace() {
|
|||
#endif
|
||||
}
|
||||
|
||||
|
||||
void script_evalf(const char *format, ...)
|
||||
{
|
||||
JSValue obj;
|
||||
|
|
465
tools/qopconv.c
Normal file
465
tools/qopconv.c
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue