mirror of
https://github.com/doldecomp/melee.git
synced 2026-11-10 17:10:37 -10:00
218 lines
6.0 KiB
C
218 lines
6.0 KiB
C
/**
|
|
* @file
|
|
* End to end: walk every archive `melee-dat native expect` lists, from the
|
|
* game's files, and compare what this library reaches with what
|
|
* `melee-dat`'s walker does. Every object converted is then read back and
|
|
* checked against the data it came from.
|
|
*
|
|
* Usage: e2e <expect.txt> <files dir> [max mismatches to print]
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "melee_dat.h"
|
|
#include <dat/test.h>
|
|
|
|
#define MAX_ARCHIVES 256
|
|
|
|
static unsigned char* read_file(const char* path, size_t* size)
|
|
{
|
|
FILE* f = fopen(path, "rb");
|
|
if (f == NULL) {
|
|
return NULL;
|
|
}
|
|
fseek(f, 0, SEEK_END);
|
|
long n = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
unsigned char* bytes = malloc(n > 0 ? (size_t) n : 1);
|
|
if (bytes == NULL || fread(bytes, 1, (size_t) n, f) != (size_t) n) {
|
|
fclose(f);
|
|
free(bytes);
|
|
return NULL;
|
|
}
|
|
fclose(f);
|
|
*size = (size_t) n;
|
|
return bytes;
|
|
}
|
|
|
|
/// A line, without its newline, in a buffer that grows; `NULL` at the end.
|
|
static char* read_line(FILE* f, char** buf, size_t* cap)
|
|
{
|
|
size_t len = 0;
|
|
int c;
|
|
while ((c = fgetc(f)) != EOF && c != '\n') {
|
|
if (len + 1 >= *cap) {
|
|
*cap = *cap ? *cap * 2 : 256;
|
|
*buf = realloc(*buf, *cap);
|
|
}
|
|
(*buf)[len++] = (char) c;
|
|
}
|
|
if (c == EOF && len == 0) {
|
|
return NULL;
|
|
}
|
|
if (*cap == 0) {
|
|
*cap = 256;
|
|
*buf = realloc(*buf, *cap);
|
|
}
|
|
(*buf)[len] = '\0';
|
|
return *buf;
|
|
}
|
|
|
|
typedef struct Lines {
|
|
char** items;
|
|
size_t len, cap;
|
|
} Lines;
|
|
|
|
static void push_line(Lines* l, const char* line)
|
|
{
|
|
if (l->len == l->cap) {
|
|
l->cap = l->cap ? l->cap * 2 : 64;
|
|
l->items = realloc(l->items, l->cap * sizeof(char*));
|
|
}
|
|
l->items[l->len++] = strcpy(malloc(strlen(line) + 1), line);
|
|
}
|
|
|
|
static void clear_lines(Lines* l)
|
|
{
|
|
for (size_t i = 0; i < l->len; i++) {
|
|
free(l->items[i]);
|
|
}
|
|
l->len = 0;
|
|
}
|
|
|
|
typedef struct Totals {
|
|
size_t archives, failed, mismatched, unverified, missing;
|
|
long budget;
|
|
} Totals;
|
|
|
|
/// Compare one archive's trace with what was expected of it.
|
|
static void check(Totals* t, const char* name, const Lines* want,
|
|
const char* dir)
|
|
{
|
|
char file[1024];
|
|
size_t at = 0;
|
|
const char* sep = strchr(name, '@');
|
|
size_t len = sep ? (size_t) (sep - name) : strlen(name);
|
|
snprintf(file, sizeof file, "%.*s", (int) len, name);
|
|
if (sep != NULL) {
|
|
at = strtoull(sep + 1, NULL, 16);
|
|
}
|
|
char path[2048];
|
|
snprintf(path, sizeof path, "%s/%s", dir, file);
|
|
size_t size;
|
|
unsigned char* bytes = read_file(path, &size);
|
|
if (bytes == NULL) {
|
|
printf("%s: can't read %s\n", name, path);
|
|
t->missing++;
|
|
return;
|
|
}
|
|
DatArchive* archives[MAX_ARCHIVES];
|
|
size_t offsets[MAX_ARCHIVES];
|
|
const char* error = NULL;
|
|
int n = dat_open_packed(&melee_dat_schema, bytes, size, archives, offsets,
|
|
MAX_ARCHIVES, &error);
|
|
free(bytes);
|
|
if (n < 0) {
|
|
printf("%s: %s\n", name, error);
|
|
t->failed++;
|
|
return;
|
|
}
|
|
t->archives++;
|
|
for (int i = 0; i < n; i++) {
|
|
if (offsets[i] != at) {
|
|
continue;
|
|
}
|
|
dat_load_roots(archives[i], file, (uint32_t) i);
|
|
FILE* out = tmpfile();
|
|
dat_trace(archives[i], out, DAT_TRACE_ALL);
|
|
rewind(out);
|
|
Lines got = { 0 };
|
|
char* buf = NULL;
|
|
size_t cap = 0;
|
|
for (char* line; (line = read_line(out, &buf, &cap)) != NULL;) {
|
|
push_line(&got, line);
|
|
}
|
|
fclose(out);
|
|
free(buf);
|
|
/* Both are sorted the same way, line by line */
|
|
size_t w = 0, g = 0;
|
|
bool differs = false;
|
|
while (w < want->len || g < got.len) {
|
|
int cmp = w == want->len ? 1
|
|
: g == got.len ? -1
|
|
: strcmp(want->items[w], got.items[g]);
|
|
if (cmp == 0) {
|
|
w++, g++;
|
|
continue;
|
|
}
|
|
differs = true;
|
|
if (t->budget-- > 0) {
|
|
printf("%s: %s %s\n", name, cmp < 0 ? "missing" : "extra",
|
|
cmp < 0 ? want->items[w] : got.items[g]);
|
|
}
|
|
if (cmp < 0) {
|
|
w++;
|
|
} else {
|
|
g++;
|
|
}
|
|
}
|
|
if (differs) {
|
|
t->mismatched++;
|
|
}
|
|
size_t bad = dat_verify(archives[i], t->budget > 0 ? stdout : NULL);
|
|
if (bad > 0) {
|
|
printf("%s: %zu don't read back\n", name, bad);
|
|
t->unverified++;
|
|
t->budget -= (long) bad;
|
|
}
|
|
clear_lines(&got);
|
|
free(got.items);
|
|
}
|
|
for (int i = 0; i < n; i++) {
|
|
dat_close(archives[i]);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
if (argc < 3) {
|
|
fprintf(stderr, "usage: %s <expect.txt> <files dir> [max]\n", argv[0]);
|
|
return 2;
|
|
}
|
|
FILE* expect = fopen(argv[1], "r");
|
|
if (expect == NULL) {
|
|
perror(argv[1]);
|
|
return 2;
|
|
}
|
|
Totals t = { 0 };
|
|
t.budget = argc > 3 ? atol(argv[3]) : 50;
|
|
Lines want = { 0 };
|
|
char name[1024] = "";
|
|
char* buf = NULL;
|
|
size_t cap = 0;
|
|
for (char* line; (line = read_line(expect, &buf, &cap)) != NULL;) {
|
|
if (strncmp(line, "archive ", 8) == 0) {
|
|
if (name[0] != '\0') {
|
|
check(&t, name, &want, argv[2]);
|
|
}
|
|
clear_lines(&want);
|
|
snprintf(name, sizeof name, "%s", line + 8);
|
|
} else {
|
|
push_line(&want, line);
|
|
}
|
|
}
|
|
if (name[0] != '\0') {
|
|
check(&t, name, &want, argv[2]);
|
|
}
|
|
fclose(expect);
|
|
free(buf);
|
|
printf("%zu archives: %zu differ from the walker, %zu don't read back, "
|
|
"%zu failed to open, %zu missing (%zu-bit %s-endian)\n",
|
|
t.archives, t.mismatched, t.unverified, t.failed, t.missing,
|
|
sizeof(void*) * 8,
|
|
*(const unsigned char*) &(const uint16_t){ 1 } ? "little" : "big");
|
|
return t.mismatched || t.unverified || t.failed || t.missing ? 1 : 0;
|
|
}
|