Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Created September 22, 2025 18:29
Show Gist options
  • Save ariankordi/15c713e1208d7a5d534152dc276bab4a to your computer and use it in GitHub Desktop.
Save ariankordi/15c713e1208d7a5d534152dc276bab4a to your computer and use it in GitHub Desktop.
(2025-05-13) Wii RFL_Res.dat resource reader in JS using Kaitai. Displays each shape in the resource. This REQUIRES having RFL_Res.dat in the directory you're using it in.
meta:
id: r_f_l_resource
#id: c_f_li_archive
title: Resource archive image format for RFL, NFL, CFL.
endian: be # Set to BE for RFL.
#bit-endian: le # No bitfields.
seq:
- id: total_archive_count
type: u2
doc: |
Amount of total archives within the file.
RFL = 18 (RFLiArcID_Max), NFL = 20 (?), CFL = 20 (CFLi_PARTS_ID_COUNT)
- id: version
type: u2
doc: |
Resource version. Set in RFL and CFL, only seen used in CFL debug mode.
CFL bootloadDB2Res_: nn::dbg::detail::Printf("CFL: Cached Resource Version = 0x%04x\n");
/ Debug assert; "cfl_resource.cpp",0x19d,"%s", "loader->mVersion >= 0x509"
#- id: archives
# type: archive
# repeat: expr
# repeat-expr: 20
- id: offsets
doc: |
Offsets for each part type.
type: u4
repeat: expr
repeat-expr: total_archive_count
instances:
archives:
pos: offsets[_index] # Reads from each offset in the list.
type: r_f_li_archive
repeat: expr
repeat-expr: total_archive_count # Should match offsets' length.
archive0:
pos: offsets[0]
type: r_f_li_archive
archive1:
pos: offsets[1]
type: r_f_li_archive
archive2:
pos: offsets[2]
type: r_f_li_archive
archive3:
pos: offsets[3]
type: r_f_li_archive
archive4:
pos: offsets[4]
type: r_f_li_archive
archive5:
pos: offsets[5]
type: r_f_li_archive
archive6:
pos: offsets[6]
type: r_f_li_archive
archive7:
pos: offsets[7]
type: r_f_li_archive
archive8:
pos: offsets[8]
type: r_f_li_archive
archive9:
pos: offsets[9]
type: r_f_li_archive
archive10:
pos: offsets[10]
type: r_f_li_archive
archive11:
pos: offsets[11]
type: r_f_li_archive
archive12:
pos: offsets[12]
type: r_f_li_archive
archive13:
pos: offsets[13]
type: r_f_li_archive
archive14:
pos: offsets[14]
type: r_f_li_archive
archive15:
pos: offsets[15]
type: r_f_li_archive
archive16:
pos: offsets[16]
type: r_f_li_archive
archive17:
pos: offsets[17]
type: r_f_li_archive
archive18:
pos: offsets[18]
type: r_f_li_archive
archive19:
pos: offsets[19]
type: r_f_li_archive
archive1texture0:
# Read texture header for archive 1 (eye) index 0.
# Seek to offsets (8). Go to offset of archive 1.
# Then estimate file count * sizeof(u32)
pos: |
8
+ offsets[1]
+ (archive1.num * 4)
#type: r_f_li_texture
type: c_f_li_tex_header
archive3shape1:
# Read header for archive 3 (faceline) index 1.
pos: 8 + offsets[3] + (archive3.num * sizeof<u4>) + 0x653
type: r_f_li_shape
archive1shape1:
# Read header for archive 1 (beard) index 1.
pos: 8 + offsets[0] + (archive0.num * sizeof<u4>) + 0x9
type: r_f_li_shape
# Default M hair.
archive8shape33:
# Read header for archive 8 (hair) index 33.
pos: 8 + offsets[8] + (archive8.num * sizeof<u4>) + 0x11F27
type: r_f_li_shape
types:
r_f_li_archive:
seq:
- id: num
type: u2
doc: Number of files in the archive.
- id: maxsize
type: u2
doc: Size of the biggest entry in the archive.
- id: offset
type: u4
doc: Offsets of each part in the archive.
repeat: expr
repeat-expr: num + 1
#instances:
# texture0:
# pos: _io.pos + offset[0] # Read each part entry at the offset
# type: r_f_li_texture
c_f_li_tex_header:
doc: |
Image width and height are calculated by
getting the next power of two. In JS:
const nextPow2 = x => x <= 1 ? 1 : 1 << (32 - Math.clz32(x - 1));
seq:
- id: image_w
type: u2
- id: image_h
type: u2
- id: m_mipmap_size
type: u1
- id: m_format
type: u1
- id: m_wrap_s
type: u1
- id: m_wrap_t
type: u1
r_f_li_texture:
seq:
- id: format
type: u1
- id: alpha
type: u1
- id: width
type: u2
- id: height
type: u2
- id: wrap_s
type: u1
- id: wrap_t
type: u1
- id: index_texture
type: u1
- id: color_format
type: u1
- id: num_colors
type: u2
- id: palette_ofs
type: u4
- id: enable_lod
type: u1
- id: enable_edge_lod
type: u1
- id: enable_bias_clamp
type: u1
- id: enable_max_aniso
type: u1
- id: min_filt
type: u1
- id: mag_filt
type: u1
- id: min_lod
type: s1
- id: max_lod
type: s1
- id: mipmap_level
type: u1
- id: reserved
type: s1
- id: lod_bias
type: s2
- id: image_ofs
type: u4
r_f_li_shape:
doc: |
NOTE: Untyped. Custom type.
Real data is loaded from "res" in RFLiInitShapeRes
seq:
- id: identifier
type: str
size: 4
encoding: ascii
doc: |
4-byte identifier, unused by RFL.
'nose', 'frhd', 'face', 'hair', 'cap_', 'berd', 'nsln', 'mask', 'glas'
https://github.com/SMGCommunity/Petari/blob/6b6a7635d3ab985a5866be9ae4db09d52d678f6c/src/RVLFaceLib/RFL_Model.c#L835
# Faceline transform fields.
- id: nose_trans
type: f4
repeat: expr
repeat-expr: 3
if: is_faceline
- id: beard_trans
type: f4
repeat: expr
repeat-expr: 3
if: is_faceline
- id: hair_trans
type: f4
repeat: expr
repeat-expr: 3
if: is_faceline
# Positions.
- id: num_vtx_pos
type: u2
- id: vtx_pos
#type: vec3_s16
type: u2
repeat: expr
#repeat-expr: num_vtx_pos
repeat-expr: num_vtx_pos * 3
# normals
- id: num_vtx_nrm
type: u2
- id: vtx_nrm
type: u2
repeat: expr
repeat-expr: num_vtx_nrm * 3
# texcoords (unless skip_txc)
- id: num_vtx_txc
type: u2
if: not skip_txc
- id: vtx_txc
type: u2
repeat: expr
repeat-expr: num_vtx_txc * 2
if: not skip_txc
# primitives
- id: prim_count
type: u1
- id: primitives
type: primitive
repeat: expr
repeat-expr: prim_count
instances:
# NOTE: RFL does not use the identifier.
# Should use RFLArcID instead.
skip_txc:
value: |
identifier == "frhd" or
identifier == "hair" or
identifier == "berd" or
identifier == "nose"
is_faceline:
value: identifier == "face"
# one primitive entry in the DL
primitive:
seq:
- id: vtx_count
type: u1
- id: prim_type
type: u1
enum: g_x_primitive
- id: vertices
type: vertex
repeat: expr
repeat-expr: vtx_count
# one vertex‐index tuple
vertex:
seq:
- id: pos_idx
type: u1
- id: nrm_idx
type: u1
- id: tex_idx
type: u1
if: not _parent._parent.skip_txc # match the r_f_li_shape skip_txc
enums:
g_x_primitive:
0xB8: points
0xA8: lines
0xB0: linestrip
0x90: triangles
0x98: trianglestrip
0xA0: trianglefan
0x80: quads
rfl_i_arc_id:
0: beard
1: eye
2: eyebrow
3: faceline
4: face_tex
5: fore_head
6: glass
7: glass_tex
8: hair
9: mask
10: mole
11: mouth
12: mustache
13: nose
14: nline
15: nline_tex
16: cap
17: cap_tex
18: max
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="importmap">
{
"imports": {
"kaitai-struct": "https://esm.sh/[email protected]",
"three": "https://esm.sh/[email protected]",
"three/": "https://esm.sh/[email protected]/"
}
}
</script>
<script type="module">
// Export modules to window.
/*
import * as THREE from 'three';
window.THREE = THREE;
globalThis.THREE = THREE;
*/
import { KaitaiStream } from 'kaitai-struct';
window.KaitaiStream = KaitaiStream; globalThis.KaitaiStream = KaitaiStream;
</script>
<!--<script type="module" src="KaitaiStream.min.js"></script>
<script type="module" src="RFLResource.js"></script>-->
<script type="module" src="rfl-resource.js"></script>
<style>
/* The semi-transparent, resizable overlay */
#shape-browser-overlay {
position: absolute;
top: 5%;
left: 5%;
width: 28%;
height: 90%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
overflow-y: auto;
padding: 1rem;
resize: both;
box-sizing: border-box;
z-index: 1000;
/* Use native system font. */
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
display: flex;
flex-direction: column;
}
/* Header and control panel */
#shape-browser-header {
margin-bottom: 1rem;
}
#shape-browser-controls {
margin-top: 0.5rem;
display: flex;
gap: 0.5rem;
align-items: center;
}
#shape-browser-controls label {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.9rem;
}
/* Remove bullets from the list */
#shape-browser-archive-list {
list-style: none;
padding-left: 0;
flex-grow: 1;
}
/* Each archive <details> */
#shape-browser-archive-list details {
margin-bottom: 0.5rem;
}
#shape-browser-archive-list summary {
cursor: pointer;
font-weight: bold;
font-size: 1rem;
padding: 0.25rem 0;
}
/* Each file entry */
.shape-file-entry {
font-size: 0.9rem;
cursor: pointer;
}
.shape-file-entry:hover {
background-color: rgba(255, 255, 255, 0.1);
}
/* Highlight the currently selected file */
.shape-file-entry.selected {
background-color: rgba(0, 128, 255, 0.3);
}
body {
margin: 0;
}
</style>
</head>
<body>
<div id="shape-browser-overlay">
<div id="shape-browser-header">
<div id="shape-browser-controls">
<!-- Wireframe toggle -->
<label>
<input type="checkbox" id="control-wireframe">
Wireframe
</label>
<!-- Rotate toggle -->
<label>
<input type="checkbox" id="control-rotate">
Auto-Rotate
</label>
<!-- Texture test button -->
<button id="control-add-texture">test UVs</button>
<input type="file" id="control-texture-input" accept="image/*" style="display: none;">
</div>
</div>
<!-- Archive list will go here -->
<ul id="shape-browser-archive-list"></ul>
</div>
</body>
</html>
// @ts-check
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
// import SPECTOR from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';
// const spector = new SPECTOR.Spector();
import { KaitaiStream } from 'kaitai-struct';
import * as RFLResourceImport from './RFLResource.js';
// Import as UMD or ESM.
let RFLResource = /** @type {*} */ (globalThis).RFLResource;
RFLResource = (!RFLResource) ? RFLResourceImport : RFLResource;
/**
* {@link https://github.com/SMGCommunity/Petari/blob/98fe1905624e3b499869eee6b74b12fcaf94f38a/libs/RVLFaceLib/include/RFLi_Types.h#L23C1-L43C13}
* @enum {number}
*/
const RFLiArcID = {
Beard: 0,
Eye: 1,
Eyebrow: 2,
Faceline: 3,
FaceTex: 4,
ForeHead: 5,
Glass: 6,
GlassTex: 7,
Hair: 8,
Mask: 9,
Mole: 10,
Mouth: 11,
Mustache: 12,
Nose: 13,
Nline: 14,
NlineTex: 15,
Cap: 16,
CapTex: 17,
Max: 18
};
/**
* Gets an offset of an individual file/element/part within the archive.
* @param {RFLResource} res
* @param {RFLiArcID} arcID
* @param {number} index
* @returns {number} Absolute offset of file within the archive.
*/
function getOffsetArchiveFile(res, arcID, index) {
/** Initial position. */
const _pos = res._io.pos;
/** Offsets begin at this location. */
const initialOffset = 8;
/** Offset of the beginning of the archive. */
const archiveOffset = res.offsets[arcID];
console.assert(archiveOffset);
res._io.seek(/* initialOffset + */archiveOffset); // Seek and read archive.
/** Construct archive to get its offsets. */
const archive = new RFLResource.RFLiArchive(res._io, res, res._root);
console.assert(archive.num > 0);
/** Location after the archive's file offsets. Value = num files * sizeof(u32). */
const filesOffset = archive.num * 4;
/** Individual file's offset. */
const fileOffset = archive.offset[index];
console.assert(typeof fileOffset === 'number');
/** Total offset of file. */
const finalOffset = initialOffset + archiveOffset + filesOffset + fileOffset;
res._io.seek(_pos); // Seek to initial position before returning.
return finalOffset;
}
/**
* @param {RFLResource} res
* @param {RFLiArcID} arcID
* @param {number} index
* @returns {typeof RFLResource.RFLiShape}
* @todo Makes use of types VIA KAITAI: RFLResource, RFLiShape (custom)
*/
function getResourceShape(res, arcID, index) {
/** Initial position. */
const _pos = res._io.pos;
const finalOffset = getOffsetArchiveFile(res, arcID, index);
/** Seek to the offset of the file. */
res._io.seek(finalOffset);
/** Read the shape at the offset. */
const shape = new RFLResource.RFLiShape(res._io, this, res._root);
res._io.seek(_pos); // Seek to initial position before returning.
return shape;
}
// ==> LOADING <==
/** @type {Object<RFLiArcID, string>} */
const RFLiArcIDShapesAndNames = {
[RFLiArcID.Faceline]: 'Faceline',
[RFLiArcID.ForeHead]: 'ForeHead',
[RFLiArcID.Glass]: 'Glass',
[RFLiArcID.Hair]: 'Hair',
[RFLiArcID.Mask]: 'Mask',
[RFLiArcID.Nose]: 'Nose',
[RFLiArcID.Nline]: 'Nline',
[RFLiArcID.Cap]: 'Cap'
};
/** List of RFLiArcIDs that contain shapes. */
const shapeArchiveIds = Object.keys(RFLiArcIDShapesAndNames)
.map(key => Number(key));
/**
* Helper: returns how many files are in archive `arcID`.
* @param {RFLResource} resource
* @param {number} arcID
* @returns {number}
*/
function getFileCountInArchive(resource, arcID) {
const archiveOffset = resource.offsets[arcID];
// Seek to archive header
const savedPosition = resource._io.pos;
resource._io.seek(archiveOffset);
const archiveHeader = new RFLResource.RFLiArchive(resource._io, resource, resource._root);
resource._io.seek(savedPosition);
return archiveHeader.num;
}
/**
* Helper: returns the byte size of file `fileIndex` within archive `arcID`.
* @param {RFLResource} resource
* @param {number} arcID
* @param {number} fileIndex
* @returns {number}
*/
function getFileSizeInArchive(resource, arcID, fileIndex) {
const archiveOffset = resource.offsets[arcID];
// We need the offset of this file and the offset of the next file
const savedPosition = resource._io.pos;
resource._io.seek(archiveOffset);
const archiveHeader = new RFLResource.RFLiArchive(resource._io, resource, resource._root);
const offsetsArray = archiveHeader.offset; // length = num+1
let startOffset = offsetsArray[fileIndex];
let endOffset;
if (fileIndex < offsetsArray.length - 1) {
endOffset = offsetsArray[fileIndex + 1];
} else {
// Last file: size is archiveHeader.maxsize or subtract from fileSize
endOffset = offsetsArray[fileIndex] + archiveHeader.maxsize;
}
resource._io.seek(savedPosition);
return endOffset - startOffset;
}
/**
* Populate the <ul> with all archives and files.
* @param {RFLResource} resource
*/
function populateShapeArchiveList(resource) {
const archiveListElement = document.getElementById('shape-browser-archive-list');
archiveListElement.innerHTML = ''; // Clear any existing entries
// For all shape archive IDs...
for (const arcID of shapeArchiveIds) {
const archiveName = RFLiArcIDShapesAndNames[arcID];
const fileCount = getFileCountInArchive(resource, arcID);
// Create <details> with a <summary>
const detailsElement = document.createElement('details');
const summaryElement = document.createElement('summary');
summaryElement.textContent = `ID ${arcID}: ${archiveName} (${fileCount} files)`;
detailsElement.appendChild(summaryElement);
// Inner list of files
const filesListElement = document.createElement('ul');
for (let fileIndex = 0; fileIndex < fileCount; fileIndex++) {
const fileSize = getFileSizeInArchive(resource, arcID, fileIndex);
const fileEntryElement = document.createElement('li');
fileEntryElement.classList.add('shape-file-entry');
// e.g. "Hair 33 (2048 bytes)"
fileEntryElement.textContent =
`${archiveName} ${fileIndex} (${fileSize} bytes)`;
// On click: load & render shape, update hash, highlight
fileEntryElement.addEventListener('click', () => {
// Remove old “selected” class
document.querySelectorAll('.shape-file-entry.selected')
.forEach(el => el.classList.remove('selected'));
// Add to this one
fileEntryElement.classList.add('selected');
// Deep-link via hash
location.hash = `#arc=${arcID}&file=${fileIndex}`;
// Load and display
const shape = getResourceShape(resource, arcID, fileIndex);
const mesh = rflShapeToMesh(shape);
if (mesh) {
applyWireframeSetting(mesh);
switchMeshInScene(mesh);
}
});
filesListElement.appendChild(fileEntryElement);
}
detailsElement.appendChild(filesListElement);
const listItemElement = document.createElement('li');
listItemElement.appendChild(detailsElement);
archiveListElement.appendChild(listItemElement);
}
}
/**
* Reads location.hash and, if present, auto-selects that shape.
* @param {RFLResource} resource
*/
function applyDeepLinkSelection(resource) {
const hash = location.hash.slice(1);
const params = new URLSearchParams(hash);
const arcID = Number(params.get('arc'));
const fileIndex = Number(params.get('file'));
if (shapeArchiveIds.includes(arcID) && !isNaN(fileIndex)) {
// Find the matching <li> element and click it
const archiveName = RFLiArcIDShapesAndNames[arcID];
const selector = `.shape-file-entry`;
document.querySelectorAll(selector).forEach((el) => {
if (el.textContent.startsWith(`${archiveName} ${fileIndex} `)) {
el.click();
}
});
}
}
/** Toggles wireframe mode on the current mesh’s material. */
function applyWireframeSetting(mesh) {
if (mesh && mesh.material instanceof THREE.Material) {
mesh.material.wireframe = document.getElementById('control-wireframe').checked;
}
}
/** Adds event listeners for the control panel. */
function initializeControlPanel(resource) {
// Wireframe toggle
document.getElementById('control-wireframe')
.addEventListener('change', () => {
if (currentMesh) {
applyWireframeSetting(currentMesh);
}
});
// Auto-rotate toggle
document.getElementById('control-rotate')
.addEventListener('change', (event) => {
if (orbitControls) {
orbitControls.autoRotate = event.target.checked;
}
});
// “Add Texture” button
const textureInputElement = document.getElementById('control-texture-input');
document.getElementById('control-add-texture')
.addEventListener('click', () => {
textureInputElement.click();
});
// Handle file selection
textureInputElement.addEventListener('change', () => {
const file = textureInputElement.files[0];
if (!file) {
return;
}
if (!currentMesh.geometry.attributes.uv) {
alert('This mesh has no UV coordinates to apply a texture.');
return;
}
const reader = new FileReader();
reader.onload = () => {
const texture = new THREE.TextureLoader().load(reader.result);
currentMesh.material.map = texture;
currentMesh.material.needsUpdate = true;
};
reader.readAsDataURL(file);
});
}
// ==> INDICES <==
/**
* @param {Array<typeof RFLResource.Primitive>} primitives
* @returns {number} The number of indices based on primitives.
*/
function getIndexCountFromPrimitives(primitives) {
let total = 0;
for (const prim of primitives) {
const numVertices = prim.vertices.length;
switch (prim.primType) {
case RFLResource.GXPrimitive.TRIANGLES:
total += Math.floor(numVertices / 3) * 3;
break;
case RFLResource.GXPrimitive.TRIANGLESTRIP:
case RFLResource.GXPrimitive.TRIANGLEFAN:
total += Math.max(0, numVertices - 2) * 3;
break;
case RFLResource.GXPrimitive.QUADS:
total += Math.floor(numVertices / 4) * 6;
break;
default:
break; // skip lines/points
}
}
return total;
}
/**
* Build a flat Uint16Array of triangle indices from RFLiShape.primitives.
* @param {Array<typeof RFLResource.Primitive>} primitives -
* Each Primitive must have:
* - primType (one of GXPrimitive.*)
* - vertices: Array of { posIdx: number, … }
* @param {Uint16Array} indices - List of indices to populate.
* @param {number} offset - Offset to begin in `indices` array.
* @returns {Uint16Array}
*/
function buildToIndexBuffer(primitives, indices, offset = 0) {
/** Current offset in the output array. */
let i = offset;
for (const prim of primitives) {
// Use just indices for positions.
const idx = /** @type {Array<{posIdx: number}>} */
(prim.vertices).map(v => v.posIdx);
switch (prim.primType) {
// Reverse winding when writing indices.
case RFLResource.GXPrimitive.TRIANGLES:
// every group of 3 is one triangle
for (let j = 0; j + 2 < idx.length; j += 3) {
indices[i] = idx[j + 2];
indices[i + 1] = idx[j + 1];
indices[i + 2] = idx[j];
i += 3;
}
break;
case RFLResource.GXPrimitive.TRIANGLESTRIP:
// strip: (0,1,2), (2,1,3), (2,3,4)
for (let j = 0; j + 2 < idx.length; j++) {
if (j % 2 === 0) {
indices[i] = idx[j + 2];
indices[i + 1] = idx[j + 1];
indices[i + 2] = idx[j];
} else {
// flip winding on odd tris
indices[i] = idx[j + 1];
indices[i + 1] = idx[j + 2];
indices[i + 2] = idx[j];
}
i += 3;
}
break;
case RFLResource.GXPrimitive.TRIANGLEFAN:
// fan: (0,1,2), (0,2,3), (0,3,4)
for (let j = 1; j + 1 < idx.length; j++) {
indices[i] = idx[j + 1];
indices[i + 1] = idx[j];
indices[i + 2] = idx[0];
i += 3;
}
break;
case RFLResource.GXPrimitive.QUADS:
// quads: split each group of 4 into two tris
for (let j = 0; j + 3 < idx.length; j += 4) {
// triangle A: 0,1,2
indices[i] = idx[j + 2];
indices[i + 1] = idx[j + 1];
indices[i + 2] = idx[j];
// triangle B: 0,2,3
indices[i + 3] = idx[j + 3];
indices[i + 4] = idx[j + 2];
indices[i + 5] = idx[j];
i += 6;
}
break;
default:
// lines, points, strips-of-lines: unexpected, assert
console.assert(false,
`Unexpected primitive type: ${prim.primType} (${RFLResource.GXPrimitive[prim.primType]})`);
break;
}
}
return new Uint16Array(indices);
}
/**
* Build de-indexed Int16 buffers from a RFLiShape.
* Each triangle emits 3 vertices in sequence. We allocate once,
* then copy raw s16 data into each attribute array.
* @param {typeof RFLResource.RFLiShape} shape -
* The Kaitai-parsed shape, with:
* - shape.vtxPos: Int16Array of [x,y,z,x,y,z,…]
* - shape.vtxNrm: Int16Array of [nx,ny,nz,…]
* - shape.vtxTxc: (optional) Int16Array of [u,v,u,v,…]
* - shape.primitives: Array of GX DL primitives
* @returns {{
* position: Int16Array, // length = triCount * 3 coords
* normal: Int16Array, // same length as positions
* texcoord: Int16Array|undefined, // length = triCount * 2 coords
* index: Uint16Array // sequential 0..N-1
* }}
* @todo REVIEW and REPLACE ?????
*/
function buildIndexedAttributesFromShape(shape) {
// 1) Count total output vertices (triangles × 3)
let totalVerts = 0;
for (const prim of shape.primitives) {
const count = prim.vertices.length;
switch (prim.primType) {
case RFLResource.GXPrimitive.TRIANGLES:
totalVerts += Math.floor(count / 3) * 3;
break;
case RFLResource.GXPrimitive.TRIANGLESTRIP:
case RFLResource.GXPrimitive.TRIANGLEFAN:
totalVerts += Math.max(0, count - 2) * 3;
break;
case RFLResource.GXPrimitive.QUADS:
totalVerts += Math.floor(count / 4) * 2 * 3;
break;
default:
// skip lines/points
break;
}
}
// 2) Pre-allocate output buffers
const position = new Int16Array(totalVerts * 3);
const normal = new Int16Array(totalVerts * 3);
const hasUVs = Array.isArray(shape.vtxTxc) && shape.vtxTxc.length > 0;
const texcoord = hasUVs ? new Int16Array(totalVerts * 2) : undefined;
const index = new Uint16Array(totalVerts);
// 3) Walk primitives and copy data
let vertexCursor = 0; // counts output vertices 0..totalVerts-1
let outPosOff = 0; // positions[offset..offset+2]
let outNrmOff = 0; // normals[offset..offset+2]
let outUvOff = 0; // uvs[offset..offset+1]
const rawPos = shape.vtxPos; // Int16Array
const rawNrm = shape.vtxNrm; // Int16Array
const rawUvs = shape.vtxTxc; // maybe Int16Array
// helper to emit one vertex by raw index triple
function emitVertex(posIndex, nrmIndex, uvIndex) {
// copy XYZ
position[outPosOff] = rawPos[posIndex * 3];
position[outPosOff + 1] = rawPos[posIndex * 3 + 1];
position[outPosOff + 2] = rawPos[posIndex * 3 + 2];
// copy normals
normal[outNrmOff] = rawNrm[nrmIndex * 3];
normal[outNrmOff + 1] = rawNrm[nrmIndex * 3 + 1];
normal[outNrmOff + 2] = rawNrm[nrmIndex * 3 + 2];
// copy UV if present
if (texcoord) {
texcoord[outUvOff] = rawUvs[uvIndex * 2];
texcoord[outUvOff + 1] = rawUvs[uvIndex * 2 + 1];
}
// set the index for this new vertex
index[vertexCursor] = vertexCursor;
// advance all cursors
vertexCursor += 1;
outPosOff += 3;
outNrmOff += 3;
if (hasUVs) outUvOff += 2;
}
// 4) Expand each primitive to triangles
for (const prim of shape.primitives) {
const verts = prim.vertices;
switch (prim.primType) {
case RFLResource.GXPrimitive.TRIANGLES:
// emit every group of 3, reversed for CCW
for (let i = 0; i + 2 < verts.length; i += 3) {
emitVertex(verts[i + 2].posIdx, verts[i + 2].nrmIdx, verts[i + 2].texIdx);
emitVertex(verts[i + 1].posIdx, verts[i + 1].nrmIdx, verts[i + 1].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
}
break;
case RFLResource.GXPrimitive.TRIANGLESTRIP:
// (0,1,2),(2,1,3),(2,3,4),… with winding flip
for (let i = 0; i + 2 < verts.length; i++) {
if (i % 2 === 0) {
emitVertex(verts[i + 2].posIdx, verts[i + 2].nrmIdx, verts[i + 2].texIdx);
emitVertex(verts[i + 1].posIdx, verts[i + 1].nrmIdx, verts[i + 1].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
} else {
emitVertex(verts[i + 1].posIdx, verts[i + 1].nrmIdx, verts[i + 1].texIdx);
emitVertex(verts[i + 2].posIdx, verts[i + 2].nrmIdx, verts[i + 2].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
}
}
break;
case RFLResource.GXPrimitive.TRIANGLEFAN:
// (0,1,2),(0,2,3),(0,3,4),…
for (let i = 1; i + 1 < verts.length; i++) {
emitVertex(verts[i + 1].posIdx, verts[i + 1].nrmIdx, verts[i + 1].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
emitVertex(verts[0].posIdx, verts[0].nrmIdx, verts[0].texIdx);
}
break;
case RFLResource.GXPrimitive.QUADS:
// [0,1,2,3] → (2,1,0),(3,2,0)
for (let i = 0; i + 3 < verts.length; i += 4) {
emitVertex(verts[i + 2].posIdx, verts[i + 2].nrmIdx, verts[i + 2].texIdx);
emitVertex(verts[i + 1].posIdx, verts[i + 1].nrmIdx, verts[i + 1].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
emitVertex(verts[i + 3].posIdx, verts[i + 3].nrmIdx, verts[i + 3].texIdx);
emitVertex(verts[i + 2].posIdx, verts[i + 2].nrmIdx, verts[i + 2].texIdx);
emitVertex(verts[i].posIdx, verts[i].nrmIdx, verts[i].texIdx);
}
break;
default:
// skip lines & points
break;
}
}
return {
position,
normal,
texcoord,
index
};
}
// ==> MY CODE ? <==
/**
* Main entrypoint.
* @param {string} resPath - Path to RFL_Res.dat.
*/
async function main(resPath = 'RFL_Res.dat') {
// Fetch resource from response.
const response = await fetch(resPath);
if (!response.ok) {
const err2 = new Error(`failed to fetch resource, HTTP status = ${response.status}`);
throw err2;
}
const buffer = await response.arrayBuffer();
/** Construct RFLResource Kaitai type. Uses new KaitaiStream from ArrayBuffer. */
const resource = new RFLResource(new KaitaiStream(buffer, null));
// Create Three.js scene.
if (!scene) {
// spector.displayUI();
initializeThree();
}
populateShapeArchiveList(resource);
initializeControlPanel(resource);
applyDeepLinkSelection(resource);
// get a shape that can be drawn
// const shape = res.archive8shape33;
const shape = getResourceShape(resource, RFLiArcID.Hair, 33);
// Add mesh to the scene.
const mesh = rflShapeToMesh(shape);
console.assert(mesh !== null);
switchMeshInScene(mesh);
}
/** @type {THREE.Scene} */
let scene;
/** @type {import('three/addons/controls/OrbitControls.js')} */
let orbitControls = null;
/** @type {THREE.Mesh|null} */
let currentMesh = null;
/** Simple triangle (apparently). */
const sample = new Int16Array([
256, 0, 0,
0, 256, 0,
0, 0, 256
]);
/**
* Creates a mesh from RFL shape data.
* @param {Object} shape - RFL shape to add.
* @returns {THREE.Mesh|null} The Three.js mesh, or null if an invalid shape is passed in.
*/
function rflShapeToMesh(shape) {
// function addMeshToScene(positioni16 = sample, normali16, texcoordi16, indexu16) {
// The position count must be valid before continuing.
if (!shape || !shape.numVtxPos) {
return null;
}
/*
// Create arrays for all attributes.
const position = new Int16Array(shape.vtxPos);
const normal = new Int16Array(shape.vtxNrm);
// UVs/texCoord are optional.
const texcoord = shape.vtxTxc ? new Int16Array(shape.vtxTxc) : undefined;
// Create indices.
const indexCount = getIndexCountFromPrimitives(shape.primitives);
const index = new Uint16Array(indexCount);
buildToIndexBuffer(shape.primitives, index);
*/
const { position, normal, texcoord, index } = buildIndexedAttributesFromShape(shape);
console.assert(position && position.length);
/*
// Convert to Float32 and scale (assume 1 unit = 256)
const positionF32 = new Float32Array(position.length);
const normalF32 = new Float32Array(normal.length);
for (let i = 0; i < position.length; i++) {
positionF32[i] = position[i] / 256.0;
}
for (let i = 0; i < normal.length; i++) {
normalF32[i] = normal[i] / 256.0;
}
*/
/** Create BufferGeometry. Normalize all attributes. */
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',
new THREE.BufferAttribute(position, 3, true));
// new THREE.BufferAttribute(positionF32, 3));
if (normal) {
geometry.setAttribute('normal',
new THREE.BufferAttribute(normal, 3, true));
// new THREE.BufferAttribute(normalF32, 3));
}
if (texcoord) {
geometry.setAttribute('uv',
new THREE.BufferAttribute(texcoord, 2, true));
}
// if (index) {
geometry.setIndex(new THREE.BufferAttribute(index, 1));
// geometry.computeVertexNormals();
/** Create material and THREE.Mesh. */
const mesh = new THREE.Mesh(geometry,
new THREE.MeshPhongMaterial({
color: 0xffffff,
// wireframe: true,
// flatShading: true,
// side: THREE.BackSide
}));
return mesh;
}
/**
* Adds mesh to the global {@link scene} and disposes the previous one.
* @param {THREE.Mesh} mesh - New mesh to add to scene.
*/
function switchMeshInScene(mesh) {
// Remove current mesh before adding new one to scene.
if (currentMesh) {
scene.remove(currentMesh);
// Dispose the currentMesh.
if (currentMesh.material instanceof THREE.Material &&
!Array.isArray(currentMesh.material)) {
currentMesh.material.dispose();
/** @type {THREE.MeshBasicMaterial} */ (currentMesh.material).map?.dispose();
} else {
console.assert(false, 'expected currentMesh.material to be a single non-array THREE.Material');
}
}
scene.add(mesh); // Add mesh to scene.
currentMesh = mesh;
}
/**
* Entrypoint that will create new THREE.Scene, renderer, and lights.
* @param {HTMLElement} canvasTarget - Where to add the renderer's canvas.
*/
function initializeThree(canvasTarget = document.body) {
scene = new THREE.Scene(); // Create scene.
scene.background = new THREE.Color(0x202020); // Set scene background.
/** Scene camera. */
const camera = new THREE.PerspectiveCamera(45,
window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0.46, 0.75, 1.2);
// Add lighting to scene for Three.js materials.
const intensity = Number(THREE.REVISION) >= 155 ? Math.PI : 1.0;
const ambientLight = new THREE.AmbientLight(new THREE.Color(0.73, 0.73, 0.73), intensity);
const directionalLight = new THREE.DirectionalLight(
new THREE.Color(0.60, 0.60, 0.60), intensity);
directionalLight.position.set(-0.455, 0.348, 0.5);
scene.add(ambientLight, directionalLight);
/** Create THREE.WebGLRenderer. */
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
// Append to DOM.
canvasTarget.appendChild(renderer.domElement);
// Create OrbitControls.
// @ts-expect-error -- 'PerspectiveCamera' not assignable to 'Camera', 'matrixWorldInverse.copy' incompatible
orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.autoRotateSpeed = 8;
orbitControls.target.set(-0.08, 0.15, -0.09);
// Set resize handler.
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
/** requestAnimationFrame handler function. */
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
animate();
}
main(); // Run async entrypoint.
// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['kaitai-struct/KaitaiStream'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('kaitai-struct/KaitaiStream'));
} else {
root.RFLResource = factory(root.KaitaiStream);
}
}(typeof self !== 'undefined' ? self : this, function (KaitaiStream) {
var RFLResource = (function() {
RFLResource.GXPrimitive = Object.freeze({
QUADS: 128,
TRIANGLES: 144,
TRIANGLESTRIP: 152,
TRIANGLEFAN: 160,
LINES: 168,
LINESTRIP: 176,
POINTS: 184,
128: "QUADS",
144: "TRIANGLES",
152: "TRIANGLESTRIP",
160: "TRIANGLEFAN",
168: "LINES",
176: "LINESTRIP",
184: "POINTS",
});
RFLResource.RflIArcId = Object.freeze({
BEARD: 0,
EYE: 1,
EYEBROW: 2,
FACELINE: 3,
FACE_TEX: 4,
FORE_HEAD: 5,
GLASS: 6,
GLASS_TEX: 7,
HAIR: 8,
MASK: 9,
MOLE: 10,
MOUTH: 11,
MUSTACHE: 12,
NOSE: 13,
NLINE: 14,
NLINE_TEX: 15,
CAP: 16,
CAP_TEX: 17,
MAX: 18,
0: "BEARD",
1: "EYE",
2: "EYEBROW",
3: "FACELINE",
4: "FACE_TEX",
5: "FORE_HEAD",
6: "GLASS",
7: "GLASS_TEX",
8: "HAIR",
9: "MASK",
10: "MOLE",
11: "MOUTH",
12: "MUSTACHE",
13: "NOSE",
14: "NLINE",
15: "NLINE_TEX",
16: "CAP",
17: "CAP_TEX",
18: "MAX",
});
function RFLResource(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
RFLResource.prototype._read = function() {
this.totalArchiveCount = this._io.readU2be();
this.version = this._io.readU2be();
this.offsets = [];
for (var i = 0; i < this.totalArchiveCount; i++) {
this.offsets.push(this._io.readU4be());
}
}
var RFLiTexture = RFLResource.RFLiTexture = (function() {
function RFLiTexture(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
RFLiTexture.prototype._read = function() {
this.format = this._io.readU1();
this.alpha = this._io.readU1();
this.width = this._io.readU2be();
this.height = this._io.readU2be();
this.wrapS = this._io.readU1();
this.wrapT = this._io.readU1();
this.indexTexture = this._io.readU1();
this.colorFormat = this._io.readU1();
this.numColors = this._io.readU2be();
this.paletteOfs = this._io.readU4be();
this.enableLod = this._io.readU1();
this.enableEdgeLod = this._io.readU1();
this.enableBiasClamp = this._io.readU1();
this.enableMaxAniso = this._io.readU1();
this.minFilt = this._io.readU1();
this.magFilt = this._io.readU1();
this.minLod = this._io.readS1();
this.maxLod = this._io.readS1();
this.mipmapLevel = this._io.readU1();
this.reserved = this._io.readS1();
this.lodBias = this._io.readS2be();
this.imageOfs = this._io.readU4be();
}
return RFLiTexture;
})();
var Vertex = RFLResource.Vertex = (function() {
function Vertex(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
Vertex.prototype._read = function() {
this.posIdx = this._io.readU1();
this.nrmIdx = this._io.readU1();
if (!(this._parent._parent.skipTxc)) {
this.texIdx = this._io.readU1();
}
}
return Vertex;
})();
/**
* NOTE: Untyped. Custom type.
* Real data is loaded from "res" in RFLiInitShapeRes
*/
var RFLiShape = RFLResource.RFLiShape = (function() {
function RFLiShape(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
RFLiShape.prototype._read = function() {
this.identifier = KaitaiStream.bytesToStr(this._io.readBytes(4), "ascii");
if (this.isFaceline) {
this.noseTrans = [];
for (var i = 0; i < 3; i++) {
this.noseTrans.push(this._io.readF4be());
}
}
if (this.isFaceline) {
this.beardTrans = [];
for (var i = 0; i < 3; i++) {
this.beardTrans.push(this._io.readF4be());
}
}
if (this.isFaceline) {
this.hairTrans = [];
for (var i = 0; i < 3; i++) {
this.hairTrans.push(this._io.readF4be());
}
}
this.numVtxPos = this._io.readU2be();
this.vtxPos = [];
for (var i = 0; i < (this.numVtxPos * 3); i++) {
this.vtxPos.push(this._io.readU2be());
}
this.numVtxNrm = this._io.readU2be();
this.vtxNrm = [];
for (var i = 0; i < (this.numVtxNrm * 3); i++) {
this.vtxNrm.push(this._io.readU2be());
}
if (!(this.skipTxc)) {
this.numVtxTxc = this._io.readU2be();
}
if (!(this.skipTxc)) {
this.vtxTxc = [];
for (var i = 0; i < (this.numVtxTxc * 2); i++) {
this.vtxTxc.push(this._io.readU2be());
}
}
this.primCount = this._io.readU1();
this.primitives = [];
for (var i = 0; i < this.primCount; i++) {
this.primitives.push(new Primitive(this._io, this, this._root));
}
}
Object.defineProperty(RFLiShape.prototype, 'skipTxc', {
get: function() {
if (this._m_skipTxc !== undefined)
return this._m_skipTxc;
this._m_skipTxc = ((this.identifier == "frhd") || (this.identifier == "hair") || (this.identifier == "berd") || (this.identifier == "nose")) ;
return this._m_skipTxc;
}
});
Object.defineProperty(RFLiShape.prototype, 'isFaceline', {
get: function() {
if (this._m_isFaceline !== undefined)
return this._m_isFaceline;
this._m_isFaceline = this.identifier == "face";
return this._m_isFaceline;
}
});
/**
* 4-byte identifier, unused by RFL.
* 'nose', 'frhd', 'face', 'hair', 'cap_', 'berd', 'nsln', 'mask', 'glas'
* https://github.com/SMGCommunity/Petari/blob/6b6a7635d3ab985a5866be9ae4db09d52d678f6c/src/RVLFaceLib/RFL_Model.c#L835
*/
return RFLiShape;
})();
var RFLiArchive = RFLResource.RFLiArchive = (function() {
function RFLiArchive(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
RFLiArchive.prototype._read = function() {
this.num = this._io.readU2be();
this.maxsize = this._io.readU2be();
this.offset = [];
for (var i = 0; i < (this.num + 1); i++) {
this.offset.push(this._io.readU4be());
}
}
/**
* Number of files in the archive.
*/
/**
* Size of the biggest entry in the archive.
*/
/**
* Offsets of each part in the archive.
*/
return RFLiArchive;
})();
/**
* Image width and height are calculated by
* getting the next power of two. In JS:
* const nextPow2 = x => x <= 1 ? 1 : 1 << (32 - Math.clz32(x - 1));
*/
var CFLiTexHeader = RFLResource.CFLiTexHeader = (function() {
function CFLiTexHeader(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
CFLiTexHeader.prototype._read = function() {
this.imageW = this._io.readU2be();
this.imageH = this._io.readU2be();
this.mMipmapSize = this._io.readU1();
this.mFormat = this._io.readU1();
this.mWrapS = this._io.readU1();
this.mWrapT = this._io.readU1();
}
return CFLiTexHeader;
})();
var Primitive = RFLResource.Primitive = (function() {
function Primitive(_io, _parent, _root) {
this._io = _io;
this._parent = _parent;
this._root = _root || this;
this._read();
}
Primitive.prototype._read = function() {
this.vtxCount = this._io.readU1();
this.primType = this._io.readU1();
this.vertices = [];
for (var i = 0; i < this.vtxCount; i++) {
this.vertices.push(new Vertex(this._io, this, this._root));
}
}
return Primitive;
})();
Object.defineProperty(RFLResource.prototype, 'archive4', {
get: function() {
if (this._m_archive4 !== undefined)
return this._m_archive4;
var _pos = this._io.pos;
this._io.seek(this.offsets[4]);
this._m_archive4 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive4;
}
});
Object.defineProperty(RFLResource.prototype, 'archive18', {
get: function() {
if (this._m_archive18 !== undefined)
return this._m_archive18;
var _pos = this._io.pos;
this._io.seek(this.offsets[18]);
this._m_archive18 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive18;
}
});
Object.defineProperty(RFLResource.prototype, 'archive9', {
get: function() {
if (this._m_archive9 !== undefined)
return this._m_archive9;
var _pos = this._io.pos;
this._io.seek(this.offsets[9]);
this._m_archive9 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive9;
}
});
Object.defineProperty(RFLResource.prototype, 'archive16', {
get: function() {
if (this._m_archive16 !== undefined)
return this._m_archive16;
var _pos = this._io.pos;
this._io.seek(this.offsets[16]);
this._m_archive16 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive16;
}
});
Object.defineProperty(RFLResource.prototype, 'archive3shape1', {
get: function() {
if (this._m_archive3shape1 !== undefined)
return this._m_archive3shape1;
var _pos = this._io.pos;
this._io.seek((((8 + this.offsets[3]) + (this.archive3.num * 4)) + 1619));
this._m_archive3shape1 = new RFLiShape(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive3shape1;
}
});
Object.defineProperty(RFLResource.prototype, 'archive8shape33', {
get: function() {
if (this._m_archive8shape33 !== undefined)
return this._m_archive8shape33;
var _pos = this._io.pos;
this._io.seek((((8 + this.offsets[8]) + (this.archive8.num * 4)) + 73511));
this._m_archive8shape33 = new RFLiShape(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive8shape33;
}
});
Object.defineProperty(RFLResource.prototype, 'archive1shape1', {
get: function() {
if (this._m_archive1shape1 !== undefined)
return this._m_archive1shape1;
var _pos = this._io.pos;
this._io.seek((((8 + this.offsets[0]) + (this.archive0.num * 4)) + 9));
this._m_archive1shape1 = new RFLiShape(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive1shape1;
}
});
Object.defineProperty(RFLResource.prototype, 'archive1', {
get: function() {
if (this._m_archive1 !== undefined)
return this._m_archive1;
var _pos = this._io.pos;
this._io.seek(this.offsets[1]);
this._m_archive1 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive1;
}
});
Object.defineProperty(RFLResource.prototype, 'archive6', {
get: function() {
if (this._m_archive6 !== undefined)
return this._m_archive6;
var _pos = this._io.pos;
this._io.seek(this.offsets[6]);
this._m_archive6 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive6;
}
});
Object.defineProperty(RFLResource.prototype, 'archive12', {
get: function() {
if (this._m_archive12 !== undefined)
return this._m_archive12;
var _pos = this._io.pos;
this._io.seek(this.offsets[12]);
this._m_archive12 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive12;
}
});
Object.defineProperty(RFLResource.prototype, 'archive0', {
get: function() {
if (this._m_archive0 !== undefined)
return this._m_archive0;
var _pos = this._io.pos;
this._io.seek(this.offsets[0]);
this._m_archive0 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive0;
}
});
Object.defineProperty(RFLResource.prototype, 'archive13', {
get: function() {
if (this._m_archive13 !== undefined)
return this._m_archive13;
var _pos = this._io.pos;
this._io.seek(this.offsets[13]);
this._m_archive13 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive13;
}
});
Object.defineProperty(RFLResource.prototype, 'archive10', {
get: function() {
if (this._m_archive10 !== undefined)
return this._m_archive10;
var _pos = this._io.pos;
this._io.seek(this.offsets[10]);
this._m_archive10 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive10;
}
});
Object.defineProperty(RFLResource.prototype, 'archive11', {
get: function() {
if (this._m_archive11 !== undefined)
return this._m_archive11;
var _pos = this._io.pos;
this._io.seek(this.offsets[11]);
this._m_archive11 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive11;
}
});
Object.defineProperty(RFLResource.prototype, 'archive8', {
get: function() {
if (this._m_archive8 !== undefined)
return this._m_archive8;
var _pos = this._io.pos;
this._io.seek(this.offsets[8]);
this._m_archive8 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive8;
}
});
Object.defineProperty(RFLResource.prototype, 'archive1texture0', {
get: function() {
if (this._m_archive1texture0 !== undefined)
return this._m_archive1texture0;
var _pos = this._io.pos;
this._io.seek(((8 + this.offsets[1]) + (this.archive1.num * 4)));
this._m_archive1texture0 = new CFLiTexHeader(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive1texture0;
}
});
Object.defineProperty(RFLResource.prototype, 'archive7', {
get: function() {
if (this._m_archive7 !== undefined)
return this._m_archive7;
var _pos = this._io.pos;
this._io.seek(this.offsets[7]);
this._m_archive7 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive7;
}
});
Object.defineProperty(RFLResource.prototype, 'archive19', {
get: function() {
if (this._m_archive19 !== undefined)
return this._m_archive19;
var _pos = this._io.pos;
this._io.seek(this.offsets[19]);
this._m_archive19 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive19;
}
});
Object.defineProperty(RFLResource.prototype, 'archive3', {
get: function() {
if (this._m_archive3 !== undefined)
return this._m_archive3;
var _pos = this._io.pos;
this._io.seek(this.offsets[3]);
this._m_archive3 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive3;
}
});
Object.defineProperty(RFLResource.prototype, 'archive17', {
get: function() {
if (this._m_archive17 !== undefined)
return this._m_archive17;
var _pos = this._io.pos;
this._io.seek(this.offsets[17]);
this._m_archive17 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive17;
}
});
Object.defineProperty(RFLResource.prototype, 'archive15', {
get: function() {
if (this._m_archive15 !== undefined)
return this._m_archive15;
var _pos = this._io.pos;
this._io.seek(this.offsets[15]);
this._m_archive15 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive15;
}
});
Object.defineProperty(RFLResource.prototype, 'archives', {
get: function() {
if (this._m_archives !== undefined)
return this._m_archives;
var _pos = this._io.pos;
this._io.seek(this.offsets[i]);
this._m_archives = [];
for (var i = 0; i < this.totalArchiveCount; i++) {
this._m_archives.push(new RFLiArchive(this._io, this, this._root));
}
this._io.seek(_pos);
return this._m_archives;
}
});
Object.defineProperty(RFLResource.prototype, 'archive5', {
get: function() {
if (this._m_archive5 !== undefined)
return this._m_archive5;
var _pos = this._io.pos;
this._io.seek(this.offsets[5]);
this._m_archive5 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive5;
}
});
Object.defineProperty(RFLResource.prototype, 'archive14', {
get: function() {
if (this._m_archive14 !== undefined)
return this._m_archive14;
var _pos = this._io.pos;
this._io.seek(this.offsets[14]);
this._m_archive14 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive14;
}
});
Object.defineProperty(RFLResource.prototype, 'archive2', {
get: function() {
if (this._m_archive2 !== undefined)
return this._m_archive2;
var _pos = this._io.pos;
this._io.seek(this.offsets[2]);
this._m_archive2 = new RFLiArchive(this._io, this, this._root);
this._io.seek(_pos);
return this._m_archive2;
}
});
/**
* Amount of total archives within the file.
* RFL = 18 (RFLiArcID_Max), NFL = 20 (?), CFL = 20 (CFLi_PARTS_ID_COUNT)
*/
/**
* Resource version. Set in RFL and CFL, only seen used in CFL debug mode.
* CFL bootloadDB2Res_: nn::dbg::detail::Printf("CFL: Cached Resource Version = 0x%04x\n");
* / Debug assert; "cfl_resource.cpp",0x19d,"%s", "loader->mVersion >= 0x509"
*/
/**
* Offsets for each part type.
*/
return RFLResource;
})();
return RFLResource;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment