jancity/src/container/src/container.c

233 lines
4.8 KiB
C

#include <container.h>
#include <gfx.h>
#include <sfx.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int readstr(char *const str, const size_t n, FILE *const f)
{
int ret = -1;
size_t i = 0;
do
{
if (i >= n)
{
fprintf(stderr, "%s: string too long (> %zu bytes): %*.s\n",
__func__, n, (int)n, str);
goto end;
}
else if (!fread(&str[i], sizeof *str, 1, f))
{
fprintf(stderr, "%s: expected null-terminated string\n", __func__);
goto end;
}
} while (str[i++]);
ret = 0;
end:
return ret;
}
static int get_file_size(size_t *const sz, FILE *const f)
{
char szstr[sizeof "2147483647"];
if (readstr(szstr, sizeof szstr, f))
return -1;
errno = 0;
char *end;
const unsigned long val = strtoul(szstr, &end, 10);
if (errno)
{
fprintf(stderr, "%s: strtoul(3): %s\n", __func__, strerror(errno));
return -1;
}
else if (*end)
{
fprintf(stderr, "%s: invalid number %s\n", __func__, szstr);
return -1;
}
*sz = val;
return 0;
}
static int read_file_contents(const struct container *const el, FILE *const f,
const long init_off, const size_t sz)
{
int ret = -1;
switch (el->type)
{
case CONTAINER_TYPE_SPRITE:
if (sprite_from_fp(el->data.sprite, f))
goto end;
break;
case CONTAINER_TYPE_SOUND:
if (sfx_sound_from_fp(el->data.sound, f, sz))
goto end;
break;
}
const long off = ftell(f);
if (off < 0)
{
fprintf(stderr, "%s:%d: fseek failed: %s\n",
__func__, __LINE__, strerror(errno));
goto end;
}
else if (off > init_off + sz)
{
fprintf(stderr, "%s: %s: detected read past file contents\n",
__func__, el->path);
goto end;
}
else if (off != init_off + sz)
{
fprintf(stderr, "only %ld bytes read, %ld expected\n",
off, init_off + sz);
goto end;
}
ret = 0;
end:
return ret;
}
static const struct container *find_element(const struct container *const list,
const size_t n, FILE *const f)
{
char name[128];
if (readstr(name, sizeof name, f))
return NULL;
for (size_t i = 0; i < n; i++)
{
const struct container *const el = &list[i];
if (!strcmp(el->path, name))
return el;
}
fprintf(stderr, "file %s not found on the list\n", name);
return NULL;
}
static int read_element(const struct container *const list, const size_t n,
FILE *const f, bool *const done)
{
long init_off;
const struct container *el = NULL;
size_t sz;
if (!(el = find_element(list, n, f))
|| get_file_size(&sz, f))
return -1;
else if ((init_off = ftell(f)) < 0)
{
fprintf(stderr, "%s:%d: fseek failed: %s\n",
__func__, __LINE__, strerror(errno));
return -1;
}
else if (read_file_contents(el, f, init_off, sz))
return -1;
done[el - list] = true;
return 0;
}
static int read_all_elements(const struct container *const list, const size_t n,
FILE *const f)
{
int ret = -1;
/* VLAs are generally frowned upon, but we can safely assume
* 'n' is reasonably low. */
bool done[n];
memset(&done, 0, sizeof done);
for (size_t i = 0; i < sizeof done / sizeof *done; i++)
{
if (feof(f) || ferror(f))
{
fprintf(stderr, "feof or ferror found\n");
goto end;
}
else if (read_element(list, n, f, done))
goto end;
}
for (size_t i = 0; i < sizeof done / sizeof *done; i++)
{
if (!done[i])
{
fprintf(stderr, "%s: %s not found inside container file\n",
__func__, list[i].path);
goto end;
}
}
ret = 0;
end:
return ret;
}
void container_free(const struct container *const list, const size_t n)
{
if (!list)
return;
for (size_t i = 0; i < n; i++)
{
const struct container *const el = &list[i];
switch (el->type)
{
case CONTAINER_TYPE_SPRITE:
sprite_free(el->data.sprite);
break;
case CONTAINER_TYPE_SOUND:
sfx_free(el->data.sound);
break;
}
}
}
int container_load_ex(const char *const path, const struct container *const list,
const size_t n)
{
int ret = -1;
FILE *f = NULL;
if (!(f = fopen(path, "rb")))
{
fprintf(stderr, "could not open %s: %s\n", path, strerror(errno));
goto end;
}
else if (read_all_elements(list, n, f))
goto end;
ret = 0;
end:
if (f)
fclose(f);
return ret;
}