Replace C# parser stack with Rust FFI via redguard-preservation#61
Open
michidk wants to merge 21 commits intoRGUnity:masterfrom
Open
Replace C# parser stack with Rust FFI via redguard-preservation#61michidk wants to merge 21 commits intoRGUnity:masterfrom
michidk wants to merge 21 commits intoRGUnity:masterfrom
Conversation
Integrate redguard-preservation Rust library via P/Invoke for native parsing of Redguard game assets. Includes complete bindings for all 21 FFI functions covering models, textures, audio, and scene conversion. New files: - Assets/Plugins/rgpre.dll — prebuilt Rust cdylib (v0.1.0) - Assets/Scripts/FFI/RgpreBindings.cs — P/Invoke bindings (21 functions) - Assets/Scripts/FFI/RgmdDeserializer.cs — RGMD binary → Unity Mesh - Assets/Editor/RgprePluginDownloader.cs — auto-download native plugin from GitHub releases for any platform (Win/Mac/Linux) Also adds GLTFExporter.ExportToPath() for headless GLB export.
907a1bb to
f23e8fd
Compare
Rewrite GLTFExporter to use RgpreBindings instead of GLTFast. Export now goes directly from raw game files to GLB via the Rust library, bypassing Unity's scene serialization entirely. - Delete Export.asmdef (no longer needed without GLTFast) - Remove com.unity.cloud.gltfast package dependency - GLTFExporter reads raw files and calls rg_convert_*_to_glb - ModelViewer sets export context (model/area, file names, palette) - Support single model, ROB, RGM, and WLD+RGM area export
SpawnModel now uses FFIModelLoader instead of the C# parsing pipeline: - FFIModelLoader.Load3D/Load3DC/LoadROB call rg_parse_model_data and rg_parse_rob_data via P/Invoke, deserialize RGMD binary into Unity Meshes, and create GameObjects with proper materials - FFITextureLoader decodes textures via rg_decode_texture FFI, with caching for both textures and palette colors - RgmdDeserializer extended to return per-submesh material info (solid color index or texture_id + image_id) and frame count SpawnArea still uses the old C# ModelLoader.LoadArea (blocked on upstream rg_parse_wld_terrain_data + RGM placement FFI).
MCP for Unity is a dev-only tool, not a project dependency. Newtonsoft.Json was a transitive dep from MCP. Neither belongs in the PR. Only change vs master: removal of com.unity.cloud.gltfast.
SpawnArea now uses FFIModelLoader.LoadArea which goes entirely through the Rust FFI: - rg_parse_rob_data → mesh dictionary (unique models) - rg_parse_rgm_placements → RGPL binary (positions, rotations, lights) - rg_parse_wld_terrain_data → RGMD terrain mesh - rg_decode_texture → texture decoding per submesh New files: - RgplDeserializer.cs — parses RGPL binary (placements + lights) Updated: - RgpreBindings.cs — added rg_parse_wld_terrain_data, rg_parse_rgm_placements - FFIModelLoader.cs — added LoadArea with full scene assembly - ModelViewer.cs — SpawnArea uses FFIModelLoader - PlayerMeshLoader.cs — uses FFIModelLoader.Load3DC - ModelLoader.cs — removed dead Load3D/Load3DC/LoadROB methods Updated rgpre.dll to v0.2.2 with terrain and placement FFI support.
…sprites - RgpreBindings: rg_decode_texture now takes TextureCache handle instead of raw TEXBSI+palette bytes (matches upstream API change) - FFITextureLoader: creates native TextureCache once, reuses for all decode calls (eliminates redundant disk I/O per texture) - RgplDeserializer: reads texture_id + image_id from placement records - FFIModelLoader: handles flat sprite placements as textured quads - Lookup uses modelName directly (upstream now strips extensions) Benchmark: 3000 meshes, 984K verts, 328K tris — exact geometry match
- SceneLoader: replace RGTexStore.MaterialDict with renderer iteration, replace RGScriptedObject animation with BlendShapeAnimator - PlayerMeshLoader: replace RGScriptedObject animation with BlendShapeAnimator - Both now fully use FFI path with zero old parsing dependencies
- Rewrite FFI layer to path-based API (DLL handles all I/O) - Delete entire RGFileImport parser stack (RGGFXImport/, stores, ModelLoader) - Keep runtime code (RGScriptedObject, AnimStore, ScriptStore) with FFI data sources - Add FFIWorldStore (C# INI parser), FFIGxaLoader, FFISoundStore adapters - Add RGM section access via rg_get_rgm_section for anim/script/attribute data - C#-side UV normalization (material texture scale) since DLL returns /16.0 UVs - Global mesh, ROB, and material caching across LoadArea calls - Performance timing instrumentation in LoadArea - Update to rgpre v0.3.3 (texture cache, stateless model parse, RGM sections) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Replace BinaryReader/MemoryStream with direct pointer walking - Add NativeBuffer, ReadBuffer/FreeBuffer pattern for deferred free - Add repr(C) struct definitions: TextureHeader, RgmdHeader, RgmdSubmeshHeader, RgmdVertex, RobHeader, RobSegmentHeader, RgplHeader, RgplPlacement, RgplLight - RGMD submesh header now fixed 16 bytes, ROB segment header always 16 bytes - RGPL placement field order: textureId, imageId, objectType - Update to rgpre v0.3.4 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…remove dead code, split long methods - Fix P0 bug: TryGetMeshData always returned false due to duplicate return block - Extract FFIPathUtils with shared ResolveFile() and NormalizeModelName() - Move EnsureReadable() to RgpreBindings as shared utility - Extract EnsureRobCached() to deduplicate ROB parse-and-cache logic - Remove ~150 lines dead code (BuildRuntimeObjectsFromMetadata, unused vars, dead types) - Remove unreachable LoadRTX body and dead GetRtxMetadataByIndex in FFISoundStore - Split LoadArea (177 lines) into PlaceRgplObjects, CreateLights, LoadTerrain - Split LoadRgmSections (201 lines) into PopulateRawSections, ParseRAHD, ParseMPOB - Fix naming: worldData→WorldData, scriptedObjects→ScriptedObjects, SFXList→SfxClips - Add Destroy() calls in ClearCache to prevent GPU resource leaks - Add StringComparer.OrdinalIgnoreCase to materialCache for consistency - Refactor FFIGxaLoader to use RgpreBindings.ReadBuffer + TextureHeader struct - Extract magic numbers to named constants (WavMinHeaderSize, Pcm scales, RalcItemSize)
…cker - Download all 4 platform binaries (Win64, macOS x64, macOS ARM64, Linux x64) per release - Place in platform-specific subfolders to avoid filename conflicts (macOS archs) - Add 'Tools/rgpre/Download Specific Version...' menu with EditorWindow prompt - Rename menu parent to 'Tools/rgpre/' with 'Update to Latest' and version picker - Configure PluginImporter with correct CPU and OS per platform - Support both latest and tag-based GitHub release API endpoints
… DLL
- All platform binaries now live under Assets/Plugins/rgpre/{OS}/{arch}/
- Remove old Assets/Plugins/rgpre.dll and version.txt that caused name conflicts
…X loaded flags Replace RTX loading stub with real rgpre-based decoding (ConvertRtxEntryToWav), matching the existing SFX loading pattern. Add AmbientRtx script function (96) mirroring AmbientSound but pulling from RTX store. Fix SFXLoaded/RTXLoaded flags that were never set to true, causing redundant reloads on every world transition.
…d RTX subtitle binding Add missing assets_dir parameter to ParseModelData and ParseRobData to match the updated rgpre native API (palette resolution for solid-color materials). Add rg_get_rtx_subtitle binding and wire it into LoadRTX so entries load with both audio and subtitle text.
Use rg_decode_texture_all_frames to load every frame for animated textures
(water, fire, torches) instead of only frame 0. All frames are stored on
the material via SetTexture(FRAME_{n}), matching the old C# parser behavior
and preparing for a future runtime texture animation system.
…d RGB for solid colors, fix stale material cache on reload
# Conflicts: # Assets/Scripts/RGFileImport/RGMData/RGScriptedObject.cs # Assets/Scripts/RGFileImport/RGMeshStore.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the custom C# game file parsing pipeline with Rust FFI calls to redguard-preservation. Scene loading, model parsing, texture decoding, audio conversion, and subtitle loading all go through the native library. The entire legacy C# parser stack (~5900 lines) has been removed.
Performance
Full Island Scene Load (Stros M'Kai)
Geometry output matches exactly. The FFI path also creates 556 point lights and flat sprite quads that the C# path previously skipped.
Changes
Assets/Scripts/FFI/layer with P/Invoke bindings, zero-copy deserializers, and cached loaders for models, textures, audio (SFX + RTX), loading screens, and world dataAssets/Editor/RgprePluginDownloader.cs— editor tool to download rgpre binaries for all platforms from GitHub releasesAssets/Plugins/rgpre/— native plugin binaries organized by platform (Windows/x86_64/,Linux/x86_64/,macOS/arm64/,macOS/x86_64/)Architecture
FFI boundary — what crosses and what doesn't
The FFI layer (
rgpre) handles all complex proprietary binary format parsing. The C# side only directly reads trivial/flat formats:rgpre).RGMsections,.3D/.3DC/.ROBmodels,.WLDterrain,TEXBSItextures,.SFX/.RTXaudio,.GXAanimations,.FNTfontsWORLD.INI(text INI),.COLpalettes (raw RGB bytes), config/save JSONWhat the FFI currently covers
rg_parse_model_data,rg_parse_rob_data,rg_parse_wld_terrain_datarg_parse_rgm_placementsrg_decode_texture,rg_decode_texture_all_frames,rg_texbsi_image_count,rg_decode_gxa,rg_gxa_frame_countrg_convert_sfx_to_wav,rg_sfx_effect_count,rg_convert_rtx_entry_to_wav,rg_rtx_entry_count,rg_get_rtx_subtitlerg_convert_fnt_to_ttfrg_convert_model_from_path,rg_convert_rgm_from_path,rg_convert_wld_from_pathrg_rgm_section_count,rg_get_rgm_sectionWhat's not yet covered — remaining C# parsing
The gameplay runtime scripts kept in this PR still do their own binary parsing from raw RGM section bytes. The Rust side already has parsers for all of this data — future FFI additions would let the C# side consume structured data instead of re-parsing the binary.
ActorScript— per-actor bytecode slice, resolved string table, variable table, script PC/offset/length from RAHD+RASC+RAST+RASB+RAVARGRGMScriptStore.cs(~55 KB)SoupDef— 367 function names + param counts, flags, references, attributes, animation group namessoupdeffcn_nimpl.cs(~79 KB)RagrAnimGroup— group index, anim ID, flag, frame count, command listRGRGMAnimStore.cs(~22 KB)RaexRecord— grip points, scabbard slots, range min/ideal/max, taunt ID, texture IDRavcRecord— offset XYZ, vertex index, radiusDLL Version
rgpre v0.5.1 — source: redguard-preservation