#wii #graphics #gamedev #3d-model

sys brres-sys

Low-level bindings for librii's .brres layer

12 releases

0.1.12 Aug 3, 2024
0.1.10 Jun 15, 2024

#87 in Data formats

Download history 602/week @ 2024-06-10 34/week @ 2024-06-17 27/week @ 2024-07-01 12/week @ 2024-07-22 125/week @ 2024-07-29 14/week @ 2024-08-05 2/week @ 2024-08-12

1,046 downloads per month
Used in brres

MIT and GPL-2.0-or-later

1MB
17K SLoC

C++ 14K SLoC // 0.1% comments Rust 2.5K SLoC // 0.0% comments Bitbake 715 SLoC

crates.io docs.rs

brres-sys

Implements a Rust layer on top of librii::g3d's JSON export-import layer. Importantly, large buffers like texture data and vertex data are not actually encoded in JSON but passed directly as a binary blob. This allows JSON files to stay light.

Exposes the following Rust interface

pub struct CBrresWrapper<'a> {
    pub json_metadata: &'a str,
    pub buffer_data: &'a [u8],
    // ...
}

impl<'a> CBrresWrapper<'a> {
	// .bin -> .json
    pub fn from_bytes(buf: &[u8]) -> anyhow::Result<Self>;
    // .json -> .bin
    pub fn write_bytes(json: &str, buffer: &[u8]) -> anyhow::Result<Self>;
}

This wraps the following C interface, which can still be used (say for other language bindings).

// include/brres_sys.h
struct CResult {
	const char* json_metadata;
	uint32_t len_json_metadata;
	const void* buffer_data;
	uint32_t len_buffer_data;
	void (*freeResult)(struct CResult* self);
	void* opaque;
};
uint32_t brres_read_from_bytes(CResult* result, const void* buf,
                               uint32_t len);
uint32_t brres_write_bytes(CResult* result, const char* json,
                          uint32_t json_len, const void* buffer,
                          uint32_t buffer_len);
void brres_free(CResult* result);

Internal API docs

1. Testing

Every BRRES format is validated to produce 1:1 byte-identical output, even the Intermediate Representations (see below). Tests can be found in the tests folder.

2. BRRES subfiles

Filetype Description Source file Binary rep Intermediate rep
BRRES v0 3D Resource ArchiveIO.cpp BinaryArchive Archive
MDL0 v11 3D Model ModelIO.cpp BinaryModel Model
TEX0 v1/v3 Texture TextureIO.cpp - TextureData
CLR0 v4 Shader uniform animation AnimClrIO.cpp BinaryClr -
SHP0 Bone/character animation - - -
SRT0 v5 Texture scale/rotate/translate animation AnimIO.cpp BinarySrt SrtAnim*
PAT0 v4 Texture image animation AnimTexPatIO.cpp BinaryTexPat -
VIS0 v4 Bone visibility animation AnimVisIO.cpp BinaryVis -

* SrtAnim is read-only for now; BinaryArchive contains read-write BinarySrt currently.

librii provides two structures for each filetype:

  1. Binary representation: Designed to be a 1:1 mapping of the format. Editing this directly may require much book-keeping and be error prone. However, even corrupt models will usually be parseable here.
  2. Intermediate representation: Simple enough to cover the entire set of officially-generated models but no simpler. Easy to edit; lots of validation. May fail on community models created with jank tooling.

Parsing a BRRES file into the Binary representation (BinaryArchive):

std::expected<librii::g3d::BinaryArchive, std::string> parsed_brres = librii::g3d::BinaryArchive::read(reader, transaction);
if (!parsed_brres) {
    return std::unexpected(std::format("Failed to parse BRRES with error: {}", parsed_brres.error()));
}

Unpacking a BinaryArchive into the Intermediate representation (Archive):

std::expected<librii::g3d::Archive, std::string> archive = librii::g3d::Archive::from(*parsed_brres);
if (!archive) {
    return std::unexpected(std::format("Failed to unpack BRRES file with error: {}", archive.error()));
}
librii::g3d::Archive data(std::move(*archive));
for (const auto &model : data.models) {
    printf("%s\n", std::format("Model: {} with {} materials", model.name, model.materials.size()).c_str());
}

3. MDL0 subfiles

Information on MDL0 (BinaryModel, Model) specifically is provided:

Filetype Description Source file Binary rep Intermediate rep
MDL0.ByteCode Draw calls + Skeleton ModelIO.cpp ByteCodeMethod Merged into BoneData, DrawMatrix
MDL0.Bone Bone BoneIO.cpp BinaryBoneData BoneData
MDL0.PositionBuf Holds vertex positions ModelIO.cpp - PositionBuffer
MDL0.NormalBuf Holds vertex normals ModelIO.cpp - NormalBuffer
MDL0.ColorBuf Holds vertex colors ModelIO.cpp - ColorBuffer
MDL0.UVBuf Holds UV maps ModelIO.cpp - TextureCoordinateBuffer
MDL0.FurVecBuf Fur related - - -
MDL0.FurPosBuf Fur related - - -
MDL0.Material Material data MatIO.cpp BinaryMaterial G3dMaterialData
MDL0.TEV Shader data TevIO.cpp BinaryTev ** Merged into G3dMaterialData
MDL0.Mesh 3D mesh data ModelIO.cpp - PolygonData
MDL0.TextureLink Internal ModelIO.cpp - - ***
MDL0.PaletteLink Internal - - - ****
MDL0.UserData Metadata - - -

*** BinaryTev is often replaced by a partial gx::LowLevelGxMaterial instance containing only the TEV fields BinaryTev sets.

*** Texture links are always recomputed. If a model had invalid texture links, simply passing the data through BinaryModel would correct it.

**** Palettes are not supported.

It's recommended you always use the intermediate representation when possible. G3dMaterialData directly satisfies LowLevelGxMaterial which makes it compatible with librii::gl's shader compiler. Of course, a BinaryMaterial + BinaryTev (in the form of a partial gx::LowLevelGxMaterial) combo can be rendered via conversion with MatIO's function fromBinMat(...) -> gx::LowLevelGxMaterial with TEV merging enabled.

Example usage (low level API)

e.g. the following idiomatic C code:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "include/brres_sys.h"

int main() {
	uint8_t my_brres_file[] = { /* ... */ }; // Initialize with actual .brres file data

	CResult result = {};
	uint32_t ok = brres_read_from_bytes(&result, my_brres_file, sizeof(my_brres_file));

	if (!ok) {
		printf("Failed to read .brres: %*.s\n", (size_t)result.len_json_metadata, result.json_metadata);
		brres_free(&result); // Free the error message
		return 1;
	}

	// Write JSON metadata to a file
	FILE *json_file = fopen("output.json", "wb");
	if (json_file == NULL) {
		perror("Failed to open output.json for writing");
		brres_free(&result);
		return 1;
	}

	size_t written = fwrite(result.json_metadata, 1, result.len_json_metadata, json_file);
	if (written != result.len_json_metadata) {
		perror("Failed to write all JSON metadata to output.json");
		fclose(json_file);
		brres_free(&result);
		return 1;
	}

	fclose(json_file);

	// Write binary buffer data to a file
	FILE *bin_file = fopen("output.bin", "wb");
	if (bin_file == NULL) {
		perror("Failed to open output.bin for writing");
		brres_free(&result);
		return 1;
	}

	written = fwrite(result.buffer_data, 1, result.len_buffer_data, bin_file);
	if (written != result.len_buffer_data) {
		perror("Failed to write all buffer data to output.bin");
		fclose(bin_file);
		brres_free(&result);
		return 1;
	}

	fclose(bin_file);
	brres_free(&result);

	return 0;
}

Dependencies

~0.5–2.8MB
~53K SLoC