Skip to content

Instantly share code, notes, and snippets.

@ariankordi
Last active April 24, 2026 21:44
Show Gist options
  • Select an option

  • Save ariankordi/f28cf53a0e46e2a034949f44eeea0265 to your computer and use it in GitHub Desktop.

Select an option

Save ariankordi/f28cf53a0e46e2a034949f44eeea0265 to your computer and use it in GitHub Desktop.
IQM shape parser in Fusion + Three.js viewer.
/// Array-only attribute conversion utilities.
public static class AttributeConverter
{
// NOTE: Only C++, JS, and #else implementations were tested.
/// Copy numComponents little-endian f32 values per record from src into dst.
public static void Float32LECopy(byte[] src, uint srcOff, float[]! dst, uint dstOff,
uint numComponents, uint srcStride, uint recordCount)
{
#if C || CPP
native {
for (int ri = 0; ri < recordCount; ri++) {
for (int ci = 0; ci < numComponents; ci++) {
const unsigned char *b = (const unsigned char *)src + srcOff + ri * srcStride + ci * 4;
unsigned bits = (unsigned)b[0] | ((unsigned)b[1] << 8) | ((unsigned)b[2] << 16) | ((unsigned)b[3] << 24);
dst[dstOff + ri * numComponents + ci] = *(float *)&bits;
}
}
}
#elif JS || TS
native {
// NOTE: Assumes a little-endian JavaScript engine.
const f32 = new Float32Array(src.buffer,
src.byteOffset, recordCount * numComponents);
for (let ri = 0; ri < recordCount; ri++) {
const base = (srcOff + ri * srcStride) >>> 2;
for (let ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] = f32[base + ci];
}
}
#elif PY
native {
import struct as _struct
for ri in range(recordCount):
vals = _struct.unpack_from(f"<{numComponents}f", src, srcOff + ri * srcStride)
for ci, v in enumerate(vals):
dst[dstOff + ri * numComponents + ci] = v
}
#elif CS
native {
for (int ri = 0; ri < recordCount; ri++)
for (int ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] =
System.Buffers.Binary.BinaryPrimitives.ReadSingleLittleEndian(
new System.ReadOnlySpan<byte>(src, (int)(srcOff + ri * srcStride + ci * 4), 4));
}
#elif JAVA
native {
java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(src).order(java.nio.ByteOrder.LITTLE_ENDIAN);
for (int ri = 0; ri < recordCount; ri++)
for (int ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] = buf.getFloat((int)(srcOff + ri * srcStride + ci * 4));
}
#else
for (uint ri = 0; ri < recordCount; ri++)
for (uint ci = 0; ci < numComponents; ci++)
{
int bi = srcOff + ri * srcStride + ci * 4;
uint bits = src[bi] | (src[bi + 1] << 8) | (src[bi + 2] << 16) | (src[bi + 3] << 24);
dst[dstOff + ri * numComponents + ci] = BitConverter.Int32BitsToSingle(bits);
// dst[dstOff + ri * numComponents + ci] = BitsToFloat(bits);
}
#endif
}
/// Copy numComponents big-endian f32 values per record from src into dst.
public static void Float32BECopy(byte[] src, uint srcOff, float[]! dst, uint dstOff,
uint numComponents, uint srcStride, uint recordCount)
{
#if C || CPP
native {
for (int ri = 0; ri < recordCount; ri++) {
for (int ci = 0; ci < numComponents; ci++) {
const unsigned char *b = (const unsigned char *)src + srcOff + ri * srcStride + ci * 4;
unsigned bits = ((unsigned)b[0] << 24) | ((unsigned)b[1] << 16) | ((unsigned)b[2] << 8) | (unsigned)b[3];
dst[dstOff + ri * numComponents + ci] = *(float *)&bits;
}
}
}
#elif JS || TS
native {
const dv = new DataView(src.buffer, src.byteOffset);
for (let ri = 0; ri < recordCount; ri++)
for (let ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] = dv.getFloat32(srcOff + ri * srcStride + ci * 4, false);
}
#elif PY
native {
import struct as _struct
for ri in range(recordCount):
vals = _struct.unpack_from(f">{numComponents}f", src, srcOff + ri * srcStride)
for ci, v in enumerate(vals):
dst[dstOff + ri * numComponents + ci] = v
}
#elif CS
native {
for (int ri = 0; ri < recordCount; ri++)
for (int ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] =
System.Buffers.Binary.BinaryPrimitives.ReadSingleBigEndian(
new System.ReadOnlySpan<byte>(src, (int)(srcOff + ri * srcStride + ci * 4), 4));
}
#elif JAVA
native {
java.nio.ByteBuffer buf = java.nio.ByteBuffer.wrap(src).order(java.nio.ByteOrder.BIG_ENDIAN);
for (int ri = 0; ri < recordCount; ri++)
for (int ci = 0; ci < numComponents; ci++)
dst[dstOff + ri * numComponents + ci] = buf.getFloat((int)(srcOff + ri * srcStride + ci * 4));
}
#else
for (uint ri = 0; ri < recordCount; ri++)
for (uint ci = 0; ci < numComponents; ci++)
{
int bi = srcOff + ri * srcStride + ci * 4;
uint bits = (src[bi] << 24) | (src[bi + 1] << 16) | (src[bi + 2] << 8) | src[bi + 3];
dst[dstOff + ri * numComponents + ci] = BitsToFloat(bits);
}
#endif
}
/// Reinterpret a 32-bit integer bit pattern as an IEEE 754 float.
/// Optimized for 3D model data where denormalized/Infinity/NaN
/// are uncommon and are just asserted against.
static float BitsToFloat(int bits)
{
int exp = (bits >> 23) & 0xFF;
int frac = bits & 0x7FFFFF;
if (exp == 0)
{
assert frac == 0, "Unexpected denormalized float in vertex data";
return 0.0;
}
assert exp != 255, "Unexpected Infinity/NaN float in vertex data";
int sign = bits & 0x80000000;
bool hasSign = sign != 0;
float mantissa = 1.0 + frac / 8388608.0; // 2^23
float value = mantissa * Math.Pow(2.0, exp - 127);
return hasSign ? -value : value;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IQM Viewer</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: monospace;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: white;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 4px;
font-size: 12px;
max-width: 300px;
}
</style>
</head>
<body>
<div id="info">Loading IQM model...</div>
<script type="importmap">
{
"imports": {
"three": "https://esm.sh/three@r180",
"three/addons/controls/OrbitControls.js": "https://esm.sh/three@r180/examples/jsm/controls/OrbitControls.js"
}
}
</script>
<script type="module">
// @ts-check
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { Ptr, IqmAccessor, IqmVertexArrayType, IqmTest } from './IqmTest.mjs';
// Set up the scene, with coordinate system set to +Z up.
const scene = new THREE.Scene();
scene.rotation.x = -Math.PI / 2;
scene.rotation.z = -Math.PI / 2;
scene.background = new THREE.Color(0x222222);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
const infoDiv = /** @type {HTMLElement} */ (document.getElementById('info'));
function displayModel(/** @type {Uint8Array} */ byteArray) {
infoDiv.textContent = 'Parsing model...';
const ptr = new Ptr();
ptr.initialize(byteArray, 0);
const accessor = new IqmAccessor();
accessor.initialize(ptr);
const numVertexes = accessor.getNumVertexes();
const numTriangles = accessor.getNumTriangles();
// Positions
const posIndex = accessor.findVertexArray(IqmVertexArrayType.POSITION);
if (posIndex < 0) {
throw new Error('Position attribute not found');
}
const positions = new Float32Array(accessor.getAttributeFloatCount(posIndex));
accessor.normalizeAttribute(posIndex, positions, 0);
// Normals (if available)
let normals = null;
const normIndex = accessor.findVertexArray(IqmVertexArrayType.NORMAL);
if (normIndex >= 0) {
normals = new Float32Array(accessor.getAttributeFloatCount(normIndex));
accessor.normalizeAttribute(normIndex, normals, 0);
}
// Triangle indices
const indices = new Uint32Array(numTriangles * 3);
accessor.getTriangleIndices(indices);
infoDiv.textContent = `Loading: ${numVertexes} vertices, ${numTriangles} triangles...`;
// Create geometry
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
if (normals) {
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
}
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// Bounding box
const bounds = new Float32Array(6);
accessor.getBounds(bounds);
const box = new THREE.Box3(new THREE.Vector3().fromArray(bounds), new THREE.Vector3().fromArray(bounds, 3));
// geometry.computeBoundingBox();
// const box = /** @type {THREE.Box3} */ (geometry.boundingBox);
// Create mesh
const material = new THREE.MeshNormalMaterial({
// color: new THREE.Color('white'),
wireframe: false,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Position camera based on bounds: https://github.com/donmccurdy/three-gltf-viewer/blob/bd83b39339a525708387006aba976342f036713e/src/viewer.js#L251-L276
// const box = new THREE.Box3().setFromObject(mesh);
const size = box.getSize(new THREE.Vector3()).length();
const center = box.getCenter(new THREE.Vector3());
mesh.position.x -= center.x;
mesh.position.y -= center.y;
mesh.position.z -= center.z;
camera.position.copy(center);
camera.position.x += size / 2.0;
camera.position.y += size / 5.0;
camera.position.z += size / 2.0;
camera.lookAt(center);
// Window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
infoDiv.textContent = `✓ Loaded ${numVertexes} vertices, ${numTriangles} triangles\nUse mouse to rotate, scroll to zoom`;
// Render loop
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
}
// infoDiv.textContent = 'Fetching model...';
// displayModel(new Uint8Array(await (await fetch('mrfixit.iqm')).arrayBuffer()));
displayModel(IqmTest.getTestData());
</script>
</body>
</html>
// Struct layouts and field offsets are derived from iqm.h, part of the
// Inter-Quake Model (IQM) format by Lee Salzman.
// https://github.com/lsalzman/iqm
/// Vertex attribute semantic type codes from iqmvertexarray.type.
public enum IqmVertexArrayType
{
Position = 0,
TexCoord = 1,
Normal = 2,
Tangent = 3,
BlendIndexes = 4,
BlendWeights = 5,
Color = 6,
Custom = 16
}
/// Vertex attribute element format codes from iqmvertexarray.format.
public enum IqmFormat
{
Byte = 0,
UByte = 1,
Short = 2,
UShort = 3,
Int = 4,
UInt = 5,
Half = 6,
Float = 7,
Double = 8
}
/// Accessor for iqmheader (124 bytes, little-endian).
public static class IqmHeader
{
public const uint Length = 124;
public static uint GetVersion(Ptr p) => p.ReadU32LE(0x10);
public static uint GetFileSize(Ptr p) => p.ReadU32LE(0x14);
public static uint GetNumMeshes(Ptr p) => p.ReadU32LE(0x24);
public static uint GetOfsMeshes(Ptr p) => p.ReadU32LE(0x28);
public static uint GetNumVertexArrays(Ptr p) => p.ReadU32LE(0x2c);
public static uint GetNumVertexes(Ptr p) => p.ReadU32LE(0x30);
public static uint GetOfsVertexArrays(Ptr p) => p.ReadU32LE(0x34);
public static uint GetNumTriangles(Ptr p) => p.ReadU32LE(0x38);
public static uint GetOfsTriangles(Ptr p) => p.ReadU32LE(0x3c);
public static uint GetOfsBounds(Ptr p) => p.ReadU32LE(0x68);
}
/// Accessor for iqmvertexarray (20 bytes, little-endian).
/// size is the number of components per vertex (e.g. 3 for xyz position).
/// offset is the byte offset of the attribute data within the file.
public static class IqmVertexArray
{
public const uint Length = 20;
/// Type discriminator: returned as int to allow enum comparison via FromInt.
public static int GetType(Ptr p) => p.ReadI32LE(0);
public static uint GetFlags(Ptr p) => p.ReadU32LE(4);
/// Format discriminator: returned as int to allow enum comparison via FromInt.
public static int GetFormat(Ptr p) => p.ReadI32LE(8);
/// Component count per vertex (e.g. 3 for xyz).
public static uint GetSize(Ptr p) => p.ReadU32LE(12);
/// Byte offset of attribute data within the file.
public static uint GetOffset(Ptr p) => p.ReadU32LE(16);
}
/// Accessor for iqmmesh (24 bytes, little-endian).
public static class IqmMesh
{
public const uint Length = 24;
public static uint GetName(Ptr p) => p.ReadU32LE(0);
public static uint GetMaterial(Ptr p) => p.ReadU32LE(4);
public static uint GetFirstVertex(Ptr p) => p.ReadU32LE(8);
public static uint GetNumVertexes(Ptr p) => p.ReadU32LE(12);
public static uint GetFirstTriangle(Ptr p) => p.ReadU32LE(16);
public static uint GetNumTriangles(Ptr p) => p.ReadU32LE(20);
}
/// Zero-copy accessor for an IQM file buffer.
/// Initialize with a Ptr positioned at byte 0 of the file.
public class IqmAccessor
{
Ptr pHeader;
/// Bind this accessor to the given file buffer.
/// p must be positioned at offset 0 (the iqmheader).
public void Initialize!(Ptr p)
{
pHeader = p;
}
public uint GetNumVertexes() => IqmHeader.GetNumVertexes(pHeader);
public uint GetNumVertexArrays() => IqmHeader.GetNumVertexArrays(pHeader);
public uint GetNumMeshes() => IqmHeader.GetNumMeshes(pHeader);
public uint GetNumTriangles() => IqmHeader.GetNumTriangles(pHeader);
/// Returns the index of the vertex array with the given type, or -1 if absent.
public int FindVertexArray(IqmVertexArrayType type)
{
uint n = IqmHeader.GetNumVertexArrays(pHeader);
uint baseOfs = IqmHeader.GetOfsVertexArrays(pHeader);
Ptr() p;
p.Initialize(pHeader.Data, baseOfs);
for (int i = 0; i < n; i++)
{
if (IqmVertexArrayType.FromInt(IqmVertexArray.GetType(p)) == type)
return i;
p.Offset += IqmVertexArray.Length;
}
return -1;
}
/// Returns the total number of float values needed to hold all vertices
/// of the attribute at vaIndex (num_vertexes * component_count).
/// Returns 0 if vaIndex is -1 (not found).
public uint GetAttributeFloatCount(int vaIndex)
{
if (vaIndex < 0)
return 0;
uint vaOffset = IqmHeader.GetOfsVertexArrays(pHeader) + vaIndex * IqmVertexArray.Length;
Ptr() vap;
vap.Initialize(pHeader.Data, vaOffset);
return IqmHeader.GetNumVertexes(pHeader) * IqmVertexArray.GetSize(vap);
}
/// Normalize the attribute at vaIndex to 32-bit floats, writing into dst starting at dstOff.
/// For signed integer formats (Byte, Short) the result is SNORM in [-1, 1].
/// For unsigned integer formats (UByte, UShort) the result is UNORM in [0, 1].
/// Float and Half are copied directly.
public void NormalizeAttribute(int vaIndex, float[]! dst, uint dstOff)
{
assert vaIndex >= 0, "vertex array not found";
uint vaOffset = IqmHeader.GetOfsVertexArrays(pHeader) + vaIndex * IqmVertexArray.Length;
Ptr() vap;
vap.Initialize(pHeader.Data, vaOffset);
IqmFormat format = IqmFormat.FromInt(IqmVertexArray.GetFormat(vap));
uint components = IqmVertexArray.GetSize(vap);
uint dataOfs = IqmVertexArray.GetOffset(vap);
uint numVertexes = IqmHeader.GetNumVertexes(pHeader);
uint total = numVertexes * components;
Ptr() dp;
dp.Initialize(pHeader.Data, dataOfs);
switch (format) {
case IqmFormat.Float:
AttributeConverter.Float32LECopy(pHeader.Data, dataOfs, dst, dstOff,
components, components * 4, numVertexes);
break;
//case IqmFormat.Half:
// AttributeConverter.Float16LEToFloat32(pHeader.Data, dataOfs, dst, dstOff, total);
// break;
case IqmFormat.Byte:
for (uint i = 0; i < total; i++)
{
int b = dp.ReadU8(0);
if (b >= 128)
b -= 256;
dst[dstOff + i] = b / 127.0;
dp.Offset++;
}
break;
case IqmFormat.UByte:
for (uint i = 0; i < total; i++)
{
dst[dstOff + i] = dp.ReadU8(0) / 255.0;
dp.Offset++;
}
break;
case IqmFormat.Short:
for (uint i = 0; i < total; i++)
{
dst[dstOff + i] = dp.ReadI16LE(0) / 32767.0;
dp.Offset += 2;
}
break;
case IqmFormat.UShort:
for (uint i = 0; i < total; i++)
{
dst[dstOff + i] = dp.ReadU16LE(0) / 65535.0;
dp.Offset += 2;
}
break;
default:
assert false, "unsupported IQM attribute format";
}
}
/// Read all triangle indices into the provided array.
/// dst must have capacity for num_triangles * 3.
public void GetTriangleIndices(uint[]! dst)
{
uint numTriangles = IqmHeader.GetNumTriangles(pHeader);
uint triangleOffset = IqmHeader.GetOfsTriangles(pHeader);
Ptr() tp;
tp.Initialize(pHeader.Data, triangleOffset);
for (uint i = 0; i < numTriangles; i++)
{
dst[i * 3 + 2] = tp.ReadU32LE(0);
dst[i * 3 + 1] = tp.ReadU32LE(4);
dst[i * 3 + 0] = tp.ReadU32LE(8);
tp.Offset += 12;
}
}
/// Read the bounding box (min/max) from the file into outBounds.
/// outBounds should have capacity for 6 floats: [minX, minY, minZ, maxX, maxY, maxZ].
public void GetBounds(float[]! outBounds)
{
uint ofsBounds = IqmHeader.GetOfsBounds(pHeader);
if (ofsBounds == 0)
return;
// iqmbounds: float bbmin[3], float bbmax[3] (6 floats total, 24 bytes)
AttributeConverter.Float32LECopy(pHeader.Data, ofsBounds, outBounds, 0, 6, 24, 1);
}
}
public static class IqmTest
{
static void AssertClose(float actual, float expected, float epsilon, string label)
{
float diff = actual - expected;
if (diff < 0.0)
diff = -diff;
assert diff < epsilon, label;
}
public static byte[]! GetTestData()
{
// Obtained from: https://github.com/lsalzman/iqm/blob/711fd2ce543cf3927ce687ffc09f9dfcb31e8d53/demo/mrfixit.iqm
return resource<byte[]>("mrfixit.iqm");
}
public static void Main()
{
byte[]! buf = GetTestData();
Ptr() p;
p.Initialize(buf, 0);
IqmAccessor() acc;
acc.Initialize(p);
// Header checks
assert acc.GetNumVertexes() == 1861, "num_vertexes";
assert acc.GetNumVertexArrays() == 6, "num_vertexarrays";
assert acc.GetNumMeshes() == 2, "num_meshes";
assert acc.GetNumTriangles() == 2988, "num_triangles";
// Find position vertex array
int posIdx = acc.FindVertexArray(IqmVertexArrayType.Position);
assert posIdx >= 0, "position VA not found";
// Verify float count
uint floatCount = acc.GetAttributeFloatCount(posIdx);
assert floatCount == 1861 * 3, "position float count";
// Absent attribute returns -1 properly:
int colorIdx = acc.FindVertexArray(IqmVertexArrayType.Color);
assert colorIdx < 0, "color VA should be absent";
// Normalize positions and spot-check first five vertices.
float[]# positions = new float[floatCount];
long startMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
acc.NormalizeAttribute(posIdx, positions, 0);
long endMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
long elapsedMs = endMs - startMs;
// Expected values extracted from demo/mrfixit.iqm (FLOAT LE, offset 1160).
float eps = 0.0001;
AssertClose(positions[0], -0.178719, eps, "v0.x");
AssertClose(positions[1], -0.857190, eps, "v0.y");
AssertClose(positions[2], 4.313614, eps, "v0.z");
AssertClose(positions[3], -0.190296, eps, "v1.x");
AssertClose(positions[4], -0.835256, eps, "v1.y");
AssertClose(positions[5], 4.248078, eps, "v1.z");
AssertClose(positions[6], -0.335791, eps, "v2.x");
AssertClose(positions[7], -0.709994, eps, "v2.y");
AssertClose(positions[8], 4.344994, eps, "v2.z");
AssertClose(positions[9], -0.389321, eps, "v3.x");
AssertClose(positions[10], -0.732750, eps, "v3.y");
AssertClose(positions[11], 4.067679, eps, "v3.z");
AssertClose(positions[12], -0.248709, eps, "v4.x");
AssertClose(positions[13], -0.848792, eps, "v4.y");
AssertClose(positions[14], 4.011199, eps, "v4.z");
// Normalize blend weights (UByte UNORM) as a format smoke-test.
int blendWeightIdx = acc.FindVertexArray(IqmVertexArrayType.BlendWeights);
assert blendWeightIdx >= 0, "blendweights VA not found";
uint bwCount = acc.GetAttributeFloatCount(blendWeightIdx);
float[]# bw = new float[bwCount];
acc.NormalizeAttribute(blendWeightIdx, bw, 0);
// Each vertex has 4 blend weights; values in [0, 1].
for (uint i = 0; i < bwCount; i++)
{
assert bw[i] >= 0.0, "blend weight negative";
assert bw[i] <= 1.0, "blend weight > 1";
}
// Success: print benchmark result.
Console.WriteLine($"IQM tests passed. NormalizeAttribute() for positions took {elapsedMs} ms");
}
}
/// Lightweight byte array accessor with big-endian and little-endian read/write.
public class Ptr
{
public byte[]! Data;
public uint Offset;
/// Initialize this pointer to read from a buffer starting at the given absolute offset.
public Ptr Initialize!(byte[]! bytes, uint startOffset)
{
Data = bytes;
Offset = startOffset;
return this;
}
/// Read a single unsigned byte (0-255) at the given relative offset.
public byte ReadU8(uint relativeOffset) => Data[Offset + relativeOffset];
//
// Big-endian reads
//
/// Read a signed 16-bit big-endian integer at the given relative offset.
public short ReadI16BE(uint relativeOffset)
{
byte hi = Data[Offset + relativeOffset];
byte lo = Data[Offset + relativeOffset + 1];
short num = (hi << 8) | lo;
// sign-extend.
return (num << 16) >> 16;
}
/// Read an unsigned 16-bit big-endian integer at the given relative offset.
public ushort ReadU16BE(uint relativeOffset)
{
byte hi = Data[Offset + relativeOffset];
byte lo = Data[Offset + relativeOffset + 1];
return (hi << 8) | lo;
}
/// Read a signed 32-bit big-endian integer at the given relative offset.
public int ReadI32BE(uint relativeOffset)
{
byte b0 = Data[Offset + relativeOffset];
byte b1 = Data[Offset + relativeOffset + 1];
byte b2 = Data[Offset + relativeOffset + 2];
byte b3 = Data[Offset + relativeOffset + 3];
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
/// Read an unsigned 32-bit big-endian integer at the given relative offset.
public uint ReadU32BE(uint relativeOffset)
{
byte b0 = Data[Offset + relativeOffset];
byte b1 = Data[Offset + relativeOffset + 1];
byte b2 = Data[Offset + relativeOffset + 2];
byte b3 = Data[Offset + relativeOffset + 3];
return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
//
// Big-endian writes
//
/// Write a single unsigned byte at the given relative offset.
public void WriteU8!(uint relativeOffset, byte value)
{
Data[Offset + relativeOffset] = value;
}
/// Write an unsigned 16-bit big-endian integer at the given relative offset.
public void WriteU16BE!(uint relativeOffset, ushort value)
{
Data[Offset + relativeOffset] = value >> 8;
Data[Offset + relativeOffset + 1] = value;
}
/// Write an unsigned 32-bit big-endian integer at the given relative offset.
public void WriteU32BE!(uint relativeOffset, uint value)
{
Data[Offset + relativeOffset] = value >> 24;
Data[Offset + relativeOffset + 1] = value >> 16;
Data[Offset + relativeOffset + 2] = value >> 8;
Data[Offset + relativeOffset + 3] = value;
}
//
// Little-endian reads
//
/// Read a signed 16-bit little-endian integer at the given relative offset.
public short ReadI16LE(uint relativeOffset)
{
byte lo = Data[Offset + relativeOffset];
byte hi = Data[Offset + relativeOffset + 1];
short num = lo | (hi << 8);
// sign-extend.
return (num << 16) >> 16;
}
/// Read an unsigned 16-bit little-endian integer at the given relative offset.
public ushort ReadU16LE(uint relativeOffset)
{
byte lo = Data[Offset + relativeOffset];
byte hi = Data[Offset + relativeOffset + 1];
return lo | (hi << 8);
}
/// Read a signed 32-bit little-endian integer at the given relative offset.
public int ReadI32LE(uint relativeOffset)
{
byte b0 = Data[Offset + relativeOffset];
byte b1 = Data[Offset + relativeOffset + 1];
byte b2 = Data[Offset + relativeOffset + 2];
byte b3 = Data[Offset + relativeOffset + 3];
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
}
/// Read an unsigned 32-bit little-endian integer at the given relative offset.
public uint ReadU32LE(uint relativeOffset)
{
byte b0 = Data[Offset + relativeOffset];
byte b1 = Data[Offset + relativeOffset + 1];
byte b2 = Data[Offset + relativeOffset + 2];
byte b3 = Data[Offset + relativeOffset + 3];
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
}
//
// Little-endian writes
//
/// Write an unsigned 16-bit little-endian integer at the given relative offset.
public void WriteU16LE!(uint relativeOffset, ushort value)
{
Data[Offset + relativeOffset] = value;
Data[Offset + relativeOffset + 1] = value >> 8;
}
/// Write an unsigned 32-bit little-endian integer at the given relative offset.
public void WriteU32LE!(uint relativeOffset, uint value)
{
Data[Offset + relativeOffset] = value;
Data[Offset + relativeOffset + 1] = value >> 8;
Data[Offset + relativeOffset + 2] = value >> 16;
Data[Offset + relativeOffset + 3] = value >> 24;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment