mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-22 16:00:34 +02:00
tools/stagekit writes a scenario's map in code (a floor in a box, props, players' starts) and plays it headless, a tick a frame, the same every time. Every player is logged each second and tests check what happened: walking, an obstacle, an elevator ridden up and down. 20 tests, about 2 s, CI-safe.
361 lines
9.4 KiB
C
361 lines
9.4 KiB
C
/*
|
|
CACHE.C
|
|
|
|
The map file StageKit writes: a Halo Custom Edition cache (version 609, which
|
|
the port converts itself: port/linux/game/cache_file_formats.c names each
|
|
offset used here). Its tags are built where the game reads a Custom Edition
|
|
map's tags, so the game's structures' pointers are written as they are; the
|
|
structure BSP goes at the top of that room, as every map puts it.
|
|
*/
|
|
|
|
/* ---------- headers */
|
|
|
|
#include "stagekit.h"
|
|
#include <sys/mman.h>
|
|
#include "tag_schema.h" /* port/linux/game/tag_schema.h */
|
|
#include "cache_file_formats.h" /* port/linux/game/cache_file_formats.h */
|
|
|
|
/* (the game's flags ask for strict ANSI, which hides these: Linux's values) */
|
|
#ifndef MAP_ANONYMOUS
|
|
#define MAP_ANONYMOUS 0x20
|
|
#endif
|
|
#ifndef MAP_FIXED_NOREPLACE
|
|
#define MAP_FIXED_NOREPLACE 0x100000
|
|
#endif
|
|
|
|
/* ---------- constants */
|
|
|
|
enum
|
|
{
|
|
HEADER_BYTES = 0x800,
|
|
TAG_INDEX_BYTES = 0x28,
|
|
MAXIMUM_TAGS = 128,
|
|
/* a tag handle's salt: any, the same in every reference */
|
|
HANDLE_SALT = 0x0E00,
|
|
/* the room kept at the top of the tag cache for the structure BSP */
|
|
BSP_BYTES = 0x10000,
|
|
};
|
|
|
|
/* ---------- structures */
|
|
|
|
/* a cache's header (cache_files.c keeps it to itself) */
|
|
struct cache_header
|
|
{
|
|
unsigned long signature;
|
|
long version;
|
|
unsigned long file_length;
|
|
unsigned long unused0;
|
|
unsigned long tag_data_offset;
|
|
unsigned long tag_data_size;
|
|
unsigned long unused1[2];
|
|
char name[0x20];
|
|
char build[0x20];
|
|
short scenario_type;
|
|
short pad;
|
|
unsigned long checksum;
|
|
unsigned char unused2[0x794];
|
|
unsigned long footer_signature;
|
|
};
|
|
typedef char cache_header_size_check[sizeof(struct cache_header) == HEADER_BYTES ? 1 : -1];
|
|
|
|
struct tag_index
|
|
{
|
|
void *instances;
|
|
long scenario_handle;
|
|
unsigned long checksum;
|
|
long tag_count;
|
|
long vertex_count;
|
|
unsigned long vertex_data_offset;
|
|
long index_count;
|
|
unsigned long index_data_offset;
|
|
unsigned long model_data_size;
|
|
unsigned long signature;
|
|
};
|
|
|
|
struct tag_instance
|
|
{
|
|
unsigned long group;
|
|
unsigned long parent_groups[2];
|
|
long handle;
|
|
char *name;
|
|
void *address;
|
|
long in_resource_map;
|
|
long pad;
|
|
};
|
|
|
|
/* ---------- globals */
|
|
|
|
static unsigned char *tag_cache;
|
|
static unsigned long tags_used;
|
|
static unsigned long bsp_used;
|
|
static struct tag_index *tag_index;
|
|
static struct tag_instance *instances;
|
|
|
|
/* ---------- private code */
|
|
|
|
/* every reference and tag index the generator left as it was made (zero, which
|
|
names the first tag) set to none, by the groups' own schemas (tag_schema_*.c),
|
|
so the map needs no correction (tools/map_validate.c) */
|
|
static void unset_references_to_none(
|
|
struct tag_schema_definition const *definition,
|
|
unsigned char *base,
|
|
char *empty_name)
|
|
{
|
|
struct tag_schema_field const *field;
|
|
|
|
for (field = definition->fields; field->type != _tag_schema_terminator; field++)
|
|
{
|
|
short index;
|
|
|
|
for (index = 0; index < field->count; index++)
|
|
{
|
|
unsigned char *at = base + field->offset + index * field->size;
|
|
|
|
if (field->type == _tag_schema_reference)
|
|
{
|
|
struct tag_reference *reference = (struct tag_reference *)at;
|
|
unsigned long const *groups = field->definition;
|
|
|
|
if (!reference->group_tag && !reference->name && !reference->index)
|
|
{
|
|
reference->group_tag = groups ? groups[0] : NONE;
|
|
reference->name = empty_name;
|
|
reference->index = NONE;
|
|
}
|
|
}
|
|
else if (field->type == _tag_schema_tag_index && TEST_FLAG(field->flags, _tag_schema_none_bit))
|
|
{
|
|
if (!*(long *)at)
|
|
*(long *)at = NONE;
|
|
}
|
|
else if (field->type == _tag_schema_block)
|
|
{
|
|
struct tag_block *block = (struct tag_block *)at;
|
|
struct tag_schema_definition const *element = field->definition;
|
|
long element_index;
|
|
|
|
for (element_index = 0; element_index < block->count; element_index++)
|
|
unset_references_to_none(element, (unsigned char *)block->address + element_index * element->size,
|
|
empty_name);
|
|
}
|
|
else if (field->type == _tag_schema_struct)
|
|
{
|
|
unset_references_to_none(field->definition, at, empty_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* each tag's references made none, where the schemas describe its group (the
|
|
structure BSP, written apart, is left as the generator made it) */
|
|
static void tags_finish(
|
|
void)
|
|
{
|
|
char *empty_name = cache_allocate(1);
|
|
long index;
|
|
|
|
for (index = 0; index < tag_index->tag_count; index++)
|
|
{
|
|
struct tag_instance const *instance = &instances[index];
|
|
struct tag_schema_group const *const *list;
|
|
|
|
for (list = tag_schema_group_lists; *list; list++)
|
|
{
|
|
struct tag_schema_group const *group;
|
|
|
|
for (group = *list; group->group_tag; group++)
|
|
{
|
|
if (group->group_tag == instance->group && instance->address)
|
|
unset_references_to_none(group->definition, instance->address, empty_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------- public code */
|
|
|
|
void cache_open(
|
|
void)
|
|
{
|
|
tag_cache = mmap((void *)CUSTOM_EDITION_TAG_CACHE_ADDRESS, CUSTOM_EDITION_TAG_CACHE_BYTES,
|
|
PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
|
|
if (tag_cache != (unsigned char *)CUSTOM_EDITION_TAG_CACHE_ADDRESS)
|
|
{
|
|
fprintf(stderr, "stagekit: the tag cache's address is taken\n");
|
|
exit(1);
|
|
}
|
|
tag_index = cache_allocate(TAG_INDEX_BYTES);
|
|
instances = cache_allocate(MAXIMUM_TAGS * sizeof(struct tag_instance));
|
|
tag_index->instances = instances;
|
|
tag_index->signature = FOURCC('t', 'a', 'g', 's');
|
|
}
|
|
|
|
void *cache_allocate(
|
|
unsigned long size)
|
|
{
|
|
void *result = tag_cache + tags_used;
|
|
|
|
tags_used = (tags_used + size + 3) & ~3UL;
|
|
if (tags_used > CUSTOM_EDITION_TAG_CACHE_BYTES - BSP_BYTES)
|
|
{
|
|
fprintf(stderr, "stagekit: the tags do not fit\n");
|
|
exit(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void *cache_block(
|
|
struct tag_block *block,
|
|
long count,
|
|
unsigned long element_size)
|
|
{
|
|
block->count = count;
|
|
block->address = count ? cache_allocate(count * element_size) : NULL;
|
|
return block->address;
|
|
}
|
|
|
|
void *cache_block_add(
|
|
struct tag_block *block,
|
|
unsigned long element_size)
|
|
{
|
|
unsigned char const *old = block->address;
|
|
unsigned char *grown = cache_allocate((block->count + 1) * element_size);
|
|
|
|
/* (the tags are allocated upwards: a copy, its old elements first) */
|
|
memcpy(grown, old, block->count * element_size);
|
|
block->address = grown;
|
|
block->count++;
|
|
return grown + (block->count - 1) * element_size;
|
|
}
|
|
|
|
void *cache_bsp_allocate(
|
|
unsigned long size)
|
|
{
|
|
void *result = tag_cache + CUSTOM_EDITION_TAG_CACHE_BYTES - BSP_BYTES + bsp_used;
|
|
|
|
bsp_used = (bsp_used + size + 3) & ~3UL;
|
|
if (bsp_used > BSP_BYTES)
|
|
{
|
|
fprintf(stderr, "stagekit: the structure BSP does not fit\n");
|
|
exit(1);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void *cache_bsp_block(
|
|
struct tag_block *block,
|
|
long count,
|
|
unsigned long element_size)
|
|
{
|
|
block->count = count;
|
|
block->address = count ? cache_bsp_allocate(count * element_size) : NULL;
|
|
return block->address;
|
|
}
|
|
|
|
long cache_tag_new(
|
|
unsigned long group,
|
|
unsigned long parent,
|
|
unsigned long grandparent,
|
|
char const *name,
|
|
unsigned long size,
|
|
void **data)
|
|
{
|
|
long index = tag_index->tag_count++;
|
|
struct tag_instance *instance = &instances[index];
|
|
char *name_copy = cache_allocate(strlen(name) + 1);
|
|
|
|
if (index >= (long)MAXIMUM_TAGS)
|
|
{
|
|
fprintf(stderr, "stagekit: more than %d tags\n", MAXIMUM_TAGS);
|
|
exit(1);
|
|
}
|
|
strcpy(name_copy, name);
|
|
instance->group = group;
|
|
instance->parent_groups[0] = parent;
|
|
instance->parent_groups[1] = grandparent;
|
|
instance->handle = ((HANDLE_SALT + index) << 16) | index;
|
|
instance->name = name_copy;
|
|
instance->address = *data = cache_allocate(size);
|
|
return instance->handle;
|
|
}
|
|
|
|
/* (a structure BSP's instance has no data of its own: the scenario's
|
|
reference places it) */
|
|
void cache_tag_no_data(
|
|
long handle)
|
|
{
|
|
instances[handle & 0xFFFF].address = NULL;
|
|
}
|
|
|
|
void cache_reference(
|
|
struct tag_reference *reference,
|
|
long handle)
|
|
{
|
|
struct tag_instance *instance = &instances[handle & 0xFFFF];
|
|
|
|
reference->group_tag = instance->group;
|
|
reference->name = instance->name;
|
|
reference->name_length = strlen(instance->name);
|
|
reference->index = handle;
|
|
}
|
|
|
|
void cache_no_reference(
|
|
struct tag_reference *reference,
|
|
unsigned long group)
|
|
{
|
|
reference->group_tag = group;
|
|
reference->name = cache_allocate(1);
|
|
reference->name_length = 0;
|
|
reference->index = NONE;
|
|
}
|
|
|
|
void cache_set_scenario(
|
|
long handle)
|
|
{
|
|
tag_index->scenario_handle = handle;
|
|
}
|
|
|
|
void cache_write(
|
|
char const *path,
|
|
long *tag_count)
|
|
{
|
|
struct cache_header header = { 0 };
|
|
unsigned long tag_data_offset = HEADER_BYTES + BSP_BYTES;
|
|
FILE *file;
|
|
|
|
tags_finish();
|
|
/* no models' vertices or indices: none, before the tags */
|
|
tag_index->vertex_data_offset = tag_data_offset;
|
|
tag_index->index_data_offset = 0;
|
|
tag_index->model_data_size = 0;
|
|
header.signature = FOURCC('h', 'e', 'a', 'd');
|
|
header.version = 609;
|
|
header.file_length = tag_data_offset + tags_used;
|
|
header.tag_data_offset = tag_data_offset;
|
|
header.tag_data_size = tags_used;
|
|
strcpy(header.name, "void");
|
|
/* (the port does not check a Custom Edition cache's build: StageKit's own) */
|
|
strcpy(header.build, "stagekit");
|
|
header.scenario_type = _scenario_type_solo;
|
|
header.footer_signature = FOURCC('f', 'o', 'o', 't');
|
|
file = fopen(path, "wb");
|
|
if (!file || fwrite(&header, sizeof(header), 1, file) != 1 ||
|
|
fwrite(tag_cache + CUSTOM_EDITION_TAG_CACHE_BYTES - BSP_BYTES, BSP_BYTES, 1, file) != 1 ||
|
|
fwrite(tag_cache, tags_used, 1, file) != 1 ||
|
|
fclose(file))
|
|
{
|
|
fprintf(stderr, "stagekit: could not write %s\n", path);
|
|
exit(1);
|
|
}
|
|
*tag_count = tag_index->tag_count;
|
|
}
|
|
|
|
/* where the structure BSP is in the file, how big, and where in memory */
|
|
void cache_bsp_place(
|
|
struct scenario_structure_bsp_reference *reference)
|
|
{
|
|
reference->file_offset = HEADER_BYTES;
|
|
reference->file_size = BSP_BYTES;
|
|
reference->base_address = tag_cache + CUSTOM_EDITION_TAG_CACHE_BYTES - BSP_BYTES;
|
|
}
|