mirror of
https://github.com/doldecomp/melee.git
synced 2026-11-11 01:51:40 -10:00
348 lines
10 KiB
C
348 lines
10 KiB
C
/**
|
|
* @file
|
|
* References to array elements share the array's native storage, including
|
|
* when an element is reached before its container and host strides differ.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <dat/test.h>
|
|
|
|
static int failures;
|
|
|
|
#define CHECK(cond) \
|
|
do { \
|
|
if (!(cond)) { \
|
|
printf("%s:%d: failed: %s\n", __FILE__, __LINE__, #cond); \
|
|
failures++; \
|
|
} \
|
|
} while (0)
|
|
|
|
typedef struct Item {
|
|
int64_t value;
|
|
struct Item* next;
|
|
} Item;
|
|
|
|
typedef struct Root {
|
|
void* array;
|
|
Item* refs[3];
|
|
} Root;
|
|
|
|
typedef struct Counted {
|
|
uint32_t n;
|
|
Item* items;
|
|
} Counted;
|
|
|
|
typedef struct Inline {
|
|
uint32_t n;
|
|
Item items[];
|
|
} Inline;
|
|
|
|
typedef struct Recursive {
|
|
int64_t value;
|
|
struct Recursive* array;
|
|
struct Recursive* next;
|
|
} Recursive;
|
|
|
|
typedef struct AliasNode {
|
|
struct AliasNode* next;
|
|
int64_t value[];
|
|
} AliasNode;
|
|
|
|
typedef struct AliasRoot {
|
|
AliasNode** cell;
|
|
AliasNode* record;
|
|
} AliasRoot;
|
|
|
|
enum {
|
|
T_INT = 1,
|
|
T_COUNT,
|
|
T_ITEM,
|
|
T_ITEM_P,
|
|
T_ROW,
|
|
T_MATRIX,
|
|
T_MATRIX_P,
|
|
T_INLINE_ARRAY,
|
|
T_INLINE,
|
|
T_INLINE_P,
|
|
T_COUNTED,
|
|
T_COUNTED_P,
|
|
T_ROOT,
|
|
T_RECURSIVE,
|
|
T_RECURSIVE_P,
|
|
T_RECURSIVE_ARRAY,
|
|
T_RECURSIVE_ARRAY_P,
|
|
T_ALIAS_NODE,
|
|
T_ALIAS_NODE_P,
|
|
T_ALIAS_NODE_CELL,
|
|
T_ALIAS_ROOT,
|
|
T_ALIAS_VALUES,
|
|
T_TYPES,
|
|
};
|
|
|
|
#include "array_refs.h"
|
|
|
|
static void put32(unsigned char* p, uint32_t value)
|
|
{
|
|
p[0] = (unsigned char) (value >> 24);
|
|
p[1] = (unsigned char) (value >> 16);
|
|
p[2] = (unsigned char) (value >> 8);
|
|
p[3] = (unsigned char) value;
|
|
}
|
|
|
|
enum {
|
|
DATA = 0xE0,
|
|
ROOT = 0x80,
|
|
CONTAINER = 0xA0,
|
|
N = 6
|
|
};
|
|
|
|
static size_t build(unsigned char* file, int kind, uint32_t base, bool plain)
|
|
{
|
|
memset(file, 0, 0x200);
|
|
unsigned char* d = file + 0x20;
|
|
uint32_t relocs[16];
|
|
size_t nrelocs = 0;
|
|
uint32_t stride = plain ? 4 : 8;
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
put32(d + base + i * stride, i + 1);
|
|
if (!plain) {
|
|
put32(d + base + i * 8 + 4, base + ((i + 1) % N) * 8);
|
|
relocs[nrelocs++] = base + i * 8 + 4;
|
|
}
|
|
}
|
|
put32(d + ROOT, kind == T_MATRIX_P ? base : CONTAINER);
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
uint32_t index = i == 0 ? 0 : i == 1 ? 2 : N - 1;
|
|
put32(d + ROOT + 4 + i * 4, base + index * stride);
|
|
}
|
|
for (uint32_t i = 0; i < 4; i++) {
|
|
relocs[nrelocs++] = ROOT + i * 4;
|
|
}
|
|
if (kind != T_MATRIX_P) {
|
|
put32(d + CONTAINER, N);
|
|
if (kind == T_COUNTED_P) {
|
|
put32(d + CONTAINER + 4, base);
|
|
relocs[nrelocs++] = CONTAINER + 4;
|
|
}
|
|
}
|
|
unsigned char* at = d + DATA;
|
|
for (size_t i = 0; i < nrelocs; i++, at += 4) {
|
|
put32(at, relocs[i]);
|
|
}
|
|
size_t size = (size_t) (at - file);
|
|
put32(file, (uint32_t) size);
|
|
put32(file + 4, DATA);
|
|
put32(file + 8, (uint32_t) nrelocs);
|
|
return size;
|
|
}
|
|
|
|
static void test(int kind, bool reverse, bool zero, bool plain)
|
|
{
|
|
const DatSchema* variants[4][2] = {
|
|
{ &fixture_matrix_0_0_schema, &fixture_matrix_1_0_schema },
|
|
{ &fixture_counted_0_0_schema, &fixture_counted_1_0_schema },
|
|
{ &fixture_inline_0_0_schema, &fixture_inline_1_0_schema },
|
|
{ &fixture_counted_0_1_schema, &fixture_counted_1_1_schema },
|
|
};
|
|
unsigned variant = plain ? 3
|
|
: kind == T_MATRIX_P ? 0
|
|
: kind == T_COUNTED_P ? 1
|
|
: 2;
|
|
const DatSchema schema = *variants[variant][reverse];
|
|
unsigned char file[0x200];
|
|
uint32_t base = kind == T_INLINE_P ? CONTAINER + 4 : zero ? 0 : 0x40;
|
|
size_t size = build(file, kind, base, plain);
|
|
const char* error = NULL;
|
|
DatArchive* a = dat_open(&schema, file, size, &error);
|
|
CHECK(a != NULL);
|
|
if (a == NULL) {
|
|
printf("open: %s\n", error);
|
|
return;
|
|
}
|
|
Root* root = dat_at(a, ROOT, T_ROOT, DAT_COUNT_ONE, 0);
|
|
CHECK(root != NULL && root->array != NULL);
|
|
if (root == NULL || root->array == NULL) {
|
|
dat_close(a);
|
|
return;
|
|
}
|
|
Item* items = kind == T_COUNTED_P ? ((Counted*) root->array)->items
|
|
: kind == T_INLINE_P ? ((Inline*) root->array)->items
|
|
: root->array;
|
|
if (plain) {
|
|
int64_t* values = (void*) items;
|
|
CHECK((void*) root->refs[0] == &values[0]);
|
|
CHECK((void*) root->refs[1] == &values[2]);
|
|
CHECK((void*) root->refs[2] == &values[N - 1]);
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
CHECK(values[i] == (int64_t) i + 1);
|
|
CHECK(dat_at(a, base + i * 4, T_INT, DAT_COUNT_ONE, 0) ==
|
|
&values[i]);
|
|
}
|
|
CHECK(dat_verify(a, stdout) == 0);
|
|
values[2] = 123;
|
|
CHECK(*(int64_t*) root->refs[1] == 123);
|
|
values[2] = 3;
|
|
CHECK(dat_verify(a, stdout) == 0);
|
|
dat_close(a);
|
|
return;
|
|
}
|
|
CHECK(root->refs[0] == &items[0]);
|
|
CHECK(root->refs[1] == &items[2]);
|
|
CHECK(root->refs[2] == &items[N - 1]);
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
CHECK(items[i].value == (int64_t) i + 1);
|
|
CHECK(items[i].next == &items[(i + 1) % N]);
|
|
CHECK(dat_at(a, base + i * 8, T_ITEM, DAT_COUNT_ONE, 0) == &items[i]);
|
|
}
|
|
CHECK(dat_verify(a, stdout) == 0);
|
|
items[2].value = 123;
|
|
CHECK(root->refs[1]->value == 123);
|
|
items[2].value = 3;
|
|
root->refs[2]->value = 456;
|
|
CHECK(items[N - 1].value == 456);
|
|
root->refs[2]->value = N;
|
|
CHECK(dat_verify(a, stdout) == 0);
|
|
dat_close(a);
|
|
}
|
|
|
|
static void test_root_array(bool nested, bool zero)
|
|
{
|
|
const DatSchema schema = {
|
|
.types = types,
|
|
.ntypes = T_TYPES,
|
|
.names = names,
|
|
.nnames = DAT_NAME_COUNT,
|
|
};
|
|
unsigned char file[0x200];
|
|
uint32_t base = zero ? 0 : 0x40;
|
|
size_t size = build(file, T_MATRIX_P, base, false);
|
|
DatArchive* a = dat_open(&schema, file, size, NULL);
|
|
CHECK(a != NULL);
|
|
if (a == NULL) {
|
|
return;
|
|
}
|
|
Item* items = nested ? dat_at(a, base, T_MATRIX, DAT_COUNT_ONE, 0)
|
|
: dat_at(a, base, T_ITEM, DAT_COUNT_EXACTLY, N);
|
|
CHECK(items != NULL);
|
|
if (items != NULL) {
|
|
for (uint32_t i = 0; i < N; i++) {
|
|
CHECK(items[i].value == (int64_t) i + 1);
|
|
CHECK(items[i].next == &items[(i + 1) % N]);
|
|
CHECK(dat_at(a, base + i * 8, T_ITEM, DAT_COUNT_ONE, 0) ==
|
|
&items[i]);
|
|
}
|
|
CHECK(dat_verify(a, stdout) == 0);
|
|
}
|
|
dat_close(a);
|
|
}
|
|
|
|
/// The array is discovered while an element's recursive reader is active.
|
|
static void test_recursive_move(bool reverse, uint32_t root_count)
|
|
{
|
|
unsigned char bytes[0x20 + 24 + 4 * 4] = { 0 };
|
|
put32(bytes, sizeof bytes);
|
|
put32(bytes + 4, 24);
|
|
unsigned char* data = bytes + 0x20;
|
|
put32(data, 11);
|
|
put32(data + 8, 12);
|
|
put32(data + 12, 22);
|
|
const uint32_t relocs[] = { 4, 8, 16, 20 };
|
|
put32(bytes + 8, DAT_COUNTOF(relocs));
|
|
for (size_t i = 0; i < DAT_COUNTOF(relocs); i++) {
|
|
put32(data + 24 + 4 * i, relocs[i]);
|
|
}
|
|
put32(bytes, 0x20 + 24 + sizeof relocs);
|
|
const DatSchema* selected =
|
|
reverse ? &fixture_recursive_reverse_schema : &schema;
|
|
DatArchive* archive =
|
|
dat_open(selected, bytes, 0x20 + 24 + sizeof relocs, NULL);
|
|
CHECK(archive != NULL);
|
|
if (!archive) {
|
|
return;
|
|
}
|
|
Recursive* root =
|
|
dat_at(archive, 0, T_RECURSIVE,
|
|
root_count ? DAT_COUNT_EXACTLY : DAT_COUNT_ONE, root_count);
|
|
CHECK(root != NULL);
|
|
if (root) {
|
|
CHECK(root->array == root);
|
|
CHECK(root->next == &root->array[1]);
|
|
CHECK(root->value == 11 && root->next->value == 22);
|
|
CHECK(root->next->array == root && root->next->next == root);
|
|
CHECK(dat_at(archive, 12, T_RECURSIVE, DAT_COUNT_ONE, 0) ==
|
|
root->next);
|
|
CHECK(dat_verify(archive, stdout) == 0);
|
|
root->next->value = 33;
|
|
CHECK(root->array[1].value == 33);
|
|
}
|
|
dat_close(archive);
|
|
}
|
|
|
|
/// A pointer-cell view shares the first field of a larger record.
|
|
static void test_first_field_alias(bool record_first)
|
|
{
|
|
unsigned char bytes[0x20 + 24 + 12] = { 0 };
|
|
put32(bytes, sizeof bytes);
|
|
put32(bytes + 4, 24);
|
|
put32(bytes + 8, 3);
|
|
unsigned char* data = bytes + 0x20;
|
|
put32(data, 8);
|
|
put32(data + 4, 8);
|
|
put32(data + 8, 16);
|
|
put32(data + 12, 11);
|
|
put32(data + 20, 22);
|
|
put32(data + 24, 0);
|
|
put32(data + 28, 4);
|
|
put32(data + 32, 8);
|
|
const DatSchema* selected =
|
|
record_first ? &fixture_alias_record_first_schema : &schema;
|
|
DatArchive* archive = dat_open(selected, bytes, sizeof bytes, NULL);
|
|
CHECK(archive != NULL);
|
|
if (!archive) {
|
|
return;
|
|
}
|
|
AliasRoot* root = dat_at(archive, 0, T_ALIAS_ROOT, DAT_COUNT_ONE, 0);
|
|
CHECK(root != NULL);
|
|
if (root) {
|
|
CHECK(root->record->value[0] == 11 &&
|
|
root->record->next->value[0] == 22);
|
|
CHECK(root->cell == &root->record->next);
|
|
CHECK(dat_at(archive, 8, T_ALIAS_NODE, DAT_COUNT_ONE, 0) ==
|
|
root->record);
|
|
CHECK(dat_at(archive, 8, T_ALIAS_NODE_P, DAT_COUNT_ONE, 0) ==
|
|
root->cell);
|
|
CHECK(dat_verify(archive, stdout) == 0);
|
|
*root->cell = NULL;
|
|
CHECK(root->record->next == NULL);
|
|
}
|
|
dat_close(archive);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
for (int reverse = 0; reverse < 2; reverse++) {
|
|
test_first_field_alias(reverse);
|
|
for (uint32_t root_count = 0; root_count <= 2; root_count++) {
|
|
test_recursive_move(reverse, root_count);
|
|
}
|
|
test(T_MATRIX_P, reverse, false, false);
|
|
test(T_MATRIX_P, reverse, true, false);
|
|
test(T_COUNTED_P, reverse, false, false);
|
|
test(T_COUNTED_P, reverse, true, false);
|
|
test(T_INLINE_P, reverse, false, false);
|
|
test(T_COUNTED_P, reverse, false, true);
|
|
test(T_COUNTED_P, reverse, true, true);
|
|
test_root_array(false, reverse);
|
|
test_root_array(true, reverse);
|
|
}
|
|
if (failures != 0) {
|
|
printf("%d failures\n", failures);
|
|
return 1;
|
|
}
|
|
puts("ok: array references share native storage");
|
|
return 0;
|
|
}
|