├── Prism ├── Resources │ ├── uv_debug.png │ ├── venice_sunset_2k.png │ ├── radiancevenicematcap.png │ ├── reflectionvenicematcap.png │ ├── screen.vert │ ├── ResourceHelper.cs │ ├── bone.obj │ ├── screen.frag │ ├── model.vert │ └── model.frag ├── AssetStreamType.cs ├── AssetMetaData.cs ├── TreeListViewEntry.cs ├── AssetStream.cs ├── Render │ ├── IRenderContext.cs │ ├── Shader │ │ ├── ShaderUniform.cs │ │ ├── GenericShaderUniform.cs │ │ ├── UniformCollection.cs │ │ └── ShaderProgram.cs │ ├── Camera.cs │ └── Framebuffer.cs ├── FlatToolStripRenderer.cs ├── Extensions │ ├── Uint32Ext.cs │ ├── PaintSuspenderExtension.cs │ ├── BitmapExt.cs │ └── IImageExt.cs ├── Program.cs ├── Prism.csproj.user ├── GlControlContext.cs ├── TextureUtil.cs ├── SettingsForm.cs ├── PrismSettings.cs └── Prism.csproj ├── .gitignore ├── RainbowForge ├── Components │ ├── BuildTable.cs │ ├── LocalizationDictionaryEntry.cs │ ├── LocalizationPointer.cs │ ├── LocalizationPackage.cs │ └── R6AIWorldComponent.cs ├── Database │ ├── AssetPath.cs │ ├── FilenameDocument.cs │ ├── EntryDocumentWithSource.cs │ ├── EntryDocument.cs │ └── IndexUtil.cs ├── research │ ├── Screenshot_DearImGUI_1_SAFE_rs.jpg │ └── Screenshot_DearImGUI_2_SAFE_rs-1.jpg ├── Model │ ├── UnknownFaceDataBlock.cs │ ├── FaceBlockStat.cs │ ├── SkeletonMirrorData.cs │ ├── BoneInitialTransforms.cs │ ├── MeshObjectContainer.cs │ ├── TrianglePointer.cs │ ├── MeshObjectSkinMapping.cs │ ├── MeshObjectHeader.cs │ └── Skeleton.cs ├── Core │ ├── Container │ │ ├── ForgeContainer.cs │ │ ├── Hash.cs │ │ ├── ForgeAsset.cs │ │ └── Descriptor.cs │ ├── DataBlock │ │ ├── IAssetBlock.cs │ │ ├── FlatDataBlock.cs │ │ ├── ChunkedData.cs │ │ └── ChunkedDataBlock.cs │ ├── Meta.cs │ ├── Entry.cs │ ├── MetaLink.cs │ ├── NameEncoding.cs │ └── DepGraph.cs ├── Structs │ ├── Vector2H.cs │ └── Matrix4F.cs ├── RenderPipeline │ ├── UniformType.cs │ ├── TextureMapSpec.cs │ ├── TextureSelector.cs │ ├── Shader.cs │ ├── TextureMap.cs │ ├── Material.cs │ ├── ShaderUniform.cs │ └── Mesh.cs ├── Image │ └── TextureType.cs ├── Link │ ├── UidLinkNode.cs │ ├── UidLinkEntry.cs │ └── UidLinkContainer.cs ├── RainbowForge.csproj ├── FileSystemUtil.cs ├── Compression │ └── OodleHelper.cs ├── BufferUtil.cs ├── BoundingBox.cs ├── Archive │ ├── FlatArchiveEntry.cs │ └── FlatArchive.cs ├── Crc32.cs ├── FileMetaData.cs ├── BinaryReaderExt.cs ├── MagicHelper.cs └── SubStream.cs ├── RainbowScimitar ├── Scimitar │ ├── BundleEntryPointer.cs │ ├── ScimitarChunkDataInfo.cs │ ├── ScimitarChunkSizeInfo.cs │ ├── ScimitarFileTableEntry.cs │ ├── ScimitarFileHeader.cs │ ├── ScimitarArchiveFileData.cs │ ├── ScimitarFilePackMethod.cs │ ├── ScimitarFastLoadTableOfContents.cs │ ├── ScimitarAssetMetadata.cs │ ├── ScimitarArchiveFileMetadata.cs │ ├── ScimitarId.cs │ ├── ScimitarGlobalMeta.cs │ ├── ScimitarBuilder.cs │ ├── ScimitarArchive.cs │ ├── ScimitarTable.cs │ ├── IScimitarFileData.cs │ └── ScimitarStreamingPackedData.cs ├── Helper │ └── StreamHelper.cs ├── Model │ ├── WorldLoaderSubNode.cs │ ├── Vec3f.cs │ ├── Box.cs │ └── Matrix4f.cs ├── DataTypes │ ├── WindDefinition.cs │ ├── LoadUnit.cs │ ├── TagClient.cs │ ├── GISettings.cs │ ├── SoundId.cs │ ├── BoundingVolume.cs │ ├── IColor.cs │ ├── WorldGraphicData.cs │ ├── SoundState.cs │ ├── WorldLoadUnitLoadByTag.cs │ ├── Character.cs │ ├── WorldLoadUnitWorldDivision.cs │ ├── SpaceManager.cs │ ├── GIBoundingVolume.cs │ ├── WorldDivisionToTagLoadUnitLookup.cs │ ├── SpaceComponentNode.cs │ ├── CompiledSoundMedia.cs │ ├── Entity.cs │ ├── WorldLoader.cs │ └── World.cs ├── Extensions │ ├── ByteArrayExt.cs │ ├── BinaryWriterExt.cs │ ├── StructExt.cs │ ├── StreamExt.cs │ └── BinaryReaderExt.cs ├── RainbowScimitar.csproj ├── Compression │ └── OodleHelper.cs └── FileTypes │ └── DepGraph.cs ├── .idea └── .idea.RainbowForge │ └── .idea │ ├── .gitignore │ ├── codeStyles │ └── codeStyleConfig.xml │ ├── encodings.xml │ ├── vcs.xml │ ├── indexLayout.xml │ └── avalonia.xml ├── AssetCatalog ├── App.xaml.cs ├── Resources │ ├── screen.vert │ ├── model.vert │ ├── model.frag │ └── screen.frag ├── Render │ ├── Shader │ │ ├── ShaderUniform.cs │ │ ├── GenericShaderUniform.cs │ │ ├── UniformCollection.cs │ │ └── ShaderProgram.cs │ ├── Camera.cs │ └── Framebuffer.cs ├── AssemblyInfo.cs ├── Converters │ ├── PixelsToGridLengthConverter.cs │ ├── CatalogStatusToTitleConverter.cs │ ├── ForgeToTitleMsgConverter.cs │ ├── BooleanToVisibilityConverter.cs │ ├── ForgeEntryToListDescConverter.cs │ ├── ForgeEntryToIconConverter.cs │ ├── EnumDescriptionTypeConverter.cs │ ├── ForgeEntryToCatalogIconConverter.cs │ └── ForgeEntryToCatalogIconBrushConverter.cs ├── Model │ ├── ICatalogApp.cs │ ├── LocalCatalogDb.cs │ └── ICatalogDb.cs ├── Extensions │ └── BitmapExt.cs ├── BindingSources │ └── EnumBindingSourceExtension.cs ├── AssetCatalog.csproj └── App.xaml ├── ForgeDiff └── ForgeDiff.csproj ├── AssetDiff └── AssetDiff.csproj ├── Sandbox └── Sandbox.csproj ├── DumpTool ├── ListCommand.cs ├── DumpTool.csproj ├── DumpAllCommand.cs ├── DumpCommand.cs ├── Program.cs ├── DumpAllMeshPropsCommand.cs ├── FindAllMeshPropsGlobalCommand.cs ├── FindCommand.cs ├── FindAllMeshPropsCommand.cs ├── IndexCommand.cs └── InspectCommand.cs └── RainbowForge.sln.DotSettings.user /Prism/Resources/uv_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/Prism/Resources/uv_debug.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | private/ 7 | .vs/ 8 | .vscode/ 9 | -------------------------------------------------------------------------------- /RainbowForge/Components/BuildTable.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Components 2 | { 3 | public class BuildTable 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Prism/Resources/venice_sunset_2k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/Prism/Resources/venice_sunset_2k.png -------------------------------------------------------------------------------- /Prism/AssetStreamType.cs: -------------------------------------------------------------------------------- 1 | namespace Prism 2 | { 3 | internal enum AssetStreamType 4 | { 5 | ForgeEntry, 6 | ArchiveEntry 7 | } 8 | } -------------------------------------------------------------------------------- /Prism/Resources/radiancevenicematcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/Prism/Resources/radiancevenicematcap.png -------------------------------------------------------------------------------- /Prism/Resources/reflectionvenicematcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/Prism/Resources/reflectionvenicematcap.png -------------------------------------------------------------------------------- /Prism/AssetMetaData.cs: -------------------------------------------------------------------------------- 1 | namespace Prism 2 | { 3 | internal record AssetMetaData(ulong Uid, ulong Magic, uint ContainerType, string Filename); 4 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/BundleEntryPointer.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowScimitar.Scimitar 2 | { 3 | public record BundleEntryPointer(int Table, int Index); 4 | } -------------------------------------------------------------------------------- /Prism/TreeListViewEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Prism 2 | { 3 | internal record TreeListViewEntry(string Key, object Value, params TreeListViewEntry[] Children); 4 | } -------------------------------------------------------------------------------- /RainbowForge/Database/AssetPath.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Database 2 | { 3 | public record AssetPath(string Forge, string Filename, ulong? ParentContainer); 4 | } -------------------------------------------------------------------------------- /RainbowForge/Database/FilenameDocument.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Database 2 | { 3 | public record FilenameDocument(string Filename, string CollectionName); 4 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarChunkDataInfo.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowScimitar.Scimitar 2 | { 3 | public record ScimitarChunkDataInfo(uint Checksum, long Offset); 4 | } -------------------------------------------------------------------------------- /RainbowForge/research/Screenshot_DearImGUI_1_SAFE_rs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/RainbowForge/research/Screenshot_DearImGUI_1_SAFE_rs.jpg -------------------------------------------------------------------------------- /RainbowForge/research/Screenshot_DearImGUI_2_SAFE_rs-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parzivail/RainbowForge/HEAD/RainbowForge/research/Screenshot_DearImGUI_2_SAFE_rs-1.jpg -------------------------------------------------------------------------------- /RainbowForge/Components/LocalizationDictionaryEntry.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Components 2 | { 3 | public record LocalizationDictionaryEntry(ushort EntryChar, ushort NextEntry); 4 | } -------------------------------------------------------------------------------- /RainbowForge/Components/LocalizationPointer.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Components 2 | { 3 | public record LocalizationPointer(uint Id, uint StringDataPos, uint LengthDataPos); 4 | } -------------------------------------------------------------------------------- /RainbowForge/Database/EntryDocumentWithSource.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Database 2 | { 3 | public class EntryDocumentWithSource : EntryDocument 4 | { 5 | public string Source { get; init; } 6 | } 7 | } -------------------------------------------------------------------------------- /Prism/AssetStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Prism 5 | { 6 | internal record AssetStream(AssetStreamType StreamType, AssetMetaData MetaData, Func StreamProvider); 7 | } -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /projectSettingsUpdater.xml 7 | /modules.xml 8 | /.idea.RainbowForge.iml 9 | -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Prism/Render/IRenderContext.cs: -------------------------------------------------------------------------------- 1 | namespace Prism.Render 2 | { 3 | public interface IRenderContext 4 | { 5 | int Width { get; } 6 | int Height { get; } 7 | int DefaultFramebuffer { get; } 8 | void MarkDirty(); 9 | } 10 | } -------------------------------------------------------------------------------- /AssetCatalog/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace AssetCatalog 4 | { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application 9 | { 10 | } 11 | } -------------------------------------------------------------------------------- /RainbowForge/Model/UnknownFaceDataBlock.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Model 2 | { 3 | public struct UnknownFaceDataBlock 4 | { 5 | public byte Data1; 6 | public byte Data2; 7 | public byte Data3; 8 | public byte Data4; 9 | } 10 | } -------------------------------------------------------------------------------- /RainbowForge/Core/Container/ForgeContainer.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Core.Container 2 | { 3 | /// 4 | /// No-op class to serve as a common parent for all Forge subcomponents 5 | /// 6 | public class ForgeContainer 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RainbowScimitar/Helper/StreamHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | 3 | namespace RainbowScimitar.Helper 4 | { 5 | public class StreamHelper 6 | { 7 | public static readonly RecyclableMemoryStreamManager MemoryStreamManager = new RecyclableMemoryStreamManager(); 8 | } 9 | } -------------------------------------------------------------------------------- /Prism/Resources/screen.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec2 aTexCoords; 4 | 5 | out vec2 TexCoords; 6 | 7 | void main() 8 | { 9 | gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 10 | TexCoords = aTexCoords; 11 | } -------------------------------------------------------------------------------- /AssetCatalog/Resources/screen.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec2 aTexCoords; 4 | 5 | out vec2 TexCoords; 6 | 7 | void main() 8 | { 9 | gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 10 | TexCoords = aTexCoords; 11 | } -------------------------------------------------------------------------------- /RainbowForge/Structs/Vector2H.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowForge.Structs 5 | { 6 | [StructLayout(LayoutKind.Explicit)] 7 | public struct Vector2H 8 | { 9 | [FieldOffset(0)] public Half X; 10 | [FieldOffset(2)] public Half Y; 11 | } 12 | } -------------------------------------------------------------------------------- /RainbowScimitar/Model/WorldLoaderSubNode.cs: -------------------------------------------------------------------------------- 1 | using RainbowScimitar.DataTypes; 2 | using RainbowScimitar.Scimitar; 3 | 4 | namespace RainbowScimitar.Model 5 | { 6 | public record WorldLoaderSubNode(WorldLoadUnitWorldDivision[] WorldDivisions, ScimitarId[] Scenarios, WorldDivisionToTagLoadUnitLookup Lookup); 7 | } -------------------------------------------------------------------------------- /RainbowForge/Core/DataBlock/IAssetBlock.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core.DataBlock 4 | { 5 | /// 6 | /// No-op class to serve as a common parent for all ForgeAsset subcomponents 7 | /// 8 | public interface IAssetBlock 9 | { 10 | public Stream GetDataStream(BinaryReader r); 11 | } 12 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarChunkSizeInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Scimitar 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public readonly struct ScimitarChunkSizeInfo 7 | { 8 | public readonly int PayloadSize; 9 | public readonly int SerializedSize; 10 | } 11 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarFileTableEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Scimitar 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public readonly struct ScimitarFileTableEntry 7 | { 8 | public readonly long Offset; 9 | public readonly ScimitarId Uid; 10 | public readonly int Size; 11 | } 12 | } -------------------------------------------------------------------------------- /Prism/FlatToolStripRenderer.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | 3 | namespace Prism 4 | { 5 | public class FlatToolStripRenderer : ToolStripProfessionalRenderer 6 | { 7 | /// 8 | protected override void OnRenderToolStripBackground(ToolStripRenderEventArgs e) 9 | { 10 | if (e.ToolStrip.GetType() != typeof(MenuStrip)) 11 | base.OnRenderToolStripBackground(e); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /RainbowForge/Model/FaceBlockStat.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Model 2 | { 3 | public struct FaceBlockStat 4 | { 5 | public float Data1; 6 | public float Data2; 7 | public float Data3; 8 | public float Data4; 9 | public float Data5; 10 | public float Data6; 11 | public float Data7; 12 | public float Data8; 13 | public float Data9; 14 | public float Data10; 15 | public float Data11; 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/.idea.RainbowForge/.idea/avalonia.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /RainbowScimitar/Model/Vec3f.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Model 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Vec3f 7 | { 8 | public readonly float X; 9 | public readonly float Y; 10 | public readonly float Z; 11 | 12 | /// 13 | public override string ToString() 14 | { 15 | return $"({X}, {Y}, {Z})"; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Prism/Render/Shader/ShaderUniform.cs: -------------------------------------------------------------------------------- 1 | namespace Prism.Render.Shader 2 | { 3 | public class ShaderUniform : GenericShaderUniform 4 | { 5 | public new T Value 6 | { 7 | get => (T)base.Value; 8 | set => base.Value = value; 9 | } 10 | 11 | public ShaderUniform(string name) : base(typeof(T), name) 12 | { 13 | } 14 | 15 | public ShaderUniform(string name, T value) : base(typeof(T), name, value) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Shader/ShaderUniform.cs: -------------------------------------------------------------------------------- 1 | namespace AssetCatalog.Render.Shader 2 | { 3 | public class ShaderUniform : GenericShaderUniform 4 | { 5 | public new T Value 6 | { 7 | get => (T) base.Value; 8 | set => base.Value = value; 9 | } 10 | 11 | public ShaderUniform(string name) : base(typeof(T), name) 12 | { 13 | } 14 | 15 | public ShaderUniform(string name, T value) : base(typeof(T), name, value) 16 | { 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WindDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | 5 | namespace RainbowScimitar.DataTypes 6 | { 7 | public record WindDefinition(float[] Unknown1) 8 | { 9 | public static WindDefinition Read(BinaryReader r) 10 | { 11 | r.ReadMagic(Magic.WindDefinition); 12 | 13 | var unk1 = r.ReadStructs(25); 14 | 15 | return new WindDefinition(unk1); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/UniformType.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.RenderPipeline 2 | { 3 | public enum UniformType : uint 4 | { 5 | ShaderCodeVariableFloat = 0x141F1820, 6 | ShaderCodeVariableTexture = 0x846B6E90, 7 | ShaderCodeVariableColor = 0xBBEC0E5C, 8 | ShaderCodeVariableTexture3D = 0x1f48b068, 9 | ShaderCodeVariableTextureCube = 0x319ec5d, 10 | ShaderCodeVariableNormalMap = 0x8d0ea587, 11 | ShaderCodeVariableVector = 0xa0b8b51e 12 | } 13 | } -------------------------------------------------------------------------------- /ForgeDiff/ForgeDiff.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/LoadUnit.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record LoadUnit(ScimitarId[] Units) 9 | { 10 | public static LoadUnit Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.LoadUnit); 13 | var units = r.ReadLengthPrefixedStructs(); 14 | return new LoadUnit(units); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Prism/Extensions/Uint32Ext.cs: -------------------------------------------------------------------------------- 1 | namespace Prism.Extensions 2 | { 3 | public static class Uint32Ext 4 | { 5 | private static readonly string[] Units = 6 | { 7 | "B", 8 | "KB", 9 | "MB", 10 | "GB" 11 | }; 12 | 13 | public static string ToFileSizeString(this uint numBytes) 14 | { 15 | var i = 0; 16 | var length = (float)numBytes; 17 | for (; length >= 1024; length /= 1024, i++) ; 18 | 19 | return $"{length:0.##} {Units[i]}"; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/TagClient.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record TagClient(ScimitarId[] Units) 9 | { 10 | public static TagClient Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.TagClient); 13 | var units = r.ReadLengthPrefixedStructs(); 14 | return new TagClient(units); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/GISettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | 5 | namespace RainbowScimitar.DataTypes 6 | { 7 | public record GISettings(float Unknown1, float Unknown2) 8 | { 9 | public static GISettings Read(BinaryReader r) 10 | { 11 | r.ReadMagic(Magic.GISettings); 12 | 13 | var unk1 = r.ReadSingle(); 14 | var unk2 = r.ReadSingle(); 15 | 16 | return new GISettings(unk1, unk2); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /AssetCatalog/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] -------------------------------------------------------------------------------- /RainbowScimitar/Extensions/ByteArrayExt.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Extensions 4 | { 5 | public static class ByteArrayExt 6 | { 7 | public static void ToStruct(this byte[] buffer, ref T dest) where T : struct 8 | { 9 | var ptr = Marshal.AllocHGlobal(buffer.Length); 10 | Marshal.Copy(buffer, 0, ptr, buffer.Length); 11 | dest = (T)Marshal.PtrToStructure(ptr, dest.GetType()); 12 | Marshal.FreeHGlobal(ptr); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/SoundId.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record SoundId(uint Unknown1, ScimitarId Id) 9 | { 10 | public static SoundId Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.SoundID); 13 | 14 | var prop1 = r.ReadUInt32(); 15 | var uid = r.ReadUid(); 16 | 17 | return new SoundId(prop1, uid); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /RainbowForge/Image/TextureType.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowForge.Image 2 | { 3 | public enum TextureType : uint 4 | { 5 | Diffuse = 0x0, // older GUI tetxures 6 | Normal = 0x1, // not just yellow (RG = XY) ones, head detail (RGA = XYZ) as well 7 | Specular = 0x2, // usually holds gloss, metalness and cavity 8 | Misc = 0x3, // Misc Map; can be Diffuse, GUI, Normal, Emission, Mask, Distortion, Cubemap or pretty much anything 9 | Displacement = 0x5, 10 | Translucence = 0x6, 11 | ColorMask = 0x7 12 | } 13 | } -------------------------------------------------------------------------------- /RainbowForge/Database/EntryDocument.cs: -------------------------------------------------------------------------------- 1 | using RainbowForge.Core; 2 | 3 | namespace RainbowForge.Database 4 | { 5 | public class EntryDocument 6 | { 7 | public ulong Uid { get; init; } 8 | public uint Timestamp { get; init; } 9 | public uint FileType { get; init; } 10 | 11 | public static EntryDocument For(Entry entry) 12 | { 13 | return new() 14 | { 15 | Uid = entry.Uid, 16 | Timestamp = entry.MetaData.Timestamp, 17 | FileType = entry.MetaData.FileType 18 | }; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Prism/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace Prism 5 | { 6 | internal static class Program 7 | { 8 | /// 9 | /// The main entry point for the application. 10 | /// 11 | [STAThread] 12 | private static void Main() 13 | { 14 | Application.SetHighDpiMode(HighDpiMode.SystemAware); 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new PrismForm()); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /RainbowScimitar/Extensions/BinaryWriterExt.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace RainbowScimitar.Extensions 5 | { 6 | public static class BinaryWriterExt 7 | { 8 | public static void WriteStructs(this BinaryWriter w, IEnumerable structs) where T : struct 9 | { 10 | foreach (var s in structs) w.WriteStruct(s); 11 | } 12 | 13 | public static void WriteStruct(this BinaryWriter w, T s) where T : struct 14 | { 15 | w.Write(s.ToBytes()); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/BoundingVolume.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Model; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record BoundingVolume(int Unknown1, Box Volume) 9 | { 10 | public static BoundingVolume Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.BoundingVolume); 13 | 14 | var unk1 = r.ReadInt32(); 15 | var box = r.ReadStruct(); 16 | 17 | return new BoundingVolume(unk1, box); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /RainbowScimitar/Extensions/StructExt.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Extensions 4 | { 5 | public static class StructExt 6 | { 7 | public static byte[] ToBytes(this T s) where T : struct 8 | { 9 | var size = Marshal.SizeOf(s); 10 | var arr = new byte[size]; 11 | 12 | var ptr = Marshal.AllocHGlobal(size); 13 | Marshal.StructureToPtr(s, ptr, true); 14 | Marshal.Copy(ptr, arr, 0, size); 15 | Marshal.FreeHGlobal(ptr); 16 | 17 | return arr; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AssetDiff/AssetDiff.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /RainbowScimitar/RainbowScimitar.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /RainbowForge/Link/UidLinkNode.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Link 4 | { 5 | public class UidLinkNode 6 | { 7 | public byte[] Data { get; } 8 | public ulong LinkedUid { get; } 9 | 10 | private UidLinkNode(byte[] data, ulong linkedUid) 11 | { 12 | Data = data; 13 | LinkedUid = linkedUid; 14 | } 15 | 16 | public static UidLinkNode Read(BinaryReader r) 17 | { 18 | var data = r.ReadBytes(12); 19 | var linkedUid = r.ReadUInt64(); 20 | 21 | return new UidLinkNode(data, linkedUid); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/IColor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | 5 | namespace RainbowScimitar.DataTypes 6 | { 7 | public record IColor(int Unknown1, float[] Unknown2, ushort Unknown3) 8 | { 9 | public static IColor Read(BinaryReader r) 10 | { 11 | r.ReadMagic(Magic.IColor); 12 | 13 | var unk1 = r.ReadInt32(); 14 | var unk2 = r.ReadStructs(16); 15 | var unk3 = r.ReadUInt16(); // could just be bytes? 16 | 17 | return new IColor(unk1, unk2, unk3); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /RainbowForge/RainbowForge.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WorldGraphicData.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record WorldGraphicData(ScimitarId Uid, GISettings GiSettings) 9 | { 10 | public static WorldGraphicData Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.WorldGraphicData); 13 | 14 | var uid = r.ReadUid(); 15 | 16 | var giSettings = GISettings.Read(r); 17 | 18 | return new WorldGraphicData(uid, giSettings); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /RainbowScimitar/Model/Box.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Model 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public struct Box 7 | { 8 | public readonly float MinX; 9 | public readonly float MinY; 10 | public readonly float MinZ; 11 | public readonly float MaxX; 12 | public readonly float MaxY; 13 | public readonly float MaxZ; 14 | 15 | /// 16 | public override string ToString() 17 | { 18 | return $"({MinX}, {MinY}, {MinZ}) -> ({MaxX}, {MaxY}, {MaxZ})"; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/SoundState.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record SoundState(ScimitarId Uid, SoundId Sound1, SoundId Sound2) 9 | { 10 | public static SoundState Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.SoundState); 13 | 14 | var uid = r.ReadUid(); 15 | 16 | var soundId1 = SoundId.Read(r); 17 | var soundId2 = SoundId.Read(r); 18 | 19 | return new SoundState(uid, soundId1, soundId2); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WorldLoadUnitLoadByTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record WorldLoadUnitLoadByTag(ScimitarId Uid, ScimitarId LoadUnitUid) 9 | { 10 | public static WorldLoadUnitLoadByTag Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.WorldLoadUnit_LoadByTag); 13 | 14 | var uid = r.ReadUid(); 15 | var loadUnitUid = r.ReadUid(); 16 | 17 | return new WorldLoadUnitLoadByTag(uid, loadUnitUid); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /RainbowForge/FileSystemUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RainbowForge 5 | { 6 | public class FileSystemUtil 7 | { 8 | public static void AssertFileExists(string filename) 9 | { 10 | if (File.Exists(filename)) return; 11 | 12 | Console.Error.WriteLine($"File not found: {filename}"); 13 | Environment.Exit(-1); 14 | } 15 | 16 | public static void AssertDirectoryExists(string dir) 17 | { 18 | if (Directory.Exists(dir)) return; 19 | 20 | Console.Error.WriteLine($"Directory not found: {dir}"); 21 | Environment.Exit(-1); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AssetCatalog/Converters/PixelsToGridLengthConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace AssetCatalog.Converters 7 | { 8 | public class PixelsToGridLengthConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | return new GridLength((double) value); 13 | } 14 | 15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Prism/Prism.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Component 6 | 7 | 8 | Form 9 | 10 | 11 | Form 12 | 13 | 14 | Form 15 | 16 | 17 | -------------------------------------------------------------------------------- /Sandbox/Sandbox.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /RainbowScimitar/Extensions/StreamExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RainbowScimitar.Extensions 5 | { 6 | public static class StreamExt 7 | { 8 | public static int CopyStreamTo(this Stream src, Stream dest, int length, int buffSize = 8192) 9 | { 10 | var buffer = new byte[buffSize]; 11 | int read; 12 | 13 | var totalRead = 0; 14 | while (length > 0 && (read = src.Read(buffer, 0, Math.Min(buffer.Length, length))) > 0) 15 | { 16 | dest.Write(buffer, 0, read); 17 | length -= read; 18 | totalRead += read; 19 | } 20 | 21 | return totalRead; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/Character.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record Character() 9 | { 10 | public static Character Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.Character); 13 | 14 | var spawningCharacterSlot = r.ReadUid(); 15 | var subUids = r.ReadLengthPrefixedStructs(); 16 | 17 | var unk1 = r.ReadBytes(12); 18 | var selfUid = r.ReadUid(); 19 | 20 | var unk2 = r.ReadBytes(12); 21 | 22 | 23 | return null; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/CatalogStatusToTitleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AssetCatalog.Converters 6 | { 7 | public class CatalogStatusToTitleConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | return value is string status ? $"AssetCatalog - {status}" : "AssetCatalog"; 12 | } 13 | 14 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarFileHeader.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Scimitar 4 | { 5 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 6 | public readonly struct ScimitarFileHeader 7 | { 8 | public readonly short Unknown1; 9 | public readonly ScimitarFilePackMethod PackMethod; // TODO: is this two int16s? 10 | public readonly byte Unknown2; 11 | public readonly short Unknown3; 12 | 13 | public ScimitarFileHeader(ScimitarFilePackMethod packMethod) 14 | { 15 | PackMethod = packMethod; 16 | Unknown1 = 0; 17 | Unknown2 = 0; 18 | Unknown3 = 0; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Prism/GlControlContext.cs: -------------------------------------------------------------------------------- 1 | using OpenTK; 2 | using Prism.Render; 3 | 4 | namespace Prism 5 | { 6 | public class GlControlContext : IRenderContext 7 | { 8 | private readonly GLControl _glContext; 9 | 10 | /// 11 | public int Width => _glContext.Width; 12 | 13 | /// 14 | public int Height => _glContext.Height; 15 | 16 | /// 17 | public int DefaultFramebuffer => 0; 18 | 19 | /// 20 | public void MarkDirty() 21 | { 22 | _glContext.Invalidate(); 23 | } 24 | 25 | public GlControlContext(GLControl glContext) 26 | { 27 | _glContext = glContext; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Prism/Render/Shader/GenericShaderUniform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Prism.Render.Shader 4 | { 5 | public class GenericShaderUniform 6 | { 7 | public Type UniformType { get; } 8 | public string Name { get; set; } 9 | public object Value { get; set; } 10 | 11 | public GenericShaderUniform(Type type, string name) 12 | { 13 | UniformType = type; 14 | Name = name; 15 | } 16 | 17 | public GenericShaderUniform(Type type, string name, object value) 18 | { 19 | UniformType = type; 20 | Name = name; 21 | Value = value; 22 | } 23 | 24 | public virtual object GetValue() 25 | { 26 | return Value; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowForge/Core/Meta.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core 4 | { 5 | public class Meta 6 | { 7 | public MetaLink[] Links { get; } 8 | 9 | public Meta(MetaLink[] links) 10 | { 11 | Links = links; 12 | } 13 | 14 | public static Meta Read(BinaryReader r) 15 | { 16 | var length = r.BaseStream.Length; 17 | 18 | var numLinks = r.ReadUInt16(); 19 | 20 | var extra = (length - 2) / numLinks != 12 || (length - 2) % numLinks != 0; 21 | 22 | var links = new MetaLink[numLinks]; 23 | for (var i = 0; i < numLinks; i++) 24 | links[i] = MetaLink.Read(r, extra); 25 | 26 | return new Meta(links); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowForge/Model/SkeletonMirrorData.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Model 4 | { 5 | public class SkeletonMirrorData 6 | { 7 | public ulong Uid { get; } 8 | public byte[] Data { get; } 9 | 10 | private SkeletonMirrorData(ulong uid, byte[] data) 11 | { 12 | Uid = uid; 13 | Data = data; 14 | } 15 | 16 | public static SkeletonMirrorData Read(BinaryReader r) 17 | { 18 | var uid = r.ReadUInt64(); 19 | 20 | var magic = r.ReadUInt32(); 21 | MagicHelper.AssertEquals(Magic.SkeletonMirrorData, magic); 22 | 23 | var data = r.ReadBytes(8); 24 | 25 | return new SkeletonMirrorData(uid, data); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/ForgeToTitleMsgConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | 5 | namespace AssetCatalog.Converters 6 | { 7 | public class ForgeToTitleMsgConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 10 | { 11 | if (value is not Forge forge) 12 | return ""; 13 | 14 | return $"{forge.NumEntries} entries, v{forge.Version}"; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Prism/Resources/ResourceHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | 4 | namespace Prism.Resources 5 | { 6 | public static class ResourceHelper 7 | { 8 | public static Stream GetResource(string filename) 9 | { 10 | var assembly = Assembly.GetExecutingAssembly(); 11 | return assembly.GetManifestResourceStream("Prism.Resources." + filename); 12 | } 13 | 14 | public static string ReadResource(string filename) 15 | { 16 | var assembly = Assembly.GetExecutingAssembly(); 17 | using var stream = new StreamReader(assembly.GetManifestResourceStream("Prism.Resources." + filename)); 18 | return stream.ReadToEnd(); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Shader/GenericShaderUniform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AssetCatalog.Render.Shader 4 | { 5 | public class GenericShaderUniform 6 | { 7 | public Type UniformType { get; } 8 | public string Name { get; set; } 9 | public object Value { get; set; } 10 | 11 | public GenericShaderUniform(Type type, string name) 12 | { 13 | UniformType = type; 14 | Name = name; 15 | } 16 | 17 | public GenericShaderUniform(Type type, string name, object value) 18 | { 19 | UniformType = type; 20 | Name = name; 21 | Value = value; 22 | } 23 | 24 | public virtual object GetValue() 25 | { 26 | return Value; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WorldLoadUnitWorldDivision.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record WorldLoadUnitWorldDivision(ScimitarId WorldSectionDivisionLookupUid, ScimitarId WorldSectionUid) 9 | { 10 | public static WorldLoadUnitWorldDivision Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.WorldLoadUnit_WorldDivision); 13 | 14 | var worldSectionDivisionLookupUid = r.ReadUid(); 15 | var worldSectionUid = r.ReadUid(); 16 | 17 | return new WorldLoadUnitWorldDivision(worldSectionDivisionLookupUid, worldSectionUid); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/BooleanToVisibilityConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace AssetCatalog.Converters 7 | { 8 | public class BooleanToVisibilityConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value is not bool b) 13 | return Visibility.Visible; 14 | return b ? Visibility.Visible : Visibility.Collapsed; 15 | } 16 | 17 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 18 | { 19 | throw new NotImplementedException(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /RainbowForge/Model/BoneInitialTransforms.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge.Structs; 3 | 4 | namespace RainbowForge.Model 5 | { 6 | public class BoneInitialTransforms 7 | { 8 | public Matrix4F Transformation { get; private set; } 9 | 10 | private BoneInitialTransforms(Matrix4F transformation) 11 | { 12 | Transformation = transformation; 13 | } 14 | 15 | public static BoneInitialTransforms Read(BinaryReader r) 16 | { 17 | var magic = r.ReadUInt32(); 18 | MagicHelper.AssertEquals(Magic.BoneInitialTransforms, magic); 19 | 20 | var transformation = r.ReadStruct(Matrix4F.SizeInBytes); 21 | 22 | return new BoneInitialTransforms(transformation); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /DumpTool/ListCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLine; 3 | using RainbowForge; 4 | using RainbowForge.Core; 5 | 6 | namespace DumpTool 7 | { 8 | [Verb("list", HelpText = "List all of the UIDs in the given container")] 9 | internal class ListCommand 10 | { 11 | [Value(0, HelpText = "The forge file to reference")] 12 | public string ForgeFilename { get; set; } 13 | 14 | public static void Run(ListCommand args) 15 | { 16 | var forge = Forge.GetForge(args.ForgeFilename); 17 | 18 | foreach (var forgeEntry in forge.Entries) 19 | { 20 | var magic = MagicHelper.GetFiletype(forgeEntry.MetaData.FileType); 21 | Console.WriteLine($"{forgeEntry.Uid}: {magic}"); 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/SpaceManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | 5 | namespace RainbowScimitar.DataTypes 6 | { 7 | public record SpaceManager(SpaceComponentNode[] Children) 8 | { 9 | public static SpaceManager Read(BinaryReader r) 10 | { 11 | r.ReadMagic(Magic.SpaceManager); 12 | 13 | var numChildren = r.ReadInt32(); 14 | 15 | var children = new SpaceComponentNode[numChildren]; 16 | for (var i = 0; i < numChildren; i++) 17 | { 18 | var zero = r.ReadByte(); 19 | var childInternalUid = r.ReadUid(); 20 | children[i] = SpaceComponentNode.Read(r); 21 | } 22 | 23 | return new SpaceManager(children); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /RainbowForge/Core/Entry.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core 4 | { 5 | public class Entry 6 | { 7 | public ulong Uid { get; } 8 | public long Offset { get; } 9 | public uint Size { get; } 10 | public long End { get; } 11 | 12 | public EntryMetaData MetaData { get; set; } 13 | 14 | public Entry(ulong uid, long offset, uint size, long end) 15 | { 16 | Uid = uid; 17 | Offset = offset; 18 | Size = size; 19 | End = end; 20 | } 21 | 22 | public static Entry Read(BinaryReader r) 23 | { 24 | var offset = r.ReadInt64(); 25 | var uid = r.ReadUInt64(); 26 | var size = r.ReadUInt32(); 27 | var end = offset + size; 28 | 29 | return new Entry(uid, offset, size, end); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /RainbowForge/Compression/OodleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace RainbowForge.Compression 6 | { 7 | public static class OodleHelper 8 | { 9 | private static bool _oodleLoaded = false; 10 | 11 | public static void EnsureOodleLoaded() 12 | { 13 | if (_oodleLoaded) 14 | return; 15 | 16 | var libraryPath = Environment.GetEnvironmentVariable("OODLE2_8_PATH"); 17 | 18 | if (libraryPath == null || !File.Exists(libraryPath)) 19 | throw new Exception("Failed to locate Oodle library file! Make sure the OODLE2_8_PATH path is set to oo2core_8_win64.dll's path"); 20 | 21 | NativeLibrary.Load(libraryPath); 22 | 23 | _oodleLoaded = true; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /RainbowScimitar/Compression/OodleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace RainbowScimitar.Compression 6 | { 7 | public static class OodleHelper 8 | { 9 | private static bool _oodleLoaded = false; 10 | 11 | public static void EnsureOodleLoaded() 12 | { 13 | if (_oodleLoaded) 14 | return; 15 | 16 | var libraryPath = Environment.GetEnvironmentVariable("OODLE2_8_PATH"); 17 | 18 | if (libraryPath == null || !File.Exists(libraryPath)) 19 | throw new Exception("Failed to locate Oodle library file! Make sure the OODLE2_8_PATH path is set to oo2core_8_win64.dll's path"); 20 | 21 | NativeLibrary.Load(libraryPath); 22 | 23 | _oodleLoaded = true; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /AssetCatalog/Resources/model.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 position; 5 | layout(location = 1) in vec3 normal; 6 | layout(location = 2) in vec2 texCoord; 7 | 8 | out vec3 fragPos; 9 | out vec3 fragNormal; 10 | out vec2 fragTexCoord; 11 | 12 | out mat4 model; 13 | out mat4 view; 14 | out mat4 proj; 15 | 16 | uniform mat4 m; 17 | uniform mat4 v; 18 | uniform mat4 p; 19 | 20 | void main() 21 | { 22 | fragNormal = normalize(normal); 23 | fragTexCoord = texCoord; 24 | 25 | model = m; 26 | view = v; 27 | proj = p; 28 | 29 | mat4 MVP = p*v*m; 30 | gl_Position = MVP * vec4(position, 1.); 31 | fragPos = vec3(v * vec4(position, 1.0)); 32 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/ForgeEntryToListDescConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using RainbowForge.Core; 5 | 6 | namespace AssetCatalog.Converters 7 | { 8 | public class ForgeEntryToListDescConverter : IValueConverter 9 | { 10 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 11 | { 12 | if (value is not Entry forgeEntry) 13 | return "Invalid Entry"; 14 | 15 | var catalogEntry = ForgeCatalog.Instance.CatalogDb.Get(forgeEntry.Uid); 16 | 17 | return catalogEntry.Name; 18 | } 19 | 20 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 21 | { 22 | throw new NotImplementedException(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /AssetCatalog/Model/ICatalogApp.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using RainbowForge.Core; 4 | 5 | namespace AssetCatalog.Model 6 | { 7 | public interface ICatalogApp 8 | { 9 | public string Status { get; set; } 10 | 11 | public ICatalogDb CatalogDb { get; } 12 | public Forge OpenedForge { get; } 13 | 14 | public bool IsEntrySelected { get; } 15 | public Entry SelectedEntry { get; set; } 16 | public CatalogEntry SelectedCatalogEntry { get; } 17 | 18 | public IEnumerable FilteredEntries { get; } 19 | 20 | public void OpenForge(Stream stream); 21 | 22 | void SetFilter(CatalogEntryStatus[] filteredStatuses, CatalogAssetCategory[] filteredCategories, ulong uidFilter, string nameFilter); 23 | void OnCatalogChanged(); 24 | } 25 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/GIBoundingVolume.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Model; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record GIBoundingVolume(Box Bounds, Vec3f Unknown1, int Unknown2, int Unknown3, int Unknown4, float Unknown5, int Unknown6) 9 | { 10 | public static GIBoundingVolume Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.GIBoundingVolume); 13 | 14 | var box = r.ReadStruct(); 15 | var vec = r.ReadStruct(); 16 | 17 | var a = r.ReadInt32(); 18 | var b = r.ReadInt32(); 19 | var c = r.ReadInt32(); 20 | var d = r.ReadSingle(); 21 | var e = r.ReadInt32(); 22 | 23 | return new GIBoundingVolume(box, vec, a, b, c, d, e); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /RainbowForge.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | 2 | ForceIncluded 3 | True 4 | True 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RainbowForge/Model/MeshObjectContainer.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | 3 | namespace RainbowForge.Model 4 | { 5 | public class MeshObjectContainer 6 | { 7 | public Vector3[] Vertices { get; init; } 8 | public Vector3[] Normals { get; init; } 9 | public Vector3[] Tangents { get; init; } 10 | public Vector3[] Binormals { get; init; } 11 | public Vector2[] TexCoords { get; init; } 12 | public Color4[,] Colors { get; init; } 13 | } 14 | 15 | public class Color4 16 | { 17 | public Color4(float red, float green, float blue, float alpha) 18 | { 19 | R = red; 20 | G = green; 21 | B = blue; 22 | A = alpha; 23 | } 24 | 25 | public float R { get; set; } 26 | public float G { get; set; } 27 | public float B { get; set; } 28 | public float A { get; set; } 29 | } 30 | } -------------------------------------------------------------------------------- /AssetCatalog/Resources/model.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | uniform vec3 lightPos; 4 | uniform sampler2D texModel; 5 | uniform int isTexture; 6 | 7 | in vec3 fragPos; 8 | in vec3 fragNormal; 9 | in vec2 fragTexCoord; 10 | 11 | out vec4 color; 12 | 13 | void main() 14 | { 15 | vec4 samp = texture(texModel, fragTexCoord); 16 | 17 | vec3 norm = normalize(fragNormal); 18 | vec3 lightDir = normalize(lightPos);// light very far away, use direction only. If light has position: normalize(lightPos - fragPos) 19 | float diffuse = clamp(dot(norm, lightDir), -1, 1) * 0.3; 20 | float ambient = 0.7; 21 | 22 | if (isTexture > 0) 23 | color = mix(vec4(vec3(0.0), 1.0), vec4(samp.rgb, 1.0), samp.a); 24 | else 25 | color = vec4(vec3(1.0) * clamp(ambient + diffuse, 0, 1), 1.0); 26 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WorldDivisionToTagLoadUnitLookup.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record WorldDivisionToTagLoadUnitLookup(ScimitarId WorldSectionUid, WorldLoadUnitLoadByTag WorldLoadUnitLoadByTag) 9 | { 10 | public static WorldDivisionToTagLoadUnitLookup Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.WorldDivisionToTagLoadUnitLookup); 13 | 14 | var worldSectionUid = r.ReadUid(); 15 | 16 | var zero = r.ReadByte(); 17 | var worldLoadUnitLoadByTagUid = r.ReadUid(); 18 | var worldLoadUnitLoadByTag = WorldLoadUnitLoadByTag.Read(r); 19 | 20 | return new WorldDivisionToTagLoadUnitLookup(worldSectionUid, worldLoadUnitLoadByTag); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/SpaceComponentNode.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record SpaceComponentNode(ScimitarId Id, SpaceComponentNode[] Children) 9 | { 10 | public static SpaceComponentNode Read(BinaryReader r) 11 | { 12 | r.ReadMagic(Magic.SpaceComponentNode); 13 | 14 | var numChildren = r.ReadInt32(); 15 | 16 | var children = new SpaceComponentNode[numChildren]; 17 | for (var i = 0; i < numChildren; i++) 18 | { 19 | var zero = r.ReadByte(); 20 | var childInternalUid = r.ReadUid(); 21 | children[i] = SpaceComponentNode.Read(r); 22 | } 23 | 24 | var uid = r.ReadUid(); 25 | 26 | return new SpaceComponentNode(uid, children); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowForge/Components/LocalizationPackage.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Components 4 | { 5 | public class LocalizationPackage 6 | { 7 | public FileMetaData FileMeta { get; } 8 | public CompressedLocalizationData Data { get; } 9 | 10 | private LocalizationPackage(FileMetaData fileMeta, CompressedLocalizationData data) 11 | { 12 | FileMeta = fileMeta; 13 | Data = data; 14 | } 15 | 16 | public static LocalizationPackage Read(BinaryReader r, uint version) 17 | { 18 | var fileMeta = FileMetaData.Read(r, version); 19 | 20 | var secondMagic = r.ReadUInt32(); 21 | MagicHelper.AssertEquals(Magic.LocalizationPackage, secondMagic); 22 | 23 | r.ReadBytes(8); // padding 24 | 25 | var cld = CompressedLocalizationData.Read(r); 26 | 27 | return new LocalizationPackage(fileMeta, cld); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /DumpTool/DumpTool.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 1.3.1 7 | 1.3.1 8 | 9 | https://github.com/parzivail/RainbowForge 10 | parzivail 11 | 1.3.1 12 | DumpTool 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/TextureMapSpec.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.RenderPipeline 4 | { 5 | public class TextureMapSpec 6 | { 7 | public ulong TextureMapUid { get; } 8 | public uint TextureType { get; } 9 | public byte[] ExtraData { get; } 10 | 11 | private TextureMapSpec(ulong textureMapUid, uint textureType, byte[] extraData) 12 | { 13 | TextureMapUid = textureMapUid; 14 | TextureType = textureType; 15 | ExtraData = extraData; 16 | } 17 | 18 | public static TextureMapSpec Read(BinaryReader r) 19 | { 20 | var magic = r.ReadUInt32(); 21 | // MagicHelper.AssertEquals(Magic.TextureMapSpec, magic); 22 | 23 | var mipUid = r.ReadUInt64(); 24 | var textureType = r.ReadUInt32(); 25 | 26 | var extraData = r.ReadBytes(3); 27 | 28 | return new TextureMapSpec(mipUid, textureType, extraData); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /DumpTool/DumpAllCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CommandLine; 3 | using RainbowForge.Core; 4 | using RainbowForge.Dump; 5 | 6 | namespace DumpTool 7 | { 8 | [Verb("dumpall", HelpText = "Dumps all assets in the given forge file")] 9 | public class DumpAllCommand 10 | { 11 | [Value(0, HelpText = "The forge file to reference")] 12 | public string ForgeFilename { get; set; } 13 | 14 | public static void Run(DumpAllCommand args) 15 | { 16 | var forge = Forge.GetForge(args.ForgeFilename); 17 | 18 | foreach (var entry in forge.Entries) 19 | { 20 | try 21 | { 22 | DumpHelper.Dump(forge, entry, Environment.CurrentDirectory); 23 | Console.Error.WriteLine($"Dumped UID {entry.Uid}"); 24 | } 25 | catch (Exception e) 26 | { 27 | Console.Error.WriteLine($"Error while dumping: UID {entry.Uid}\n{e}"); 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /RainbowForge/Link/UidLinkEntry.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Link 4 | { 5 | public class UidLinkEntry 6 | { 7 | public uint InternalUid { get; } 8 | public UidLinkNode UidLinkNode1 { get; } 9 | public UidLinkNode UidLinkNode2 { get; } 10 | 11 | private UidLinkEntry(uint internalUid, UidLinkNode uidLinkNode1, UidLinkNode uidLinkNode2) 12 | { 13 | InternalUid = internalUid; 14 | UidLinkNode1 = uidLinkNode1; 15 | UidLinkNode2 = uidLinkNode2; 16 | } 17 | 18 | public static UidLinkEntry Read(BinaryReader r, bool hasDoubles) 19 | { 20 | var internalUid = r.ReadUInt32(); 21 | var uidLinkNode1 = UidLinkNode.Read(r); 22 | 23 | UidLinkNode uidLinkNode2 = null; 24 | 25 | if (hasDoubles) 26 | uidLinkNode2 = UidLinkNode.Read(r); 27 | 28 | return new UidLinkEntry(internalUid, uidLinkNode1, uidLinkNode2); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarArchiveFileData.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowScimitar.Scimitar 4 | { 5 | public record ScimitarArchiveFileData(ScimitarId Id, int Length, long Offset) 6 | { 7 | public static ScimitarArchiveFileData Read(BinaryReader r) 8 | { 9 | var uid = (ScimitarId)r.ReadUInt64(); 10 | var length = r.ReadInt32(); 11 | 12 | // if (r.BaseStream.Position == r.BaseStream.Length) 13 | return new ScimitarArchiveFileData(uid, length, 0); 14 | // 15 | // r.ba 16 | // 17 | // var numUnknown = r.ReadInt16(); 18 | // if (numUnknown <= 0) 19 | // return new ScimitarSubFileData(uid, length, Array.Empty()); 20 | // 21 | // var unknowns = new short[numUnknown]; 22 | // 23 | // for (var i = 0; i < numUnknown; i++) 24 | // unknowns[i] = r.ReadInt16(); 25 | // 26 | // return new ScimitarSubFileData(uid, length, unknowns); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Prism/Extensions/PaintSuspenderExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Windows.Forms; 4 | 5 | namespace Prism.Extensions 6 | { 7 | internal static class PaintSuspenderExtension 8 | { 9 | [DllImport("user32.dll")] 10 | private static extern int SendMessage(IntPtr hWnd, int wMsg, bool wParam, int lParam); 11 | 12 | private const int WmSetRedraw = 11; 13 | 14 | public static IDisposable SuspendPainting(this Control ctrl) 15 | { 16 | return new PaintSuspender(ctrl); 17 | } 18 | 19 | private class PaintSuspender : IDisposable 20 | { 21 | private readonly Control _ctrl; 22 | 23 | public PaintSuspender(Control ctrl) 24 | { 25 | _ctrl = ctrl; 26 | SendMessage(_ctrl.Handle, WmSetRedraw, false, 0); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | SendMessage(_ctrl.Handle, WmSetRedraw, true, 0); 32 | _ctrl.Refresh(); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RainbowForge/BufferUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace RainbowForge 6 | { 7 | public static class BufferUtil 8 | { 9 | public static T ToStruct(this byte[] buffer) where T : struct 10 | { 11 | var temp = new T(); 12 | var size = Marshal.SizeOf(temp); 13 | var ptr = Marshal.AllocHGlobal(size); 14 | 15 | Marshal.Copy(buffer, 0, ptr, size); 16 | 17 | var ret = (T) Marshal.PtrToStructure(ptr, temp.GetType()); 18 | Marshal.FreeHGlobal(ptr); 19 | 20 | return ret; 21 | } 22 | 23 | public static void CopyStream(this Stream input, Stream output, int length, int buffSize = 8192) 24 | { 25 | var buffer = new byte[buffSize]; 26 | int read; 27 | while (length > 0 && (read = input.Read(buffer, 0, Math.Min(buffer.Length, length))) > 0) 28 | { 29 | output.Write(buffer, 0, read); 30 | length -= read; 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DumpTool/DumpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandLine; 4 | using RainbowForge.Core; 5 | using RainbowForge.Dump; 6 | 7 | namespace DumpTool 8 | { 9 | [Verb("dump", HelpText = "Dumps the given asset")] 10 | public class DumpCommand 11 | { 12 | [Value(0, HelpText = "The forge file to reference")] 13 | public string ForgeFilename { get; set; } 14 | 15 | [Value(1, HelpText = "The UID to dump")] 16 | public ulong Uid { get; set; } 17 | 18 | public static void Run(DumpCommand args) 19 | { 20 | var forge = Forge.GetForge(args.ForgeFilename); 21 | 22 | try 23 | { 24 | var metaEntry = forge.Entries.First(entry1 => entry1.Uid == args.Uid); 25 | DumpHelper.Dump(forge, metaEntry, Environment.CurrentDirectory); 26 | 27 | Console.Error.WriteLine($"Dumped UID {args.Uid}"); 28 | } 29 | catch (Exception e) 30 | { 31 | Console.Error.WriteLine($"Error while dumping: {e}"); 32 | } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /RainbowForge/BoundingBox.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge 4 | { 5 | public class BoundingBox 6 | { 7 | public float MinX { get; } 8 | public float MinY { get; } 9 | public float MinZ { get; } 10 | public float MaxX { get; } 11 | public float MaxY { get; } 12 | public float MaxZ { get; } 13 | 14 | private BoundingBox(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) 15 | { 16 | MinX = minX; 17 | MinY = minY; 18 | MinZ = minZ; 19 | MaxX = maxX; 20 | MaxY = maxY; 21 | MaxZ = maxZ; 22 | } 23 | 24 | public static BoundingBox Read(BinaryReader r) 25 | { 26 | // TODO: These might not be in this order 27 | var minX = r.ReadSingle(); 28 | var minY = r.ReadSingle(); 29 | var minZ = r.ReadSingle(); 30 | 31 | var maxX = r.ReadSingle(); 32 | var maxY = r.ReadSingle(); 33 | var maxZ = r.ReadSingle(); 34 | 35 | return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /RainbowForge/Core/MetaLink.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core 4 | { 5 | public class MetaLink 6 | { 7 | public ulong Uid { get; } 8 | public uint Size { get; } 9 | public ulong Un1 { get; } 10 | public ulong Un2 { get; } 11 | public bool Extra { get; } 12 | 13 | private MetaLink(ulong uid, uint size) 14 | { 15 | Uid = uid; 16 | Size = size; 17 | Un1 = 0; 18 | Un2 = 0; 19 | Extra = false; 20 | } 21 | 22 | private MetaLink(ulong uid, uint size, ulong un1, ulong un2) 23 | { 24 | Uid = uid; 25 | Size = size; 26 | Un1 = un1; 27 | Un2 = un2; 28 | Extra = true; 29 | } 30 | 31 | public static MetaLink Read(BinaryReader r, bool extra) 32 | { 33 | var uid = r.ReadUInt64(); 34 | var size = r.ReadUInt32(); 35 | 36 | if (!extra) 37 | return new MetaLink(uid, size); 38 | 39 | var un1 = r.ReadUInt64(); 40 | var un2 = r.ReadUInt64(); 41 | return new MetaLink(uid, size, un1, un2); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Prism/Resources/bone.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.80 (sub 75) OBJ File: '' 2 | # www.blender.org 3 | mtllib bone.mtl 4 | o Cone.001 5 | v 0.200000 0.176777 -0.176777 6 | v 1.000000 0.000000 0.000000 7 | v 0.200000 -0.176777 -0.176777 8 | v 0.200000 -0.176777 0.176777 9 | v 0.200000 0.176777 0.176777 10 | v 0.000000 0.000000 0.000000 11 | vt 0.250000 0.490000 12 | vt 0.250000 0.250000 13 | vt 0.490000 0.250000 14 | vt 0.250000 0.010000 15 | vt 0.010000 0.250000 16 | vt 0.250000 0.490000 17 | vt 0.250000 0.250000 18 | vt 0.250000 0.010000 19 | vn 0.2158 0.0000 -0.9764 20 | vn 0.2158 -0.9764 0.0000 21 | vn 0.2158 0.0000 0.9764 22 | vn 0.2158 0.9764 -0.0000 23 | vn -0.6623 -0.7493 -0.0000 24 | vn -0.6623 -0.0000 -0.7493 25 | vn -0.6623 0.7493 -0.0000 26 | vn -0.6623 -0.0000 0.7493 27 | usemtl None.001 28 | s 1 29 | f 1/1/1 2/2/1 3/3/1 30 | f 3/3/2 2/2/2 4/4/2 31 | f 4/4/3 2/2/3 5/5/3 32 | f 5/5/4 2/2/4 1/1/4 33 | f 4/6/5 6/7/5 3/3/5 34 | f 3/3/6 6/7/6 1/8/6 35 | f 1/8/7 6/7/7 5/5/7 36 | f 5/5/8 6/7/8 4/6/8 37 | -------------------------------------------------------------------------------- /RainbowForge/Core/NameEncoding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowForge.Core 4 | { 5 | public class NameEncoding 6 | { 7 | public const ulong FILENAME_ENCODING_BASE_KEY = 0xA860F0ECDE3339FB; 8 | public const ulong FILENAME_ENCODING_ENTRY_KEY_STEP = 0x357267C76FFB9EB2; 9 | public const ulong FILENAME_ENCODING_FILE_KEY_STEP = 0xE684BFF857699452; 10 | 11 | public static byte[] DecodeName(byte[] name, uint fileType, ulong uid, ulong dataOffset, ulong keyStep) 12 | { 13 | var key = FILENAME_ENCODING_BASE_KEY + uid + dataOffset + fileType + ((ulong)fileType << 32); 14 | 15 | var blocks = (name.Length + 8) / 8; 16 | 17 | var output = new ulong[blocks]; 18 | Buffer.BlockCopy(name, 0, output, 0, name.Length); 19 | 20 | for (var i = 0; i < blocks; i++) 21 | { 22 | output[i] ^= key; 23 | key += keyStep; 24 | } 25 | 26 | var bytes = new byte[name.Length]; 27 | Buffer.BlockCopy(output, 0, bytes, 0, bytes.Length); 28 | return bytes; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/ForgeEntryToIconConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using RainbowForge; 5 | using RainbowForge.Core; 6 | 7 | namespace AssetCatalog.Converters 8 | { 9 | public class ForgeEntryToIconConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is not Entry forgeEntry) 14 | return "\uE7BA"; 15 | 16 | var magic = MagicHelper.GetFiletype(forgeEntry.MetaData.FileType); 17 | 18 | switch (magic) 19 | { 20 | case AssetType.Mesh: 21 | return "\uF158"; 22 | case AssetType.Texture: 23 | return "\uEB9F"; 24 | case AssetType.Sound: 25 | return "\uE767"; 26 | default: 27 | return "\uE9CE"; 28 | } 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/TextureSelector.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.RenderPipeline 4 | { 5 | public class TextureSelector 6 | { 7 | public ulong EntryUid { get; } 8 | public ulong TextureMapSpecUid { get; } 9 | public uint Var1 { get; } 10 | public uint MipTarget { get; } 11 | 12 | private TextureSelector(ulong entryUid, ulong textureMapSpecUid, uint var1, uint mipTarget) 13 | { 14 | EntryUid = entryUid; 15 | TextureMapSpecUid = textureMapSpecUid; 16 | Var1 = var1; 17 | MipTarget = mipTarget; 18 | } 19 | 20 | public static TextureSelector Read(BinaryReader r) 21 | { 22 | var entryUid = r.ReadUInt64(); 23 | 24 | var magic = r.ReadUInt32(); 25 | MagicHelper.AssertEquals(Magic.TextureSelector, magic); 26 | 27 | var mipContainerUid = r.ReadUInt64(); 28 | 29 | var var1 = r.ReadUInt32(); 30 | var mipTarget = r.ReadUInt32(); 31 | 32 | return new TextureSelector(entryUid, mipContainerUid, var1, mipTarget); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /DumpTool/Program.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace DumpTool 4 | { 5 | internal class Program 6 | { 7 | private static void Main(string[] args) 8 | { 9 | Parser.Default.ParseArguments(args) 11 | .WithParsed(ListCommand.Run) 12 | .WithParsed(FindCommand.Run) 13 | .WithParsed(InspectCommand.Run) 14 | .WithParsed(DumpCommand.Run) 15 | .WithParsed(DumpAllCommand.Run) 16 | .WithParsed(DumpMeshPropsCommand.Run) 17 | .WithParsed(DumpAllMeshPropsCommand.Run) 18 | .WithParsed(FindAllMeshPropsCommand.Run) 19 | .WithParsed(FindAllMeshPropsGlobalCommand.Run) 20 | .WithParsed(IndexCommand.Run); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/CompiledSoundMedia.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowScimitar.Extensions; 3 | using RainbowScimitar.Scimitar; 4 | 5 | namespace RainbowScimitar.DataTypes 6 | { 7 | public record CompiledSoundMedia() 8 | { 9 | public static CompiledSoundMedia Read(BinaryReader r) 10 | { 11 | var fmeta = r.ReadStruct(); 12 | 13 | var secondMagic = r.ReadUInt32(); 14 | var var1 = r.ReadUInt32(); 15 | var var2 = r.ReadUInt32(); 16 | 17 | var payloadLengthA = r.ReadInt32(); 18 | var payloadPosA = r.BaseStream.Position; 19 | // var payloadA = r.ReadBytes(payloadLengthA); 20 | r.BaseStream.Seek(payloadLengthA, SeekOrigin.Current); 21 | 22 | var internalUid = r.ReadUInt64(); 23 | var var3 = r.ReadUInt32(); 24 | 25 | var payloadLengthB = r.ReadInt32(); 26 | var payloadPosB = r.BaseStream.Position; 27 | // var payloadB = r.ReadBytes(payloadLengthB); 28 | r.BaseStream.Seek(payloadLengthB, SeekOrigin.Current); 29 | 30 | return new CompiledSoundMedia(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Prism/Resources/screen.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoords; 5 | 6 | uniform sampler2DMS texScene; 7 | uniform int width; 8 | uniform int height; 9 | uniform int samplesScene; 10 | 11 | vec4 mtexture(sampler2DMS s, vec2 coords, int samp) 12 | { 13 | ivec2 vpCoords = ivec2(width, height); 14 | vpCoords.x = int(vpCoords.x * coords.x); 15 | vpCoords.y = int(vpCoords.y * coords.y); 16 | 17 | vec4 avg = vec4(0); 18 | for (int i = 0; i < samp; i++) 19 | { 20 | avg += texelFetch(s, vpCoords, i); 21 | } 22 | return avg / float(samp); 23 | } 24 | 25 | float linearDepth(float depthSample) 26 | { 27 | const float zNear = 1; 28 | const float zFar = 1024; 29 | depthSample = 2.0 * depthSample - 1.0; 30 | float zLinear = 2.0 * zNear * zFar / (zFar + zNear - depthSample * (zFar - zNear)); 31 | return zLinear; 32 | } 33 | 34 | void main() 35 | { 36 | vec4 color = mtexture(texScene, TexCoords, samplesScene); 37 | FragColor = color;//vec4(TexCoords.x, TexCoords.y, 0., 1.); 38 | } -------------------------------------------------------------------------------- /AssetCatalog/Resources/screen.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoords; 5 | 6 | uniform sampler2DMS texScene; 7 | uniform int width; 8 | uniform int height; 9 | uniform int samplesScene; 10 | 11 | vec4 mtexture(sampler2DMS s, vec2 coords, int samp) 12 | { 13 | ivec2 vpCoords = ivec2(width, height); 14 | vpCoords.x = int(vpCoords.x * coords.x); 15 | vpCoords.y = int(vpCoords.y * coords.y); 16 | 17 | vec4 avg = vec4(0); 18 | for (int i = 0; i < samp; i++) 19 | { 20 | avg += texelFetch(s, vpCoords, i); 21 | } 22 | return avg / float(samp); 23 | } 24 | 25 | float linearDepth(float depthSample) 26 | { 27 | const float zNear = 1; 28 | const float zFar = 1024; 29 | depthSample = 2.0 * depthSample - 1.0; 30 | float zLinear = 2.0 * zNear * zFar / (zFar + zNear - depthSample * (zFar - zNear)); 31 | return zLinear; 32 | } 33 | 34 | void main() 35 | { 36 | vec4 color = mtexture(texScene, TexCoords, samplesScene); 37 | FragColor = color;//vec4(TexCoords.x, TexCoords.y, 0., 1.); 38 | } -------------------------------------------------------------------------------- /Prism/Render/Camera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK; 3 | 4 | namespace Prism.Render 5 | { 6 | public class Camera 7 | { 8 | public Vector3 Position = new(64, 64, 64); 9 | public Vector2 Rotation = new(-45, 45); 10 | 11 | public float FieldOfView { get; set; } = 70; 12 | 13 | public Matrix4 GetRotationMatrix() 14 | { 15 | return Matrix4.CreateRotationY((float)(Rotation.X / 180 * Math.PI)) * 16 | Matrix4.CreateRotationX((float)(Rotation.Y / 180 * Math.PI)); 17 | } 18 | 19 | public Matrix4 GetTranslationMatrix() 20 | { 21 | return Matrix4.CreateTranslation(-Position); 22 | } 23 | 24 | public void Move(Vector3 direction, float speed = 1, bool local = true) 25 | { 26 | if (!local) 27 | { 28 | Position += direction * speed; 29 | return; 30 | } 31 | 32 | var matrix = GetRotationMatrix(); 33 | var offset = matrix * new Vector4(direction); 34 | Position += offset.Xyz * speed; 35 | } 36 | 37 | public Matrix4 GetTransformation() 38 | { 39 | return GetTranslationMatrix() * GetRotationMatrix(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/EnumDescriptionTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Globalization; 4 | 5 | namespace AssetCatalog.Converters 6 | { 7 | public class EnumDescriptionTypeConverter : EnumConverter 8 | { 9 | public EnumDescriptionTypeConverter(Type type) 10 | : base(type) 11 | { 12 | } 13 | 14 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 15 | { 16 | if (destinationType != typeof(string)) 17 | return base.ConvertTo(context, culture, value, destinationType); 18 | 19 | if (value == null) 20 | return string.Empty; 21 | 22 | var fi = value.GetType().GetField(value.ToString()); 23 | 24 | if (fi == null) 25 | return string.Empty; 26 | 27 | var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false); 28 | 29 | if (attributes.Length > 0 && !string.IsNullOrEmpty(attributes[0].Description)) 30 | return attributes[0].Description; 31 | 32 | return value.ToString(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarFilePackMethod.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowScimitar.Scimitar 2 | { 3 | public enum ScimitarFilePackMethod : short 4 | { 5 | /// 6 | /// Indicates the Block strategy for packing file data chunks. The asset 7 | /// serializes the list of block sizes, and each block is accompanied by 8 | /// a checksum at the start of each block. Blocks are compressed by Zstd. 9 | /// 10 | BlockZstd = 3, 11 | 12 | /// 13 | /// Indicates the Block strategy for packing file data chunks. The asset 14 | /// serializes the list of block sizes, and each block is accompanied by 15 | /// a checksum at the start of each block. Blocks are compressed by Oodle. 16 | /// 17 | BlockOodle = 13, 18 | 19 | /// 20 | /// Indicates the Streaming strategy for packing file data chunks. The 21 | /// asset serializes the list of block sizes as well as the checksums, 22 | /// and blocks are tightly packed with no interruptions. Compression 23 | /// is not supported for blocks. 24 | /// 25 | Streaming = 7 26 | } 27 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/ForgeEntryToCatalogIconConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using AssetCatalog.Model; 5 | using RainbowForge.Core; 6 | 7 | namespace AssetCatalog.Converters 8 | { 9 | public class ForgeEntryToCatalogIconConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | if (value is not Entry forgeEntry) 14 | return "\uE7BA"; 15 | 16 | var catalogEntry = ForgeCatalog.Instance.CatalogDb.Get(forgeEntry.Uid); 17 | 18 | switch (catalogEntry.Status) 19 | { 20 | case CatalogEntryStatus.Incomplete: 21 | return ""; 22 | case CatalogEntryStatus.PartiallyComplete: 23 | return "\uE73C"; 24 | case CatalogEntryStatus.Complete: 25 | return "\uE73E"; 26 | default: 27 | throw new ArgumentOutOfRangeException(); 28 | } 29 | } 30 | 31 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RainbowForge/Archive/FlatArchiveEntry.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Archive 4 | { 5 | public class FlatArchiveEntry 6 | { 7 | public FileMetaData MetaData { get; } 8 | public int Index { get; } 9 | public long PayloadOffset { get; } 10 | public int PayloadLength { get; } 11 | 12 | private FlatArchiveEntry(FileMetaData metaData, int index, long payloadOffset, int payloadLength) 13 | { 14 | MetaData = metaData; 15 | Index = index; 16 | PayloadOffset = payloadOffset; 17 | PayloadLength = payloadLength; 18 | } 19 | 20 | public static FlatArchiveEntry Read(BinaryReader r, uint version, int index) 21 | { 22 | var meta = FileMetaData.Read(r, version); 23 | 24 | var length = meta.ContainerType - 8; // length - int64(uid) 25 | 26 | var payloadOffset = r.BaseStream.Position; 27 | 28 | if (r.BaseStream.Length < r.BaseStream.Position + length) 29 | throw new EndOfStreamException(); 30 | 31 | r.BaseStream.Seek(length, SeekOrigin.Current); 32 | 33 | return new FlatArchiveEntry(meta, index, payloadOffset, (int)length); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarFastLoadTableOfContents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace RainbowScimitar.Scimitar 6 | { 7 | public record ScimitarFastLoadTableOfContents(int Unknown1, ulong Unknown2, int Unknown3, ulong Unknown4, string Name) 8 | { 9 | public static ScimitarFastLoadTableOfContents Read(Stream bundleStream) 10 | { 11 | var r = new BinaryReader(bundleStream); 12 | 13 | var unk1 = r.ReadInt32(); // 6 14 | var unk2 = r.ReadUInt64(); // varies, hash? 15 | var unk3 = r.ReadInt32(); // 1 16 | var unk4 = r.ReadUInt64(); // varies, hash? 17 | var hasNameData = r.ReadInt32(); // 1 if contains name data, 0 otherwise 18 | 19 | string name = null; 20 | if (hasNameData == 1) 21 | { 22 | var nameBytes = r.ReadBytes(64); 23 | var zeroTerminatorPos = Array.IndexOf(nameBytes, (byte)0); 24 | name = Encoding.ASCII.GetString(nameBytes, 0, zeroTerminatorPos == -1 ? nameBytes.Length : zeroTerminatorPos); 25 | } 26 | 27 | return new ScimitarFastLoadTableOfContents(unk1, unk2, unk3, unk4, name); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Camera.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Mathematics; 3 | 4 | namespace AssetCatalog.Render 5 | { 6 | public class Camera 7 | { 8 | public Vector3 Position = new(64, 64, 64); 9 | public Vector2 Rotation = new(-45, 45); 10 | 11 | public float FieldOfView { get; set; } = 70; 12 | 13 | public Matrix4 GetRotationMatrix() 14 | { 15 | return Matrix4.CreateRotationY((float) (Rotation.X / 180 * Math.PI)) * 16 | Matrix4.CreateRotationX((float) (Rotation.Y / 180 * Math.PI)); 17 | } 18 | 19 | public Matrix4 GetTranslationMatrix() 20 | { 21 | return Matrix4.CreateTranslation(-Position); 22 | } 23 | 24 | public void Move(Vector3 direction, float speed = 1, bool local = true) 25 | { 26 | if (!local) 27 | { 28 | Position += direction * speed; 29 | return; 30 | } 31 | 32 | var matrix = GetRotationMatrix(); 33 | var offset = matrix * new Vector4(direction); 34 | Position += offset.Xyz * speed; 35 | } 36 | 37 | public Matrix4 GetTransformation() 38 | { 39 | return GetTranslationMatrix() * GetRotationMatrix(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /RainbowScimitar/Model/Matrix4f.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowScimitar.Model 4 | { 5 | /// 6 | /// Stores a column-major 4x4 matrix 7 | /// 8 | /// 9 | /// Member naming scheme is M(row)(column) 10 | /// 11 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 12 | public struct Matrix4f 13 | { 14 | public readonly float M11; 15 | public readonly float M21; 16 | public readonly float M31; 17 | public readonly float M41; 18 | public readonly float M12; 19 | public readonly float M22; 20 | public readonly float M32; 21 | public readonly float M42; 22 | public readonly float M13; 23 | public readonly float M23; 24 | public readonly float M33; 25 | public readonly float M43; 26 | public readonly float M14; 27 | public readonly float M24; 28 | public readonly float M34; 29 | public readonly float M44; 30 | 31 | /// 32 | public override string ToString() 33 | { 34 | return $"[{M11} {M12} {M13} {M14}; {M21} {M22} {M23} {M24}; {M31} {M32} {M33} {M34}; {M41} {M42} {M43} {M44}]"; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /AssetCatalog/Converters/ForgeEntryToCatalogIconBrushConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using System.Windows.Media; 5 | using AssetCatalog.Model; 6 | using RainbowForge.Core; 7 | 8 | namespace AssetCatalog.Converters 9 | { 10 | public class ForgeEntryToCatalogIconBrushConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 13 | { 14 | if (value is not Entry forgeEntry) 15 | return Brushes.DarkGray; 16 | 17 | var catalogEntry = ForgeCatalog.Instance.CatalogDb.Get(forgeEntry.Uid); 18 | 19 | switch (catalogEntry.Status) 20 | { 21 | case CatalogEntryStatus.Incomplete: 22 | return Brushes.Red; 23 | case CatalogEntryStatus.PartiallyComplete: 24 | return Brushes.Orange; 25 | case CatalogEntryStatus.Complete: 26 | return Brushes.LimeGreen; 27 | default: 28 | throw new ArgumentOutOfRangeException(); 29 | } 30 | } 31 | 32 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /DumpTool/DumpAllMeshPropsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CommandLine; 4 | using LiteDB; 5 | using RainbowForge; 6 | using RainbowForge.Core; 7 | 8 | namespace DumpTool 9 | { 10 | [Verb("dumpallmeshprops", HelpText = "Dumps all MeshProperties containers in all flat archives in the given forge file")] 11 | public class DumpAllMeshPropsCommand 12 | { 13 | [Value(0, HelpText = "The search index to use (see command: index)")] 14 | public string IndexFilename { get; set; } 15 | 16 | [Value(1, HelpText = "The forge file to reference")] 17 | public string ForgeFilename { get; set; } 18 | 19 | public static void Run(DumpAllMeshPropsCommand args) 20 | { 21 | FileSystemUtil.AssertFileExists(args.IndexFilename); 22 | var forge = Forge.GetForge(args.ForgeFilename); 23 | 24 | try 25 | { 26 | var db = new LiteDatabase(args.IndexFilename); 27 | 28 | foreach (var entry in forge.Entries) DumpMeshPropsCommand.ProcessFlatArchive(db, forge, entry, Environment.CurrentDirectory, Path.GetDirectoryName(args.ForgeFilename)); 29 | } 30 | catch (Exception e) 31 | { 32 | Console.Error.WriteLine($"Error while dumping: {e}"); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /RainbowForge/Model/TrianglePointer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowForge.Model 4 | { 5 | public class TrianglePointer 6 | { 7 | public ushort A { get; } 8 | public ushort B { get; } 9 | public ushort C { get; } 10 | 11 | public TrianglePointer(ushort a, ushort b, ushort c) 12 | { 13 | A = a; 14 | B = b; 15 | C = c; 16 | } 17 | 18 | protected bool Equals(TrianglePointer other) 19 | { 20 | return A == other.A && B == other.B && C == other.C; 21 | } 22 | 23 | /// 24 | public override bool Equals(object obj) 25 | { 26 | if (ReferenceEquals(null, obj)) return false; 27 | if (ReferenceEquals(this, obj)) return true; 28 | if (obj.GetType() != GetType()) return false; 29 | return Equals((TrianglePointer) obj); 30 | } 31 | 32 | /// 33 | public override int GetHashCode() 34 | { 35 | return HashCode.Combine(A, B, C); 36 | } 37 | 38 | public static bool operator ==(TrianglePointer left, TrianglePointer right) 39 | { 40 | return Equals(left, right); 41 | } 42 | 43 | public static bool operator !=(TrianglePointer left, TrianglePointer right) 44 | { 45 | return !Equals(left, right); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /AssetCatalog/Model/LocalCatalogDb.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using LiteDB; 3 | 4 | namespace AssetCatalog.Model 5 | { 6 | public class LocalCatalogDb : ICatalogDb 7 | { 8 | private LiteDatabase _db; 9 | private ILiteCollection _catalog; 10 | 11 | /// 12 | public bool NeedsAuth() 13 | { 14 | return false; 15 | } 16 | 17 | /// 18 | public Task Connect(string email, string password) 19 | { 20 | _db = new LiteDatabase("catalog.db"); 21 | _catalog = _db.GetCollection("catalog"); 22 | _catalog.EnsureIndex(entry => entry.Uid); 23 | 24 | return Task.CompletedTask; 25 | } 26 | 27 | /// 28 | public CatalogEntry Get(ulong uid) 29 | { 30 | var entry = _catalog.FindById(uid); 31 | 32 | if (entry != null) 33 | return entry; 34 | 35 | return new CatalogEntry 36 | { 37 | Uid = uid, 38 | Status = CatalogEntryStatus.Incomplete, 39 | Category = CatalogAssetCategory.Uncategorized 40 | }; 41 | } 42 | 43 | /// 44 | public void Put(ulong uid, CatalogEntry entry) 45 | { 46 | _catalog.Upsert(uid, entry); 47 | ForgeCatalog.Instance.OnCatalogChanged(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Prism/Resources/model.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // Input vertex data, different for all executions of this shader. 4 | layout(location = 0) in vec3 position; 5 | layout(location = 1) in vec3 normal; 6 | layout(location = 2) in vec2 texCoord; 7 | layout(location = 3) in vec3 color; 8 | layout(location = 4) in int objectId; 9 | 10 | out vec3 fragPos; 11 | out vec3 fragNormal; 12 | out vec2 fragTexCoord; 13 | out vec3 fragVertColor; 14 | out vec3 reflectedVector; 15 | out vec2 matcapTexCoord; 16 | 17 | flat out int fragObjectId; 18 | 19 | out mat4 model; 20 | out mat4 view; 21 | out mat4 proj; 22 | 23 | uniform mat4 m; 24 | uniform mat4 v; 25 | uniform mat4 p; 26 | 27 | void main() 28 | { 29 | fragNormal = normalize(normal); 30 | fragTexCoord = texCoord; 31 | 32 | fragVertColor = color; 33 | 34 | fragObjectId = objectId; 35 | 36 | model = m; 37 | view = v; 38 | proj = p; 39 | 40 | mat4 MVP = p*v*m; 41 | gl_Position = MVP * vec4(position, 1.); 42 | fragPos = vec3(v * vec4(position, 1.0)); 43 | vec3 v_normal = normalize(vec3(v * (m * vec4(fragNormal, 0.0)))); 44 | matcapTexCoord = v_normal.xy * vec2(0.5, -0.5) + vec2(0.5, 0.5); 45 | //vec3 viewVector = normalize(world) 46 | } -------------------------------------------------------------------------------- /Prism/Extensions/BitmapExt.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using OpenTK.Graphics.OpenGL; 4 | using PixelFormat = System.Drawing.Imaging.PixelFormat; 5 | 6 | namespace Prism.Extensions 7 | { 8 | public static class BitmapExt 9 | { 10 | public static void LoadGlTexture(this Bitmap bitmap, int texId, TextureTarget target) 11 | { 12 | GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); 13 | 14 | GL.BindTexture(target, texId); 15 | 16 | var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), 17 | ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 18 | 19 | GL.TexImage2D(target, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, 20 | OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); 21 | bitmap.UnlockBits(data); 22 | 23 | GL.TexParameter(target, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 24 | GL.TexParameter(target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); 25 | GL.TexParameter(target, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge); 26 | GL.TexParameter(target, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/Entity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using RainbowForge; 5 | using RainbowScimitar.Extensions; 6 | using RainbowScimitar.Model; 7 | using RainbowScimitar.Scimitar; 8 | 9 | namespace RainbowScimitar.DataTypes 10 | { 11 | public record Entity() 12 | { 13 | public static Entity Read(BinaryReader r) 14 | { 15 | r.ReadMagic(Magic.Entity); 16 | 17 | var unk1 = r.ReadByte(); 18 | 19 | var matrix = r.ReadStruct(); 20 | 21 | var boundingVolumeUid = r.ReadUid(); 22 | var boundingVolume = BoundingVolume.Read(r); 23 | 24 | var unk2 = r.ReadBytes(31); 25 | 26 | var unk3 = r.ReadByte(); // either 1 or 3 27 | ScimitarId worldSectionUid; 28 | if (unk3 == 1) worldSectionUid = r.ReadUid(); 29 | 30 | var zero1 = r.ReadByte(); 31 | 32 | var tagClientUid = r.ReadUid(); 33 | var tagClient = TagClient.Read(r); 34 | 35 | var unk4 = r.ReadBytes(14); 36 | 37 | var next = r.ReadUInt32(); 38 | 39 | Console.WriteLine($"{next:X8} - {string.Join(' ', unk2.Select(b => b == 0 ? ".." : b.ToString("X2")))} - {string.Join(' ', unk4.Select(b => b == 0 ? ".." : b.ToString("X2")))}"); 40 | 41 | var pos = r.BaseStream.Position; 42 | return new Entity(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /RainbowForge/Archive/FlatArchive.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace RainbowForge.Archive 6 | { 7 | public class FlatArchive 8 | { 9 | public FlatArchiveEntry[] Entries { get; } 10 | 11 | private FlatArchive(FlatArchiveEntry[] entries) 12 | { 13 | Entries = entries; 14 | } 15 | 16 | public static FlatArchive Read(BinaryReader r, uint version) 17 | { 18 | var entries = new List(); 19 | 20 | var index = 0; 21 | while (r.BaseStream.Position != r.BaseStream.Length) 22 | entries.Add(FlatArchiveEntry.Read(r, version, index++)); 23 | 24 | if (r.BaseStream.Length != r.BaseStream.Position) 25 | throw new InvalidDataException(); 26 | 27 | return new FlatArchive(entries.ToArray()); 28 | } 29 | 30 | public BinaryReader GetEntryStream(Stream archiveStream, ulong entryUid) 31 | { 32 | var entry = Entries.FirstOrDefault(entry => entry.MetaData.Uid == entryUid); 33 | if (entry == null) 34 | return null; 35 | 36 | var ms = new MemoryStream(); 37 | archiveStream.Seek(entry.PayloadOffset, SeekOrigin.Begin); 38 | archiveStream.CopyStream(ms, entry.PayloadLength); 39 | ms.Seek(0, SeekOrigin.Begin); 40 | return new BinaryReader(ms); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /DumpTool/FindAllMeshPropsGlobalCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using CommandLine; 4 | using RainbowForge; 5 | using RainbowForge.Core; 6 | 7 | namespace DumpTool 8 | { 9 | [Verb("findallmeshpropsglobal", HelpText = "Find all MeshProperties containers which reference the given UID in all flat archives in the given forge file")] 10 | public class FindAllMeshPropsGlobalCommand 11 | { 12 | [Value(0, HelpText = "The directory of forge files to search")] 13 | public string ForgeDirectory { get; set; } 14 | 15 | [Value(1, HelpText = "The UID to search for")] 16 | public ulong Uid { get; set; } 17 | 18 | public static void Run(FindAllMeshPropsGlobalCommand args) 19 | { 20 | FileSystemUtil.AssertDirectoryExists(args.ForgeDirectory); 21 | 22 | foreach (var file in Directory.GetFiles(args.ForgeDirectory, "*.forge")) 23 | { 24 | var forge = Forge.GetForge(file); 25 | for (var i = 0; i < forge.Entries.Length; i++) 26 | { 27 | try 28 | { 29 | var entry = forge.Entries[i]; 30 | if (FindAllMeshPropsCommand.SearchFlatArchive(forge, entry, args.Uid)) 31 | Console.WriteLine($"{Path.GetFileName(file)}: {entry.Uid}"); 32 | } 33 | catch 34 | { 35 | // ignored 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /RainbowForge/Structs/Matrix4F.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowForge.Structs 4 | { 5 | /// 6 | /// A DirectX-style row-major transformation matrix 7 | /// 8 | [StructLayout(LayoutKind.Explicit)] 9 | public struct Matrix4F 10 | { 11 | public const int SizeInBytes = 16 * sizeof(float); 12 | 13 | [FieldOffset(0 * sizeof(float))] public float M11; 14 | [FieldOffset(1 * sizeof(float))] public float M12; 15 | [FieldOffset(2 * sizeof(float))] public float M13; 16 | [FieldOffset(3 * sizeof(float))] public float M14; 17 | 18 | [FieldOffset(4 * sizeof(float))] public float M21; 19 | [FieldOffset(5 * sizeof(float))] public float M22; 20 | [FieldOffset(6 * sizeof(float))] public float M23; 21 | [FieldOffset(7 * sizeof(float))] public float M24; 22 | 23 | [FieldOffset(8 * sizeof(float))] public float M31; 24 | [FieldOffset(9 * sizeof(float))] public float M32; 25 | [FieldOffset(10 * sizeof(float))] public float M33; 26 | [FieldOffset(11 * sizeof(float))] public float M34; 27 | 28 | [FieldOffset(12 * sizeof(float))] public float M41; 29 | [FieldOffset(13 * sizeof(float))] public float M42; 30 | [FieldOffset(14 * sizeof(float))] public float M43; 31 | [FieldOffset(15 * sizeof(float))] public float M44; 32 | } 33 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarAssetMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using System.Text; 3 | using RainbowForge.Core; 4 | 5 | namespace RainbowScimitar.Scimitar 6 | { 7 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 8 | public readonly struct ScimitarAssetMetadata 9 | { 10 | public readonly uint Unknown1; 11 | public readonly uint Unknown2; 12 | public readonly ulong Unknown3; 13 | public readonly uint Unknown4; 14 | 15 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)] 16 | public readonly byte[] NameData; 17 | 18 | public readonly byte NameLength; 19 | public readonly uint Timestamp; 20 | public readonly uint Unknown5; 21 | public readonly int PreviousEntryIndex; 22 | public readonly int NextEntryIndex; 23 | public readonly ulong Unknown6; 24 | public readonly uint TypeHash; 25 | public readonly uint Unknown7; 26 | 27 | public readonly ulong Unknown8; 28 | public readonly int FileSize; 29 | 30 | public string DecodeName(ScimitarFileTableEntry file) 31 | { 32 | // TODO: move this function into RainbowScimitar 33 | var nameBytes = NameEncoding.DecodeName(NameData[..NameLength], TypeHash, file.Uid, (ulong)file.Offset, NameEncoding.FILENAME_ENCODING_ENTRY_KEY_STEP); 34 | return Encoding.ASCII.GetString(nameBytes); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/Shader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace RainbowForge.RenderPipeline 5 | { 6 | public class Shader 7 | { 8 | public uint Magic { get; } 9 | public string Vert { get; } 10 | public string ExtraFunctions { get; } 11 | public ShaderUniform[] Uniforms { get; } 12 | 13 | private Shader(uint magic, string vert, string extraFunctions, ShaderUniform[] uniforms) 14 | { 15 | Magic = magic; 16 | Vert = vert; 17 | ExtraFunctions = extraFunctions; 18 | Uniforms = uniforms; 19 | } 20 | 21 | public static Shader Read(BinaryReader r) 22 | { 23 | var magic = r.ReadUInt32(); 24 | 25 | var extraFunctionsLength = r.ReadInt32(); 26 | var extraFunctions = Encoding.UTF8.GetString(r.ReadBytes(extraFunctionsLength)); 27 | r.ReadByte(); // null terminator 28 | 29 | var vertLength = r.ReadInt32(); 30 | var vert = Encoding.UTF8.GetString(r.ReadBytes(vertLength)); 31 | r.ReadByte(); // null terminator 32 | 33 | r.ReadBytes(16); // padding 34 | 35 | var numUniforms = r.ReadInt32(); 36 | 37 | var uniforms = new ShaderUniform[numUniforms]; 38 | 39 | for (var i = 0; i < numUniforms; i++) 40 | uniforms[i] = ShaderUniform.Read(r); 41 | 42 | return new Shader(magic, vert, extraFunctions, uniforms); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /RainbowForge/Model/MeshObjectSkinMapping.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Model 4 | { 5 | /// 6 | /// This is just a guess based on structure. It might turn out being something else. 7 | /// 8 | public class MeshObjectSkinMapping 9 | { 10 | private const int Size = 0x10C; 11 | 12 | public byte BonesUsed { get; } 13 | public byte MatId { get; } 14 | public ushort VertBufLen { get; } 15 | public byte[] Indices { get; } 16 | 17 | public MeshObjectSkinMapping(byte bonesUsed, byte matId, ushort vertBufLen, byte[] indices) 18 | { 19 | BonesUsed = bonesUsed; 20 | MatId = matId; 21 | VertBufLen = vertBufLen; 22 | Indices = indices; 23 | } 24 | 25 | public static MeshObjectSkinMapping Read(BinaryReader r) 26 | { 27 | var x00 = r.ReadUInt16(); 28 | var bonesUsed = r.ReadByte(); 29 | var matId = r.ReadByte(); 30 | var x04 = r.ReadUInt16(); 31 | var vertBufLen = r.ReadUInt16(); 32 | var lenIndices = r.ReadByte(); 33 | var indices = r.ReadBytes(lenIndices); 34 | 35 | if (lenIndices > 0) 36 | { 37 | r.BaseStream.Seek(Size - 2 - 4 - 2 - 1 - lenIndices - 4, SeekOrigin.Current); 38 | var x108 = r.ReadUInt32(); 39 | } 40 | 41 | return new MeshObjectSkinMapping(bonesUsed, matId, vertBufLen, indices); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarArchiveFileMetadata.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using RainbowForge.Core; 4 | using RainbowScimitar.Extensions; 5 | 6 | namespace RainbowScimitar.Scimitar 7 | { 8 | public record ScimitarArchiveFileMetadata(string Filename, int Size, uint FileType, ScimitarId Uid) 9 | { 10 | public static ScimitarArchiveFileMetadata Read(Stream bundleStream) 11 | { 12 | var r = new BinaryReader(bundleStream); 13 | 14 | // in AC, a name length with 0x80000000 bit set means encrypted 15 | var filenameLength = r.ReadUInt32(); 16 | var filenameBytes = r.ReadBytes((int)filenameLength); 17 | var size = r.ReadInt32(); 18 | var fileType = r.ReadUInt32(); 19 | var uid = r.ReadUid(); 20 | 21 | var nameBytes = NameEncoding.DecodeName(filenameBytes, fileType, uid, 0, NameEncoding.FILENAME_ENCODING_FILE_KEY_STEP); 22 | 23 | return new ScimitarArchiveFileMetadata(Encoding.ASCII.GetString(nameBytes), size, fileType, uid); 24 | } 25 | 26 | public void Write(Stream fileStream) 27 | { 28 | var w = new BinaryWriter(fileStream); 29 | 30 | w.Write(Filename.Length); 31 | w.Write(NameEncoding.DecodeName(Encoding.ASCII.GetBytes(Filename), FileType, Uid, 0, NameEncoding.FILENAME_ENCODING_FILE_KEY_STEP)); 32 | w.Write(Size); 33 | w.Write(FileType); 34 | w.Write(Uid); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /AssetCatalog/Extensions/BitmapExt.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using OpenTK.Graphics.OpenGL; 4 | using PixelFormat = System.Drawing.Imaging.PixelFormat; 5 | 6 | namespace AssetCatalog.Extensions 7 | { 8 | public static class BitmapExt 9 | { 10 | public static void LoadGlTexture(this Bitmap bitmap, int texId) 11 | { 12 | GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest); 13 | 14 | GL.BindTexture(TextureTarget.Texture2D, texId); 15 | 16 | var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), 17 | ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 18 | 19 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, 20 | OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); 21 | bitmap.UnlockBits(data); 22 | 23 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear); 24 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest); 25 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge); 26 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /RainbowForge/Database/IndexUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using RainbowForge.Core; 4 | 5 | namespace RainbowForge.Database 6 | { 7 | public class IndexUtil 8 | { 9 | public static Dictionary CreateIndex(string forgeDirectory) 10 | { 11 | var idTable = new Dictionary(); 12 | 13 | foreach (var file in Directory.GetFiles(forgeDirectory, "*.forge")) 14 | { 15 | var forgeFilename = Path.GetFileName(file); 16 | 17 | var forge = Forge.GetForge(file); 18 | 19 | foreach (var entry in forge.Entries) 20 | { 21 | idTable[entry.Uid] = new AssetPath(forgeFilename, entry.MetaData.FileName, null); 22 | 23 | // var container = forge.GetContainer(entry.Uid); 24 | // if (container is not ForgeAsset forgeAsset) continue; 25 | // 26 | // try 27 | // { 28 | // var assetStream = forgeAsset.GetDataStream(forge); 29 | // var arc = FlatArchive.Read(assetStream); 30 | // 31 | // foreach (var arcEntry in arc.Entries) 32 | // { 33 | // idTable[arcEntry.MetaData.Uid] = new AssetPath(file, entry.Uid); 34 | // } 35 | // } 36 | // catch (Exception e) 37 | // { 38 | // // TODO: some flat archives throw invalid frame descriptor 39 | // } 40 | } 41 | } 42 | 43 | return idTable; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /RainbowForge/Core/DataBlock/FlatDataBlock.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core.DataBlock 4 | { 5 | public class FlatDataBlock : IAssetBlock 6 | { 7 | public long Offset { get; } 8 | public int Length { get; } 9 | 10 | private FlatDataBlock(long offset, int length) 11 | { 12 | Offset = offset; 13 | Length = length; 14 | } 15 | 16 | public static FlatDataBlock Read(BinaryReader r, Entry entry) 17 | { 18 | var numChunks = r.ReadUInt16(); 19 | var unk1 = r.ReadUInt16(); 20 | 21 | var payloadSizes = new int[numChunks]; 22 | var serializedSizes = new int[numChunks]; 23 | for (var i = 0; i < numChunks; i++) 24 | { 25 | payloadSizes[i] = r.ReadInt32(); 26 | serializedSizes[i] = r.ReadInt32(); 27 | } 28 | 29 | var checksumData = new uint[numChunks]; 30 | 31 | for (var i = 0; i < numChunks; i++) 32 | checksumData[i] = r.ReadUInt32(); 33 | 34 | var dataStart = r.BaseStream.Position; 35 | r.BaseStream.Seek(entry.End, SeekOrigin.Begin); 36 | 37 | return new FlatDataBlock(dataStart, (int)(entry.End - dataStart)); 38 | } 39 | 40 | public Stream GetDataStream(BinaryReader r) 41 | { 42 | var ms = new MemoryStream(); 43 | r.BaseStream.Seek(Offset, SeekOrigin.Begin); 44 | r.BaseStream.CopyStream(ms, Length); 45 | 46 | ms.Seek(0, SeekOrigin.Begin); 47 | return ms; 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarId.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowScimitar.Scimitar 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 7 | public readonly struct ScimitarId : IComparable, IFormattable 8 | { 9 | private const ulong RELATIVE_ID_MASK = 0xFFFFFFFFFF000000; 10 | private const ulong RELATIVE_ID_BITMAP = 0x00000000F8000000; 11 | private const ulong RELATIVE_INDEX_MASK = 0x0000000000FFFFFF; 12 | 13 | public readonly ulong Id; 14 | 15 | public bool IsRelative => (Id & RELATIVE_ID_MASK) == RELATIVE_ID_BITMAP; 16 | public int RelativeIndex => IsRelative ? (int)(Id & RELATIVE_INDEX_MASK) : throw new NotSupportedException(); 17 | 18 | public ScimitarId(ulong id) 19 | { 20 | Id = id; 21 | } 22 | 23 | public static implicit operator ulong(ScimitarId id) => id.Id; 24 | public static implicit operator ScimitarId(ulong id) => new(id); 25 | 26 | /// 27 | public int CompareTo(ScimitarId other) 28 | { 29 | return Id.CompareTo(other.Id); 30 | } 31 | 32 | /// 33 | public string ToString(string format, IFormatProvider formatProvider) 34 | { 35 | return Id.ToString(format, formatProvider); 36 | } 37 | 38 | /// 39 | public override string ToString() 40 | { 41 | return Id.ToString("X16"); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /RainbowForge/Core/DataBlock/ChunkedData.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Core.DataBlock 4 | { 5 | /// 6 | /// Describes where to find particular data chunk, whether 7 | /// it is compressed/raw and where to find it within data 8 | /// stream. It's serialized form is split in parts. First 9 | /// we have ``[unpacked, packed]`` for each chunk, then we 10 | /// have ``[hash, data]`` for each chunk. ``offset`` is not 11 | /// a serialized variable, it's added merely for reverse 12 | /// engineering ease. 13 | /// 14 | public class ChunkedData 15 | { 16 | public uint DataLength { get; } 17 | public uint SerializedLength { get; } 18 | public uint Hash { get; private set; } 19 | public long Offset { get; private set; } 20 | 21 | public bool IsCompressed => DataLength > SerializedLength; 22 | 23 | private ChunkedData(uint dataLength, uint serializedLength) 24 | { 25 | DataLength = dataLength; 26 | SerializedLength = serializedLength; 27 | } 28 | 29 | public void Finalize(BinaryReader r) 30 | { 31 | Hash = r.ReadUInt32(); 32 | Offset = r.BaseStream.Position; 33 | } 34 | 35 | public static ChunkedData Read(BinaryReader r) 36 | { 37 | var unpacked = r.ReadUInt32(); 38 | var packed = r.ReadUInt32(); 39 | 40 | return new ChunkedData(unpacked, packed); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /RainbowScimitar/Extensions/BinaryReaderExt.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | using RainbowForge; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.Extensions 7 | { 8 | public static class BinaryReaderExt 9 | { 10 | public static T ReadStruct(this BinaryReader stream) where T : struct 11 | { 12 | var t = new T(); 13 | var size = Marshal.SizeOf(t); 14 | 15 | var buf = new byte[size]; 16 | var amountRead = stream.Read(buf, 0, size); 17 | if (amountRead != size) 18 | throw new EndOfStreamException(); 19 | 20 | buf.ToStruct(ref t); 21 | 22 | return t; 23 | } 24 | 25 | public static T[] ReadStructs(this BinaryReader r, int count) where T : struct 26 | { 27 | var structs = new T[count]; 28 | 29 | for (var i = 0; i < count; i++) 30 | structs[i] = r.ReadStruct(); 31 | 32 | return structs; 33 | } 34 | 35 | public static T[] ReadLengthPrefixedStructs(this BinaryReader r) where T : struct 36 | { 37 | var count = r.ReadInt32(); 38 | return r.ReadStructs(count); 39 | } 40 | 41 | public static ScimitarId ReadUid(this BinaryReader r) 42 | { 43 | return r.ReadUInt64(); 44 | } 45 | 46 | public static void ReadMagic(this BinaryReader r, Magic magic) 47 | { 48 | var needle = r.ReadUInt32(); 49 | MagicHelper.AssertEquals(magic, needle); 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /AssetCatalog/BindingSources/EnumBindingSourceExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Markup; 3 | 4 | namespace AssetCatalog.BindingSources 5 | { 6 | public class EnumBindingSourceExtension : MarkupExtension 7 | { 8 | private readonly Type _enumType; 9 | 10 | public Type EnumType 11 | { 12 | get => _enumType; 13 | init 14 | { 15 | if (value == _enumType) return; 16 | 17 | if (null != value) 18 | { 19 | var enumType = Nullable.GetUnderlyingType(value) ?? value; 20 | 21 | if (!enumType.IsEnum) 22 | throw new ArgumentException("Type must be for an Enum."); 23 | } 24 | 25 | _enumType = value; 26 | } 27 | } 28 | 29 | public EnumBindingSourceExtension() 30 | { 31 | } 32 | 33 | public EnumBindingSourceExtension(Type enumType) 34 | { 35 | EnumType = enumType; 36 | } 37 | 38 | public override object ProvideValue(IServiceProvider serviceProvider) 39 | { 40 | if (null == _enumType) 41 | throw new InvalidOperationException("The EnumType must be specified."); 42 | 43 | var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType; 44 | var enumValues = Enum.GetValues(actualEnumType); 45 | 46 | if (actualEnumType == _enumType) 47 | return enumValues; 48 | 49 | var tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1); 50 | enumValues.CopyTo(tempArray, 1); 51 | return tempArray; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /RainbowForge/Components/R6AIWorldComponent.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace RainbowForge.Components 5 | { 6 | public record R6AIRoom(string Name, ulong[] Uids); 7 | 8 | public class R6AIWorldComponent 9 | { 10 | public R6AIRoom[] Rooms { get; } 11 | 12 | private R6AIWorldComponent(R6AIRoom[] rooms) 13 | { 14 | Rooms = rooms; 15 | } 16 | 17 | public static R6AIWorldComponent Read(BinaryReader r) 18 | { 19 | var magic = r.ReadUInt32(); 20 | MagicHelper.AssertEquals(Magic.R6AIWorldComponent, magic); 21 | 22 | var data = r.ReadBytes(34); 23 | 24 | var numAreas = r.ReadInt32(); 25 | 26 | var areas = new R6AIRoom[numAreas]; 27 | 28 | for (var i = 0; i < numAreas; i++) 29 | { 30 | var data2 = r.ReadBytes(9); 31 | 32 | var entryMagic = r.ReadUInt32(); 33 | MagicHelper.AssertEquals(Magic.R6AIRoom, entryMagic); 34 | 35 | var entryLength = r.ReadInt32(); 36 | var entry = Encoding.ASCII.GetString(r.ReadBytes(entryLength)); 37 | 38 | var data3 = r.ReadBytes(17); 39 | 40 | var numThings = r.ReadInt32(); 41 | var uids = new ulong[numThings]; 42 | for (var j = 0; j < numThings; j++) 43 | { 44 | uids[j] = r.ReadUInt64(); 45 | } 46 | 47 | var data4 = r.ReadBytes(17); 48 | 49 | areas[i] = new R6AIRoom(entry, uids); 50 | } 51 | 52 | // TODO: there's more data at the end of this file, possibly UIDs? 53 | 54 | return new R6AIWorldComponent(areas); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Prism/TextureUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace Prism 7 | { 8 | class TextureUtil 9 | { 10 | public static void PatchNormalMap(Bitmap bmp, int channelFlipIdx, bool bRecalculateZ) 11 | { 12 | var bits = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); 13 | var pointer = bits.Scan0; 14 | var size = Math.Abs(bits.Stride) * bmp.Height; 15 | var pixels = new byte[size]; 16 | Marshal.Copy(pointer, pixels, 0, size); 17 | 18 | for (var i = 0; i < pixels.Length; i += 4) 19 | { 20 | if (channelFlipIdx >= 0) 21 | pixels[i + channelFlipIdx] = (byte)(255 - pixels[i + channelFlipIdx]); // BGRA 22 | if (bRecalculateZ) 23 | pixels[i] = CalculateNormalMapZ(pixels[i + 2], pixels[i + 1]); 24 | } 25 | 26 | Marshal.Copy(pixels, 0, pointer, size); 27 | bmp.UnlockBits(bits); 28 | } 29 | 30 | private static byte CalculateNormalMapZ(byte r, byte g) 31 | { 32 | float x = r / 255.0f * 2 - 1; 33 | float y = g / 255.0f * 2 - 1; 34 | 35 | var dot1 = 1 - x * x - y * y; 36 | float z = dot1 > 0 ? MathF.Sqrt(dot1) : 0.0f; 37 | 38 | return (byte)(Math.Clamp((z + 1) / 2.0f, 0.0f, 1.0f) * 255); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RainbowForge/Core/Container/Hash.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace RainbowForge.Core.Container 5 | { 6 | public class Hash : ForgeContainer 7 | { 8 | public ulong Hash1 { get; } 9 | public ulong Hash2 { get; } 10 | public string Name { get; } 11 | 12 | private Hash(ulong hash1, ulong hash2 = 0, string name = "") 13 | { 14 | Hash1 = hash1; 15 | Hash2 = hash2; 16 | Name = name; 17 | } 18 | 19 | public static Hash Read(BinaryReader r) 20 | { 21 | var hash1 = r.ReadUInt64(); // [0x0] 22 | 23 | var continueFlag = r.ReadUInt32(); // [0x8] 24 | if (continueFlag == 0) // switch value, controls whether there is more metadata 25 | return new Hash(hash1); 26 | 27 | var hash2 = r.ReadUInt64(); // [0xC] 28 | 29 | continueFlag = r.ReadUInt32(); // [0x14] 30 | if (continueFlag == 0) 31 | return new Hash(hash1, hash2); 32 | 33 | var nameBytes = r.ReadBytes(0x40); 34 | var name = Encoding.UTF8.GetString(nameBytes); // [0x18] 35 | var x58 = r.ReadUInt64(); // [0x58] 36 | var x60 = r.ReadUInt64(); // [0x60] 37 | 38 | continueFlag = r.ReadUInt32(); // [0x68] 39 | if (continueFlag == 0) 40 | return new Hash(hash1, hash2, name); 41 | 42 | var x6C = r.ReadUInt64(); // [0x6C] 43 | var x74 = r.ReadUInt64(); // [0x74] 44 | 45 | continueFlag = r.ReadUInt32(); // [0x7C] 46 | if (continueFlag == 0) 47 | return new Hash(hash1, hash2, name); 48 | 49 | var x80 = r.ReadUInt64(); // [0x80] 50 | 51 | return new Hash(hash1, hash2, name); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/TextureMap.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.RenderPipeline 4 | { 5 | public class TextureMap 6 | { 7 | public uint Var1 { get; } 8 | public uint Var2 { get; } 9 | public uint Var3 { get; } 10 | public uint Var4 { get; } 11 | public byte[] Data { get; } 12 | public ulong[] TexUidMipSet1 { get; } 13 | public ulong[] TexUidMipSet2 { get; } 14 | public byte[] Data2 { get; } 15 | 16 | private TextureMap(uint var1, uint var2, uint var3, uint var4, byte[] data, ulong[] texUidMipSet1, ulong[] texUidMipSet2, byte[] data2) 17 | { 18 | Var1 = var1; 19 | Var2 = var2; 20 | Var3 = var3; 21 | Var4 = var4; 22 | Data = data; 23 | TexUidMipSet1 = texUidMipSet1; 24 | TexUidMipSet2 = texUidMipSet2; 25 | Data2 = data2; 26 | } 27 | 28 | public static TextureMap Read(BinaryReader r) 29 | { 30 | var magic = r.ReadUInt32(); 31 | MagicHelper.AssertEquals(Magic.TextureMap, magic); 32 | 33 | var var1 = r.ReadUInt32(); 34 | var var2 = r.ReadUInt32(); 35 | var var3 = r.ReadUInt32(); 36 | var var4 = r.ReadUInt32(); 37 | 38 | var data = r.ReadBytes(52); 39 | 40 | var texUidMipSet1 = new ulong[5]; 41 | for (var i = 0; i < 5; i++) 42 | texUidMipSet1[i] = r.ReadUInt64(); 43 | 44 | var texUidMipSet2 = new ulong[5]; 45 | for (var i = 0; i < 5; i++) 46 | texUidMipSet2[i] = r.ReadUInt64(); 47 | 48 | var data2 = r.ReadBytes(32); 49 | 50 | return new TextureMap(var1, var2, var3, var4, data, texUidMipSet1, texUidMipSet2, data2); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/WorldLoader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Model; 5 | using RainbowScimitar.Scimitar; 6 | 7 | namespace RainbowScimitar.DataTypes 8 | { 9 | public record WorldLoader(WorldLoaderSubNode[] Children) 10 | { 11 | public static WorldLoader Read(BinaryReader r) 12 | { 13 | r.ReadMagic(Magic.WorldLoader); 14 | 15 | var childCount = r.ReadInt32(); 16 | 17 | var children = new WorldLoaderSubNode[childCount]; 18 | 19 | // var zero = r.ReadByte(); 20 | var uid1 = r.ReadUid(); 21 | 22 | for (var i = 0; i < childCount; i++) 23 | { 24 | r.ReadBytes(1); 25 | 26 | var lookup = WorldDivisionToTagLoadUnitLookup.Read(r); 27 | 28 | var numWorldDivisions = r.ReadInt32(); 29 | var divisions = new WorldLoadUnitWorldDivision[numWorldDivisions]; 30 | for (var j = 0; j < numWorldDivisions; j++) 31 | { 32 | var zero2 = r.ReadByte(); 33 | var childInternalUid = r.ReadUid(); 34 | divisions[j] = WorldLoadUnitWorldDivision.Read(r); 35 | } 36 | 37 | /* TODO: It doesn't seem right to have scenarios bundled into WorldLoader, especially 38 | * because it's only ever the last sub node that has one. Should these actually be after 39 | * WorldLoader is read completely? 40 | */ 41 | var scenarios = r.ReadLengthPrefixedStructs(); 42 | 43 | children[i] = new WorldLoaderSubNode(divisions, scenarios, lookup); 44 | } 45 | 46 | return new WorldLoader(children); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Prism/SettingsForm.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Forms; 3 | 4 | namespace Prism 5 | { 6 | public class SettingsForm : Form 7 | { 8 | public string Filename { get; } 9 | 10 | public SettingsForm(string filename) 11 | { 12 | Filename = filename; 13 | SuspendLayout(); 14 | 15 | AutoScaleMode = AutoScaleMode.Font; 16 | ClientSize = new Size(530, 500); 17 | Text = "Prism"; 18 | 19 | PropertyGrid pgSettings; 20 | 21 | Controls.Add(pgSettings = new PropertyGrid 22 | { 23 | Dock = DockStyle.Fill, 24 | SelectedObject = PrismSettings.Load(filename) 25 | }); 26 | 27 | Button bSave; 28 | Button bCancel; 29 | 30 | Controls.Add(new FlowLayoutPanel 31 | { 32 | Dock = DockStyle.Bottom, 33 | FlowDirection = FlowDirection.RightToLeft, 34 | AutoSize = true, 35 | Controls = 36 | { 37 | (bSave = new Button 38 | { 39 | Text = "Save", 40 | Enabled = false, 41 | DialogResult = DialogResult.OK 42 | }), 43 | (bCancel = new Button 44 | { 45 | Text = "Cancel", 46 | DialogResult = DialogResult.Cancel 47 | }) 48 | } 49 | }); 50 | 51 | pgSettings.PropertyValueChanged += (o, args) => { bSave.Enabled = true; }; 52 | 53 | bSave.Click += (sender, args) => 54 | { 55 | if (pgSettings.SelectedObject is not PrismSettings po) 56 | return; 57 | 58 | po.Save(filename); 59 | 60 | Close(); 61 | }; 62 | 63 | bCancel.Click += (sender, args) => Close(); 64 | 65 | ResumeLayout(true); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /DumpTool/FindCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using CommandLine; 5 | using RainbowForge; 6 | using RainbowForge.Archive; 7 | using RainbowForge.Core; 8 | using RainbowForge.Core.Container; 9 | 10 | namespace DumpTool 11 | { 12 | [Verb("find", HelpText = "Find the forge file(s) that contain the given UID")] 13 | public class FindCommand 14 | { 15 | [Option('d', "deep", Default = false, Required = false)] 16 | public bool DeepSearch { get; set; } 17 | 18 | [Value(0, HelpText = "The directory of forge files to search")] 19 | public string SearchDirectory { get; set; } 20 | 21 | [Value(1, HelpText = "The UID to search for")] 22 | public ulong Uid { get; set; } 23 | 24 | public static void Run(FindCommand args) 25 | { 26 | FileSystemUtil.AssertDirectoryExists(args.SearchDirectory); 27 | 28 | foreach (var file in Directory.GetFiles(args.SearchDirectory, "*.forge")) 29 | { 30 | var forge = Forge.GetForge(file); 31 | 32 | foreach (var entry in forge.Entries) 33 | { 34 | if (entry.Uid == args.Uid) 35 | Console.WriteLine(file); 36 | 37 | if (args.DeepSearch) 38 | { 39 | var container = forge.GetContainer(entry.Uid); 40 | if (container is not ForgeAsset forgeAsset) continue; 41 | 42 | var assetStream = forgeAsset.GetDataStream(forge); 43 | var arc = FlatArchive.Read(assetStream, forge.Version); 44 | 45 | if (arc.Entries.Any(archiveEntry => archiveEntry.MetaData.Uid == args.Uid)) 46 | Console.WriteLine($"{file} -> within flat archive {entry.Uid}"); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /RainbowForge/Crc32.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge 4 | { 5 | public class Crc32 6 | { 7 | public const uint DefaultPolynomial = 0xedb88320u; 8 | public const uint DefaultSeed = 0xffffffffu; 9 | private static readonly uint[] DefaultTable; 10 | 11 | static Crc32() 12 | { 13 | DefaultTable = new uint[256]; 14 | for (var i = 0; i < 256; i++) 15 | { 16 | var entry = (uint)i; 17 | for (var j = 0; j < 8; j++) 18 | if ((entry & 1) == 1) 19 | entry = (entry >> 1) ^ DefaultPolynomial; 20 | else 21 | entry >>= 1; 22 | DefaultTable[i] = entry; 23 | } 24 | } 25 | 26 | public static uint Compute(byte[] buffer) 27 | { 28 | return Compute(DefaultSeed, buffer); 29 | } 30 | 31 | public static uint Compute(uint seed, byte[] buffer) 32 | { 33 | return ~CalculateHash(seed, buffer, 0, buffer.Length); 34 | } 35 | 36 | public static uint CalculateHash(uint seed, byte[] buffer, int start, int size) 37 | { 38 | var hash = seed; 39 | for (var i = start; i < start + size; i++) 40 | hash = (hash >> 8) ^ DefaultTable[buffer[i] ^ (hash & 0xff)]; 41 | return hash; 42 | } 43 | 44 | public static uint Compute(Stream buffer, long length) 45 | { 46 | return Compute(DefaultSeed, buffer, length); 47 | } 48 | 49 | public static uint Compute(uint seed, Stream buffer, long length) 50 | { 51 | return ~CalculateHash(seed, buffer, length); 52 | } 53 | 54 | public static uint CalculateHash(uint seed, Stream buffer, long size) 55 | { 56 | var hash = seed; 57 | for (var i = 0L; i < size; i++) 58 | hash = (hash >> 8) ^ DefaultTable[buffer.ReadByte() ^ (hash & 0xff)]; 59 | return hash; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /RainbowForge/FileMetaData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using RainbowForge.Core; 5 | 6 | namespace RainbowForge 7 | { 8 | public class FileMetaData 9 | { 10 | public string FileName { get; } 11 | public byte[] EncodedMeta { get; } 12 | public uint ContainerType { get; } 13 | public uint FileType { get; } 14 | public ulong Uid { get; } 15 | 16 | private FileMetaData(string fileName, byte[] encodedMeta, uint containerType, uint fileType, ulong uid) 17 | { 18 | FileName = fileName; 19 | EncodedMeta = encodedMeta; 20 | ContainerType = containerType; 21 | FileType = fileType; 22 | Uid = uid; 23 | } 24 | 25 | public static FileMetaData Read(BinaryReader r, uint version) 26 | { 27 | switch (version) 28 | { 29 | case >= 30: 30 | { 31 | // in AC, a name length with 0x80000000 bit set means encrypted 32 | var filenameLength = r.ReadUInt32(); 33 | var filename = r.ReadBytes((int)filenameLength); 34 | var var1 = r.ReadUInt32(); 35 | var fileType = r.ReadUInt32(); 36 | var uid = r.ReadUInt64(); 37 | 38 | var name = NameEncoding.DecodeName(filename, fileType, uid, 0, NameEncoding.FILENAME_ENCODING_FILE_KEY_STEP); 39 | return new FileMetaData(Encoding.ASCII.GetString(name), filename, var1, fileType, uid); 40 | } 41 | case <= 29: 42 | { 43 | var fileType = r.ReadUInt32(); 44 | var var1 = r.ReadUInt32(); 45 | var var2 = r.ReadUInt32(); 46 | var uid = r.ReadUInt64(); 47 | 48 | return new FileMetaData("", Array.Empty(), var1, fileType, uid); 49 | } 50 | default: 51 | throw new NotImplementedException($"Unsupported version {version}"); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /AssetCatalog/AssetCatalog.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net5.0-windows10.0.18362.0 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | 37 | 38 | 39 | bin\Release\net5.0-windows10.0.18362.0\runtimes\win\lib\netcoreapp3.0\System.Drawing.Common.dll 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarGlobalMeta.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace RainbowScimitar.Scimitar 7 | { 8 | public record ScimitarGlobalMeta(int Unknown1, Dictionary Entries) 9 | { 10 | public enum DataType 11 | { 12 | Null = 0x0, 13 | LengthPrefixedAsciiString = 0x1, 14 | Int64A = 0x2, 15 | Int64B = 0x5 16 | } 17 | 18 | public record Entry(DataType DataType, object Value); 19 | 20 | public static ScimitarGlobalMeta Read(Stream bundleStream) 21 | { 22 | var r = new BinaryReader(bundleStream); 23 | 24 | var unk1 = r.ReadInt32(); 25 | 26 | var entries = new Dictionary(); 27 | 28 | while (true) 29 | { 30 | var id = r.ReadInt32(); 31 | var type = (DataType)r.ReadInt32(); 32 | 33 | if (!Enum.IsDefined(typeof(DataType), type)) 34 | throw new NotSupportedException(); 35 | 36 | if (type == DataType.Null) 37 | break; 38 | 39 | switch (type) 40 | { 41 | case DataType.LengthPrefixedAsciiString: 42 | { 43 | var strLength = r.ReadInt32(); 44 | var strBytes = r.ReadBytes(strLength); 45 | var str = Encoding.ASCII.GetString(strBytes); 46 | 47 | r.ReadByte(); // null terminator 48 | 49 | entries[id] = new Entry(type, str); 50 | break; 51 | } 52 | case DataType.Int64A: 53 | { 54 | var i = r.ReadInt64(); 55 | entries[id] = new Entry(type, i); 56 | break; 57 | } 58 | case DataType.Int64B: 59 | { 60 | var i = r.ReadInt64(); 61 | entries[id] = new Entry(type, i); 62 | break; 63 | } 64 | default: 65 | throw new ArgumentOutOfRangeException(); 66 | } 67 | } 68 | 69 | return new ScimitarGlobalMeta(unk1, entries); 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /RainbowForge/Model/MeshObjectHeader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.Model 4 | { 5 | /// 6 | /// Holds data needed to construct a given island. 7 | /// What a tris_chunk is: 8 | /// triangles are stored in chunks. Each chunk is 0x180 bytes long. 9 | /// If given chunk is the last one in island ant is not filled till the 10 | /// end, it gets filled with last vert's id, forming invalid triangles. 11 | /// Example of an end of such chunk: 12 | /// ...: ... 13 | /// 0x160: 0F AB 1C AB 1B AB 1A AB 1B AB 1f AB 10 AB 11 AB 14 | /// 0x170: 12 AB 11 AB|11 AB 11 AB 11 AB 11 AB 11 AB 11 AB 15 | /// ^ |^ 16 | /// last valid tri's id|buffer filled with last id 17 | /// 18 | public class MeshObjectHeader 19 | { 20 | public uint OffsetVerts { get; } 21 | public uint NumVerts { get; } 22 | public uint OffsetFaceChunks { get; } 23 | public uint NumFaceChunks { get; } 24 | public uint MatId { get; } 25 | 26 | public MeshObjectHeader(uint offsetVerts, uint numVerts, uint offsetFaceChunks, uint numFaceChunks, uint matId) 27 | { 28 | OffsetVerts = offsetVerts; 29 | NumVerts = numVerts; 30 | OffsetFaceChunks = offsetFaceChunks; 31 | NumFaceChunks = numFaceChunks; 32 | MatId = matId; 33 | } 34 | 35 | public static MeshObjectHeader Read(BinaryReader r) 36 | { 37 | var x00 = r.ReadUInt32(); 38 | var offsetVerts = r.ReadUInt32(); 39 | var numVerts = r.ReadUInt32(); 40 | var offsetFaceChunks = r.ReadUInt32(); // tris chunks offset 41 | var numFaceChunks = r.ReadUInt32(); // tris chunks num per this island 42 | var matId = r.ReadUInt32(); 43 | var x18 = r.ReadUInt32(); 44 | var x1C = r.ReadUInt32(); 45 | var x20 = r.ReadUInt32(); 46 | 47 | return new MeshObjectHeader(offsetVerts, numVerts, offsetFaceChunks, numFaceChunks, matId); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /RainbowForge/BinaryReaderExt.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Numerics; 3 | using RainbowForge.Model; 4 | using RainbowForge.Structs; 5 | 6 | namespace RainbowForge 7 | { 8 | public static class BinaryReaderExt 9 | { 10 | public static Vector3 ReadVector3(this BinaryReader r) 11 | { 12 | var x = r.ReadSingle(); 13 | var y = r.ReadSingle(); 14 | var z = r.ReadSingle(); 15 | 16 | return new Vector3(x, y, z); 17 | } 18 | 19 | public static Vector3 ReadUInt64AsPos(this BinaryReader r) 20 | { 21 | const float bias = 0x7FFF; 22 | 23 | var x = (float)r.ReadInt16(); 24 | var y = (float)r.ReadInt16(); 25 | var z = (float)r.ReadInt16(); 26 | var s = (float)r.ReadInt16(); 27 | 28 | return new Vector3(x * s / bias, y * s / bias, z * s / bias); 29 | } 30 | 31 | public static Vector3 ReadUInt32AsVec(this BinaryReader r) 32 | { 33 | const float bias = 0x7F; 34 | 35 | var x = r.ReadByte(); 36 | var y = r.ReadByte(); 37 | var z = r.ReadByte(); 38 | var l = r.ReadByte(); 39 | 40 | return new Vector3(x / bias - 1, y / bias - 1, z / bias - 1); 41 | } 42 | 43 | public static Color4 ReadUInt32AsColor(this BinaryReader r) 44 | { 45 | const float bias = 0xFF; 46 | 47 | var red = r.ReadByte(); 48 | var green = r.ReadByte(); 49 | var blue = r.ReadByte(); 50 | var alpha = r.ReadByte(); 51 | 52 | return new Color4(red / bias, green / bias, blue / bias, alpha / bias); 53 | } 54 | 55 | public static Vector2 ReadUInt32AsUv(this BinaryReader r) 56 | { 57 | var vec = r.ReadStruct(4); 58 | return new Vector2((float)vec.X, (float)vec.Y); 59 | } 60 | 61 | public static T ReadStruct(this BinaryReader stream, int size) where T : struct 62 | { 63 | var buf = new byte[size]; 64 | if (stream.Read(buf, 0, size) != size) 65 | throw new EndOfStreamException(); 66 | 67 | return buf.ToStruct(); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /AssetCatalog/Model/ICatalogDb.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | using AssetCatalog.Converters; 4 | using LiteDB; 5 | 6 | namespace AssetCatalog.Model 7 | { 8 | public interface ICatalogDb 9 | { 10 | public bool NeedsAuth(); 11 | public Task Connect(string email, string password); 12 | 13 | public CatalogEntry Get(ulong uid); 14 | public void Put(ulong uid, CatalogEntry entry); 15 | } 16 | 17 | public class CatalogEntry 18 | { 19 | [BsonId(false)] public ulong Uid { get; set; } 20 | 21 | public CatalogEntryStatus Status { get; set; } = CatalogEntryStatus.PartiallyComplete; 22 | public CatalogAssetCategory Category { get; set; } = CatalogAssetCategory.Uncategorized; 23 | 24 | public string Name { get; set; } = ""; 25 | public string Notes { get; set; } = ""; 26 | 27 | /// 28 | public override string ToString() 29 | { 30 | return Name; 31 | } 32 | } 33 | 34 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 35 | public enum CatalogAssetCategory 36 | { 37 | [Description("Uncategorized")] Uncategorized, 38 | [Description("GUI Texture")] GuiTexture, 39 | [Description("Model Texture")] ModelTexture, 40 | [Description("Operator Headgear")] OperatorHeadgear, 41 | [Description("Operator Body")] OperatorFullBody, 42 | [Description("Operator Hands")] OperatorHands, 43 | [Description("Operator Legs")] OperatorLegs, 44 | [Description("Gadget")] Gadget, 45 | [Description("Weapon")] Weapon, 46 | [Description("Weapon Accessory")] WeaponAccessory, // TODO: split 47 | [Description("Charm")] Charm, 48 | [Description("Map Prop")] MapProp, 49 | [Description("Junk")] Junk 50 | } 51 | 52 | [TypeConverter(typeof(EnumDescriptionTypeConverter))] 53 | public enum CatalogEntryStatus 54 | { 55 | [Description("Incomplete")] Incomplete, 56 | [Description("Partially Complete")] PartiallyComplete, 57 | [Description("Complete")] Complete 58 | } 59 | } -------------------------------------------------------------------------------- /DumpTool/FindAllMeshPropsCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CommandLine; 4 | using RainbowForge; 5 | using RainbowForge.Archive; 6 | using RainbowForge.Core; 7 | using RainbowForge.Core.Container; 8 | using RainbowForge.Dump; 9 | 10 | namespace DumpTool 11 | { 12 | [Verb("findallmeshprops", HelpText = "Find all MeshProperties containers which reference the given UID in all flat archives in the given forge file")] 13 | public class FindAllMeshPropsCommand 14 | { 15 | [Value(0, HelpText = "The forge file to reference")] 16 | public string ForgeFilename { get; set; } 17 | 18 | [Value(1, HelpText = "The UID to search for")] 19 | public ulong Uid { get; set; } 20 | 21 | public static void Run(FindAllMeshPropsCommand args) 22 | { 23 | var forge = Forge.GetForge(args.ForgeFilename); 24 | foreach (var entry in forge.Entries) 25 | try 26 | { 27 | if (SearchFlatArchive(forge, entry, args.Uid)) 28 | Console.WriteLine(entry.Uid); 29 | } 30 | catch 31 | { 32 | // ignored 33 | } 34 | } 35 | 36 | public static bool SearchFlatArchive(Forge forge, Entry entry, ulong uid) 37 | { 38 | if (MagicHelper.GetFiletype(entry.MetaData.FileType) != AssetType.FlatArchive) 39 | return false; 40 | 41 | var container = forge.GetContainer(entry.Uid); 42 | if (container is not ForgeAsset forgeAsset) 43 | return false; 44 | 45 | var assetStream = forgeAsset.GetDataStream(forge); 46 | var arc = FlatArchive.Read(assetStream, forge.Version); 47 | 48 | foreach (var meshProp in arc.Entries) 49 | { 50 | var unresolvedExterns = new List(); 51 | 52 | try 53 | { 54 | DumpHelper.SearchNonContainerChildren(assetStream, arc, meshProp, unresolvedExterns); 55 | } 56 | catch 57 | { 58 | // ignored 59 | } 60 | 61 | if (unresolvedExterns.Contains(uid)) 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /RainbowForge/Core/Container/ForgeAsset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using RainbowForge.Core.DataBlock; 4 | 5 | namespace RainbowForge.Core.Container 6 | { 7 | public class ForgeAsset : ForgeContainer 8 | { 9 | public IAssetBlock MetaBlock { get; } 10 | public ulong AssetBlockMagic { get; } 11 | public IAssetBlock AssetBlock { get; } 12 | 13 | public bool HasMeta => MetaBlock != null; 14 | 15 | private ForgeAsset(IAssetBlock metaBlock, ulong assetBlockMagic, IAssetBlock assetBlock) 16 | { 17 | AssetBlockMagic = assetBlockMagic; 18 | MetaBlock = metaBlock; 19 | AssetBlock = assetBlock; 20 | } 21 | 22 | public static ForgeAsset Read(BinaryReader r, Entry entry) 23 | { 24 | var magic = r.ReadUInt32(); 25 | // MagicHelper.AssertEquals(Magic.FileContainer, magic); 26 | 27 | var end = entry.End; 28 | 29 | var blockA = GetAssetBlock(r, entry); 30 | 31 | if (r.BaseStream.Position >= end) 32 | return new ForgeAsset(null, 0, blockA); 33 | 34 | var assetBlockMagic = r.ReadUInt64(); // this might actually be 8 shorts and 16 0x00 bytes 35 | 36 | var blockB = GetAssetBlock(r, entry); 37 | 38 | return new ForgeAsset(blockA, assetBlockMagic, blockB); 39 | } 40 | 41 | private static IAssetBlock GetAssetBlock(BinaryReader r, Entry entry) 42 | { 43 | var x = r.ReadUInt16(); // 2 (changed to 3 in Y5) <-- container deserializer type 44 | var assetDeserializerType = r.ReadUInt16(); // 3 for chunked, 7 for linear (flags?) 45 | var y = r.ReadByte(); // 0 46 | var z = r.ReadUInt16(); 47 | 48 | return assetDeserializerType switch 49 | { 50 | 3 => ChunkedDataBlock.Read(r), 51 | 13 => ChunkedDataBlock.Read(r, true), 52 | 7 => FlatDataBlock.Read(r, entry), 53 | _ => throw new NotImplementedException() 54 | }; 55 | } 56 | 57 | public BinaryReader GetDataStream(Forge forge) 58 | { 59 | return new BinaryReader(AssetBlock.GetDataStream(forge.Stream)); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /RainbowScimitar/FileTypes/DepGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using RainbowScimitar.Extensions; 6 | 7 | namespace RainbowScimitar.FileTypes 8 | { 9 | public record DepGraph 10 | { 11 | public static DepGraph Read(Stream s) 12 | { 13 | // var fileData = IScimitarFileData.Read(s); 14 | // var dataStream = fileData.GetStream(s); 15 | 16 | var r = new BinaryReader(s); 17 | 18 | // var unk1 = r.ReadByte(); // 2 19 | 20 | while (r.BaseStream.Position < r.BaseStream.Length) 21 | { 22 | var root = DepGraphNode.Read(r); 23 | } 24 | 25 | foreach (var (i, count) in DepGraphNode.Data.OrderByDescending(pair => pair.Value)) 26 | Console.WriteLine($"{i:X6}: {count}"); 27 | 28 | return null; 29 | } 30 | } 31 | 32 | public record DepGraphNode 33 | { 34 | public static Dictionary Data = new Dictionary(); 35 | 36 | public static DepGraphNode Read(BinaryReader r) 37 | { 38 | // datapc64_ui_playgo.depgraphbin.unpacked.bin: (zero entries with sub-entries) 39 | // - unk3 EF: 39929 40 | 41 | // datapc64_pvp20_italy.depgraphbin.unpacked: (57 entries with sub-entries) 42 | // - unk3 EF: 23950 43 | // - unk3 FF: 3702 44 | 45 | var parentUid = r.ReadUid(); 46 | var childUid = r.ReadUid(); 47 | 48 | var unk2 = r.ReadInt32(); // -1 49 | 50 | var unk3 = r.ReadByte(); // 0xEF, 0xFF 51 | var unk4 = r.ReadByte(); // 0x1 52 | var unk5 = r.ReadByte(); 53 | /* 54 | unk5 == 55 | 0 00000000 56 | 1 00000001 57 | 2 00000010 58 | 3 00000011 59 | 4 00000100 60 | 5 00000101 61 | 8 00001000 62 | 9 00001001 63 | A 00001010 64 | B 00001011 65 | */ 66 | 67 | var unk6 = r.ReadByte(); 68 | /* 69 | unk6 == 70 | 0 00000000 71 | 1 00000001 72 | 5 00000101 73 | 7 00000111 74 | */ 75 | 76 | var unk56 = unk5; //(unk3 << 16) | (unk5 << 8) | unk6; 77 | if (!Data.ContainsKey(unk56)) 78 | Data[unk56] = 0; 79 | Data[unk56]++; 80 | 81 | return null; 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /RainbowForge/Core/DepGraph.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | using RainbowForge.Core.DataBlock; 4 | 5 | namespace RainbowForge.Core 6 | { 7 | public class DepGraph 8 | { 9 | public DepGraphEntry[] Structs { get; private set; } 10 | 11 | private DepGraph(DepGraphEntry[] structs) 12 | { 13 | Structs = structs; 14 | } 15 | 16 | public static DepGraph Read(BinaryReader r) 17 | { 18 | var containerMagic = (ContainerMagic)r.ReadUInt32(); 19 | if (containerMagic != ContainerMagic.File) 20 | throw new InvalidDataException($"Expected container magic, found 0x{containerMagic:X8}"); 21 | 22 | var fileContainerMagic = r.ReadUInt32(); 23 | MagicHelper.AssertEquals(Magic.FileContainer, fileContainerMagic); 24 | 25 | var x = r.ReadUInt16(); // 2 (changed to 3 in Y5) <-- container deserializer type 26 | var assetDeserializerType = r.ReadUInt16(); // 3 for chunked, 7 for linear (flags?) 27 | var y = r.ReadByte(); // 0 28 | var z = r.ReadUInt16(); 29 | 30 | if (assetDeserializerType != 3) 31 | throw new InvalidDataException($"Expected chunked asset deserializer magic, found 0x{assetDeserializerType:X4}"); 32 | 33 | var dataBlock = ChunkedDataBlock.Read(r); 34 | var stream = dataBlock.GetDataStream(r); 35 | 36 | using var depGraphStream = new BinaryReader(stream); 37 | var x00 = depGraphStream.ReadByte(); // == 2 38 | 39 | var numStructs = stream.Length / DepGraphEntry.SizeInBytes; 40 | 41 | var structs = new DepGraphEntry[numStructs]; 42 | 43 | for (var i = 0; i < numStructs; i++) 44 | structs[i] = depGraphStream.ReadStruct(DepGraphEntry.SizeInBytes); 45 | 46 | return new DepGraph(structs); 47 | } 48 | } 49 | 50 | [StructLayout(LayoutKind.Explicit)] 51 | public struct DepGraphEntry 52 | { 53 | public const int SizeInBytes = 24; 54 | 55 | [FieldOffset(0)] public ulong ParentUid; 56 | [FieldOffset(8)] public ulong ChildUid; 57 | [FieldOffset(16)] public uint ChildType; 58 | [FieldOffset(20)] public ushort Unk1; 59 | [FieldOffset(22)] public byte Unk2; 60 | [FieldOffset(23)] public byte Unk3; 61 | } 62 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using RainbowForge.Core; 6 | 7 | namespace RainbowScimitar.Scimitar 8 | { 9 | public class ScimitarBuilder 10 | { 11 | public Dictionary Entries = new(); 12 | public ScimitarId GlobalMetaFileId { get; set; } 13 | 14 | public void Write(Stream fileStream) 15 | { 16 | var w = new BinaryWriter(fileStream); 17 | var formatId = Encoding.ASCII.GetBytes("scimitar\x00"); 18 | 19 | w.Write(formatId); 20 | 21 | const int version = 30; 22 | w.Write(version); 23 | 24 | var fatLocationPos = fileStream.Position; 25 | w.Write(0); // Fat Location 26 | 27 | w.Write(0); // 0 28 | 29 | w.Write(GlobalMetaFileId); 30 | 31 | w.Write((byte)1); // 1 32 | 33 | var tables = CreateTables(); 34 | var directories = CreateDirectories(); 35 | 36 | var numEntries = tables.Sum(table => table.Count); 37 | w.Write(numEntries); 38 | w.Write(directories.Count); // TODO: directories 39 | 40 | w.Write(2); // unk3 == 2 41 | w.Write(2); // unk4 == 2 42 | w.Write(0); // unk4b == 0 43 | 44 | w.Write(-1); // FirstFreeFile == -1 45 | w.Write(-1); // FirstFreeDir == -1 46 | 47 | w.Write(numEntries + directories.Count); 48 | w.Write(tables.Count); 49 | 50 | w.Write(fileStream.Position + sizeof(ulong)); // FirstTablePosition -- pack tightly against header 51 | 52 | foreach (var t in tables) 53 | WriteTable(w, t); 54 | } 55 | 56 | private void WriteTable(BinaryWriter w, Dictionary table) 57 | { 58 | } 59 | 60 | private List CreateDirectories() 61 | { 62 | // No directories 63 | return new List(); 64 | } 65 | 66 | private List> CreateTables() 67 | { 68 | // current strategy for creating tables is to put all entries into the same table 69 | return new List> 70 | { 71 | Entries 72 | }; 73 | } 74 | } 75 | 76 | public record UnbakedScimitarEntry(EntryMetaData MetaData, Stream DataStream); 77 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/Material.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RainbowForge.RenderPipeline 4 | { 5 | public class Material 6 | { 7 | public TextureSelector[] BaseTextureMapSpecs { get; } 8 | public TextureSelector[] SecondaryTextureMapSpecs { get; } 9 | public TextureSelector[] TertiaryTextureMapSpecs { get; } 10 | 11 | private Material(TextureSelector[] baseTextureMapSpecs, TextureSelector[] secondaryTextureMapSpecs, TextureSelector[] tertiaryTextureMapSpecs) 12 | { 13 | BaseTextureMapSpecs = baseTextureMapSpecs; 14 | SecondaryTextureMapSpecs = secondaryTextureMapSpecs; 15 | TertiaryTextureMapSpecs = tertiaryTextureMapSpecs; 16 | } 17 | 18 | public static Material Read(BinaryReader r) 19 | { 20 | var magic1 = r.ReadUInt32(); 21 | MagicHelper.AssertEquals(Magic.Material, magic1); 22 | 23 | var baseMipContainers = new TextureSelector[5]; 24 | for (var i = 0; i < baseMipContainers.Length; i++) 25 | baseMipContainers[i] = TextureSelector.Read(r); 26 | 27 | var padding = r.ReadUInt64(); 28 | 29 | var magic2 = r.ReadUInt32(); 30 | MagicHelper.AssertEquals(Magic.UVTransform, magic2); 31 | 32 | var uvTransform1 = r.ReadBytes(51); 33 | 34 | var secondaryMipContainers = new TextureSelector[3]; 35 | for (var i = 0; i < secondaryMipContainers.Length; i++) 36 | secondaryMipContainers[i] = TextureSelector.Read(r); 37 | 38 | var padding2 = r.ReadUInt64(); 39 | 40 | var magic3 = r.ReadUInt32(); 41 | MagicHelper.AssertEquals(Magic.UVTransform, magic3); 42 | 43 | var uvTransform2 = r.ReadBytes(51); 44 | 45 | var internalUid1 = r.ReadUInt64(); 46 | var magic4 = r.ReadUInt32(); 47 | MagicHelper.AssertEquals(Magic.DetailMapDescriptor, magic4); 48 | 49 | var detailMapDesc1 = r.ReadBytes(16); 50 | 51 | var internalUid2 = r.ReadUInt64(); 52 | var magic5 = r.ReadUInt32(); 53 | MagicHelper.AssertEquals(Magic.DetailMapDescriptor, magic5); 54 | 55 | var detailMapDesc2 = r.ReadBytes(16); 56 | 57 | var tertiaryMipContainers = new TextureSelector[2]; 58 | for (var i = 0; i < tertiaryMipContainers.Length; i++) 59 | tertiaryMipContainers[i] = TextureSelector.Read(r); 60 | 61 | return new Material(baseMipContainers, secondaryMipContainers, tertiaryMipContainers); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /RainbowForge/MagicHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace RainbowForge 6 | { 7 | public static class MagicHelper 8 | { 9 | public static readonly HashSet UnknownMagics = new() 10 | { 11 | // Notable magics 12 | 0x61CDA86C, // Not ShotClassificationInfo even though the CRC fits, this magic is unicode font-related 13 | // Totally unknown 14 | 0x2FEF6C7F, 0x01DD1BAE, 0x07E763B3, 0x854A3D0B, 0x6A52331D, 0x297D86AF, 0x87EEAF47, 0x431843B9, 0xC8241D45, 15 | 0x9DFC17AC 16 | }; 17 | 18 | public static void AssertEquals(Magic magic, ulong value) 19 | { 20 | if (!Equals(magic, value)) 21 | throw new InvalidDataException(); 22 | } 23 | 24 | public static bool Equals(Magic magic, ulong value) 25 | { 26 | return (Magic)value == magic; 27 | } 28 | 29 | public static AssetType GetFiletype(ulong magic) 30 | { 31 | if (!Enum.IsDefined(typeof(Magic), magic)) 32 | return AssetType.Unknown; 33 | 34 | return (Magic)magic switch 35 | { 36 | Magic.CompiledMeshObject => AssetType.Mesh, 37 | Magic.CompiledLowResolutionTextureMap => AssetType.Texture, 38 | Magic.CompiledMediumResolutionTextureMap => AssetType.Texture, 39 | Magic.CompiledHighResolutionTextureMap => AssetType.Texture, 40 | Magic.CompiledUltraResolutionTextureMap => AssetType.Texture, 41 | Magic.CompiledFutureResolutionTextureMap => AssetType.Texture, 42 | Magic.CompiledLowResolutionGuiTextureMap => AssetType.Texture, 43 | Magic.CompiledMediumResolutionGuiTextureMap => AssetType.Texture, 44 | Magic.CompiledSoundMedia => AssetType.Sound, 45 | Magic.EntityBuilder => AssetType.FlatArchive, 46 | Magic.WeaponData => AssetType.FlatArchive, 47 | Magic.GameBootstrap => AssetType.FlatArchive, 48 | Magic.PlatformManager => AssetType.FlatArchive, 49 | Magic.World => AssetType.FlatArchive, 50 | Magic.LoadUnit => AssetType.FlatArchive, 51 | Magic.CompiledSoundBank => AssetType.FlatArchive, 52 | Magic.BuildTable => AssetType.FlatArchive, 53 | Magic.CompiledMeshShapeDataObject => AssetType.FlatArchive, 54 | Magic.GIStream => AssetType.FlatArchive, 55 | Magic.WorldMetaData => AssetType.FlatArchive, 56 | _ => AssetType.Unknown 57 | }; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Prism/Render/Shader/UniformCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Prism.Render.Shader 6 | { 7 | public class UniformCollection : ICollection 8 | { 9 | private readonly Dictionary _uniforms; 10 | 11 | public UniformCollection() 12 | { 13 | _uniforms = new Dictionary(); 14 | } 15 | 16 | public ShaderUniform Get(string key) 17 | { 18 | if (_uniforms.ContainsKey(key)) 19 | return (ShaderUniform)_uniforms[key]; 20 | return null; 21 | } 22 | 23 | public void Set(string key, ShaderUniform value) 24 | { 25 | _uniforms[key] = value; 26 | } 27 | 28 | public T GetValue(string key) 29 | { 30 | var uniform = Get(key); 31 | if (uniform == null) 32 | throw new KeyNotFoundException(); 33 | return uniform.Value; 34 | } 35 | 36 | public void SetValue(string key, T value) 37 | { 38 | if (_uniforms.ContainsKey(key)) 39 | { 40 | var uniform = _uniforms[key]; 41 | if (typeof(T) != uniform.UniformType) 42 | throw new ArrayTypeMismatchException(); 43 | 44 | uniform.Value = value; 45 | } 46 | else 47 | { 48 | _uniforms[key] = new ShaderUniform(key, value); 49 | } 50 | } 51 | 52 | public int Count => _uniforms.Count; 53 | public bool IsReadOnly => false; 54 | 55 | public IEnumerator GetEnumerator() 56 | { 57 | return _uniforms.Values.GetEnumerator(); 58 | } 59 | 60 | IEnumerator IEnumerable.GetEnumerator() 61 | { 62 | return GetEnumerator(); 63 | } 64 | 65 | public void Add(GenericShaderUniform item) 66 | { 67 | if (item == null) 68 | return; 69 | 70 | _uniforms[item.Name] = item; 71 | } 72 | 73 | public void Clear() 74 | { 75 | _uniforms.Clear(); 76 | } 77 | 78 | public bool Contains(GenericShaderUniform item) 79 | { 80 | return item != null && _uniforms.ContainsKey(item.Name); 81 | } 82 | 83 | public void CopyTo(GenericShaderUniform[] array, int arrayIndex) 84 | { 85 | _uniforms.Values.CopyTo(array, arrayIndex); 86 | } 87 | 88 | public bool Remove(GenericShaderUniform item) 89 | { 90 | return item != null && _uniforms.Remove(item.Name); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Shader/UniformCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace AssetCatalog.Render.Shader 6 | { 7 | public class UniformCollection : ICollection 8 | { 9 | private readonly Dictionary _uniforms; 10 | 11 | public UniformCollection() 12 | { 13 | _uniforms = new Dictionary(); 14 | } 15 | 16 | public ShaderUniform Get(string key) 17 | { 18 | if (_uniforms.ContainsKey(key)) 19 | return (ShaderUniform) _uniforms[key]; 20 | return null; 21 | } 22 | 23 | public void Set(string key, ShaderUniform value) 24 | { 25 | _uniforms[key] = value; 26 | } 27 | 28 | public T GetValue(string key) 29 | { 30 | var uniform = Get(key); 31 | if (uniform == null) 32 | throw new KeyNotFoundException(); 33 | return uniform.Value; 34 | } 35 | 36 | public void SetValue(string key, T value) 37 | { 38 | if (_uniforms.ContainsKey(key)) 39 | { 40 | var uniform = _uniforms[key]; 41 | if (typeof(T) != uniform.UniformType) 42 | throw new ArrayTypeMismatchException(); 43 | 44 | uniform.Value = value; 45 | } 46 | else 47 | { 48 | _uniforms[key] = new ShaderUniform(key, value); 49 | } 50 | } 51 | 52 | public int Count => _uniforms.Count; 53 | public bool IsReadOnly => false; 54 | 55 | public IEnumerator GetEnumerator() 56 | { 57 | return _uniforms.Values.GetEnumerator(); 58 | } 59 | 60 | IEnumerator IEnumerable.GetEnumerator() 61 | { 62 | return GetEnumerator(); 63 | } 64 | 65 | public void Add(GenericShaderUniform item) 66 | { 67 | if (item == null) 68 | return; 69 | 70 | _uniforms[item.Name] = item; 71 | } 72 | 73 | public void Clear() 74 | { 75 | _uniforms.Clear(); 76 | } 77 | 78 | public bool Contains(GenericShaderUniform item) 79 | { 80 | return item != null && _uniforms.ContainsKey(item.Name); 81 | } 82 | 83 | public void CopyTo(GenericShaderUniform[] array, int arrayIndex) 84 | { 85 | _uniforms.Values.CopyTo(array, arrayIndex); 86 | } 87 | 88 | public bool Remove(GenericShaderUniform item) 89 | { 90 | return item != null && _uniforms.Remove(item.Name); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /RainbowForge/Core/Container/Descriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace RainbowForge.Core.Container 7 | { 8 | public class Descriptor : ForgeContainer 9 | { 10 | public Dictionary UIntData { get; } 11 | public Dictionary StringData { get; } 12 | public Dictionary ULongData { get; } 13 | public Dictionary UlongData2 { get; } 14 | 15 | private Descriptor(Dictionary uIntData, Dictionary stringData, Dictionary uLongData, Dictionary ulongData2) 16 | { 17 | UIntData = uIntData; 18 | StringData = stringData; 19 | ULongData = uLongData; 20 | UlongData2 = ulongData2; 21 | } 22 | 23 | public static Descriptor Read(BinaryReader r, Entry entry) 24 | { 25 | var uintData = new Dictionary(); 26 | var stringData = new Dictionary(); 27 | var ulongData = new Dictionary(); 28 | var ulongData2 = new Dictionary(); 29 | 30 | while (r.BaseStream.Position < entry.End) 31 | { 32 | var dataId = r.ReadUInt32(); 33 | var dataType = r.ReadUInt32(); 34 | 35 | switch (dataType) 36 | { 37 | case 0x0: // uint 38 | if (uintData.ContainsKey(dataId)) 39 | throw new InvalidOperationException(); 40 | uintData[dataId] = r.ReadUInt32(); 41 | break; 42 | case 0x1: // string 43 | if (stringData.ContainsKey(dataId)) 44 | throw new InvalidOperationException(); 45 | var strLen = r.ReadInt32(); 46 | stringData[dataId] = Encoding.UTF8.GetString(r.ReadBytes(strLen)); 47 | r.ReadByte(); // 0x00 48 | break; 49 | case 0x2: // ulong 50 | if (ulongData.ContainsKey(dataId)) 51 | throw new InvalidOperationException(); 52 | ulongData[dataId] = r.ReadUInt64(); 53 | break; 54 | case 0x5: // ??? 55 | if (ulongData2.ContainsKey(dataId)) 56 | throw new InvalidOperationException(); 57 | ulongData2[dataId] = r.ReadUInt64(); 58 | break; 59 | default: 60 | throw new InvalidDataException($"Unknown data type! Data ID: 0x{dataId:X}, type 0x{dataType:X}"); 61 | } 62 | } 63 | 64 | return new Descriptor(uintData, stringData, ulongData, ulongData2); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarArchive.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | 4 | namespace RainbowScimitar.Scimitar 5 | { 6 | public record ScimitarArchive(IScimitarFileData FileData, IScimitarFileData MetaData, ScimitarArchiveFileData[] SubFileData) 7 | { 8 | public static ScimitarArchive Read(Stream bundleStream) 9 | { 10 | var metaData = IScimitarFileData.Read(bundleStream); 11 | var fileData = IScimitarFileData.Read(bundleStream); 12 | 13 | using var rMeta = new BinaryReader(metaData.GetStream(bundleStream)); 14 | var numMetaSubFileData = rMeta.ReadInt32(); 15 | var metaSubFileData = new ScimitarArchiveFileData[numMetaSubFileData]; 16 | 17 | // TODO 18 | // if (magic is (Magic.CompiledLowResolutionTextureMap or Magic.CompiledMediumResolutionTextureMap 19 | // or Magic.CompiledHighResolutionTextureMap or Magic.CompiledFutureResolutionTextureMap or Magic.CompiledUltraResolutionTextureMap or Magic.TextureGui0 or Magic.TextureGui1 20 | // or Magic.CompiledSoundMedia or Magic.CompiledSoundBank)) 21 | // { 22 | // // Read "big" ScimitarSubFileData 23 | // // "Big" is either 28 bytes per entry, or 12 bytes per entry with 16 bytes of extra data 24 | // // at the end. Unable to confirm because all "big" ScimitarSubFileData sets only have one entry. 25 | // } 26 | 27 | var offset = 0; 28 | for (var i = 0; i < numMetaSubFileData; i++) 29 | { 30 | var data = ScimitarArchiveFileData.Read(rMeta) with 31 | { 32 | Offset = offset 33 | }; 34 | 35 | offset += data.Length; 36 | metaSubFileData[i] = data; 37 | } 38 | 39 | return new ScimitarArchive(fileData, metaData, metaSubFileData); 40 | } 41 | 42 | public (ScimitarArchiveFileMetadata metadata, Stream stream) GetSubFile(Stream fileStream, int index) 43 | { 44 | var (_, _, offset) = SubFileData[index]; 45 | 46 | fileStream.Seek(offset, SeekOrigin.Begin); 47 | var metadata = ScimitarArchiveFileMetadata.Read(fileStream); 48 | 49 | // TODO: is it valid to replace {length} with {metadata.Size} here? 50 | // ulong size (8 bytes) is subtracted because {metadata.Size} is always that much too big -- it's 51 | // possible that the UID isn't part of ScimitarArchiveFileMetadata, it's part of the asset stream 52 | return (metadata, new SubStream(fileStream, fileStream.Position, metadata.Size - sizeof(ulong))); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /RainbowForge/SubStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RainbowForge 5 | { 6 | public class SubStream : Stream, IDisposable 7 | { 8 | private readonly Stream _source; 9 | private readonly int _length; 10 | private readonly long _minPos; 11 | private readonly long _maxPos; 12 | 13 | public SubStream(Stream source, long offset, int length) 14 | { 15 | _source = source; 16 | _minPos = offset; 17 | _length = length; 18 | _maxPos = offset + length; 19 | 20 | if (offset + length > source.Length) 21 | throw new EndOfStreamException(); 22 | } 23 | 24 | /// 25 | protected override void Dispose(bool disposing) 26 | { 27 | if (disposing) 28 | { 29 | _source?.Dispose(); 30 | } 31 | 32 | base.Dispose(disposing); 33 | } 34 | 35 | /// 36 | public override void Flush() 37 | { 38 | _source.Flush(); 39 | } 40 | 41 | /// 42 | public override int Read(byte[] buffer, int offset, int count) 43 | { 44 | return _source.Read(buffer, offset, Math.Max(Math.Min((int)(_maxPos - _source.Position), count), 0)); 45 | } 46 | 47 | /// 48 | public override long Seek(long offset, SeekOrigin origin) 49 | { 50 | switch (origin) 51 | { 52 | case SeekOrigin.Begin: 53 | return _source.Seek(offset + _minPos, SeekOrigin.Begin); 54 | case SeekOrigin.Current: 55 | return _source.Seek(offset, SeekOrigin.Current); 56 | case SeekOrigin.End: 57 | return _source.Seek(_maxPos - offset, SeekOrigin.Begin); 58 | default: 59 | throw new ArgumentOutOfRangeException(nameof(origin), origin, null); 60 | } 61 | } 62 | 63 | /// 64 | public override void SetLength(long value) => throw new NotSupportedException(); 65 | 66 | /// 67 | public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException(); 68 | 69 | /// 70 | public override bool CanRead => _source.CanRead; 71 | 72 | /// 73 | public override bool CanSeek => _source.CanSeek; 74 | 75 | /// 76 | public override bool CanWrite => false; 77 | 78 | /// 79 | public override long Length => _length; 80 | 81 | /// 82 | public override long Position 83 | { 84 | get => _source.Position - _minPos; 85 | set => _source.Position = _minPos + value; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Prism/Resources/model.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | uniform sampler2D texModel; 4 | uniform sampler2D texReflection; 5 | uniform sampler2D texUv; 6 | uniform sampler2D texRadiance; 7 | 8 | uniform vec3 lightPos; 9 | uniform vec3 lightColor; 10 | 11 | uniform int useReflections; 12 | uniform int useCheckerboard; 13 | 14 | in vec3 fragPos; 15 | in vec3 fragNormal; 16 | in vec3 fragVertColor; 17 | in vec2 fragTexCoord; 18 | in vec2 matcapTexCoord; 19 | flat in int fragObjectId; 20 | 21 | out vec4 color; 22 | 23 | #define NUM_COLOR_LOOKUPS 8 24 | vec3 COLOR_LOOKUP[NUM_COLOR_LOOKUPS] = vec3[NUM_COLOR_LOOKUPS]( 25 | vec3(0.85, 0.32, 0.30), 26 | vec3(0.36, 0.75, 0.87), 27 | vec3(0.36, 0.72, 0.36), 28 | vec3(0.26, 0.55, 0.79), 29 | vec3(1.00, 0.75, 0.14), 30 | vec3(0.95, 0.46, 0.20), 31 | vec3(0.89, 0.12, 0.91), 32 | vec3(0.28, 0.56, 0.56) 33 | ); 34 | 35 | vec4 GRAY80 = vec4(vec3(0.8), 1.0); 36 | vec4 WHITE = vec4(vec3(1.0), 1.0); 37 | 38 | vec3 fresnelSchlick(float cosTheta, vec3 F0) 39 | { 40 | return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); 41 | } 42 | 43 | void main() 44 | { 45 | float specularStrength = 0.5; 46 | float specularIntensity = 0.5; 47 | vec4 radianceSamp = mix(GRAY80, texture(texRadiance, matcapTexCoord), useReflections); 48 | vec4 checkerSamp = mix(WHITE, texture(texUv, fragTexCoord), useCheckerboard); 49 | vec4 reflectionSamp = texture(texReflection, matcapTexCoord); 50 | vec3 norm = normalize(fragNormal); 51 | 52 | vec3 lightDir = normalize(lightPos);// light very far away, use direction only. If light has position: normalize(lightPos - fragPos) 53 | float diffuse = clamp(dot(norm, lightDir), -1, 1) * 0.3; 54 | float ambient = 0.7; 55 | 56 | //phong r dot v ^ strength 57 | // r - light off normal 58 | 59 | vec3 appliedColor = fragVertColor; 60 | 61 | appliedColor = COLOR_LOOKUP[fragObjectId % NUM_COLOR_LOOKUPS]; 62 | 63 | vec3 specR = reflect(lightDir, norm); 64 | float nDotR = dot(specR, normalize(fragPos)); 65 | vec3 coloredSpec = clamp(pow(nDotR, pow(specularStrength + 1, 8)) * lightColor * specularIntensity, 0, 1); 66 | vec4 beforeMatCap = mix(checkerSamp, vec4(appliedColor, 1.0), 0.5) * vec4(vec3(clamp(ambient + diffuse, 0, 1)), 1); 67 | vec4 beforeReflection = mix(beforeMatCap, radianceSamp * beforeMatCap, 0.75); 68 | 69 | color = mix(beforeReflection, beforeReflection * reflectionSamp, 0.3 * useReflections); 70 | //color = samp; 71 | 72 | } 73 | -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarTable.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | using RainbowScimitar.Extensions; 4 | 5 | namespace RainbowScimitar.Scimitar 6 | { 7 | public record ScimitarTable(int NumFiles, int NumDirs, long PosFat, long NextPosFat, int FirstIndex, int LastIndex, long MetaTableOffset, long DirectoryOffset, ScimitarFileTableEntry[] Files, 8 | ScimitarAssetMetadata[] MetaTableEntries) 9 | { 10 | public static ScimitarTable Read(BinaryReader r) 11 | { 12 | var numFiles = r.ReadInt32(); 13 | var numDirs = r.ReadInt32(); 14 | var posFat = r.ReadInt64(); 15 | var nextPosFat = r.ReadInt64(); 16 | var firstIndex = r.ReadInt32(); 17 | var lastIndex = r.ReadInt32(); 18 | var metaTableOffset = r.ReadInt64(); 19 | var directoryOffset = r.ReadInt64(); 20 | 21 | r.BaseStream.Seek(posFat, SeekOrigin.Begin); 22 | var files = r.ReadStructs(numFiles); 23 | 24 | r.BaseStream.Seek(metaTableOffset, SeekOrigin.Begin); 25 | var metaTableEntries = r.ReadStructs(numFiles); 26 | 27 | r.BaseStream.Seek(directoryOffset, SeekOrigin.Begin); 28 | var directories = r.ReadStructs(numDirs); 29 | 30 | return new ScimitarTable(numFiles, numDirs, posFat, nextPosFat, firstIndex, lastIndex, metaTableOffset, directoryOffset, files, metaTableEntries); 31 | } 32 | 33 | public void Write(BinaryWriter w) 34 | { 35 | w.Write(NumFiles); 36 | w.Write(NumDirs); 37 | w.Write(PosFat); 38 | w.Write(NextPosFat); 39 | w.Write(FirstIndex); 40 | w.Write(LastIndex); 41 | w.Write(MetaTableOffset); 42 | w.Write(DirectoryOffset); 43 | 44 | w.BaseStream.Seek(PosFat, SeekOrigin.Begin); 45 | w.WriteStructs(Files); 46 | 47 | w.BaseStream.Seek(MetaTableOffset, SeekOrigin.Begin); 48 | w.WriteStructs(MetaTableEntries); 49 | } 50 | } 51 | 52 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 53 | internal struct ScimitarDirectory 54 | { 55 | public readonly int NumFiles; 56 | public readonly int Unknown1; 57 | public readonly int Unknown2; 58 | public readonly int Unknown3; 59 | public readonly int Unknown4; 60 | 61 | [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] 62 | public readonly byte[] Name; 63 | 64 | public readonly int Unknown5; 65 | public readonly int Unknown6; 66 | public readonly int Unknown7; 67 | public readonly int Unknown8; 68 | public readonly int Unknown9; 69 | public readonly int Unknown10; 70 | public readonly int Unknown11; 71 | } 72 | } -------------------------------------------------------------------------------- /DumpTool/IndexCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using CommandLine; 5 | using LiteDB; 6 | using RainbowForge; 7 | using RainbowForge.Core; 8 | using RainbowForge.Database; 9 | 10 | namespace DumpTool 11 | { 12 | [Verb("index", HelpText = "Create a search index of all of the forge files in a given directory. Required for some commands.")] 13 | public class IndexCommand 14 | { 15 | private static readonly char[] Characters = Enumerable.Range(0, 26).Select(i => (char) ('a' + i)).ToArray(); 16 | 17 | [Value(0, HelpText = "The directory of forge files to search")] 18 | public string SearchDirectory { get; set; } 19 | 20 | [Value(1, HelpText = "The output index file")] 21 | public string IndexFilename { get; set; } 22 | 23 | private static string GetFilename(int i) 24 | { 25 | var len = Characters.Length; 26 | 27 | var s = ""; 28 | do 29 | { 30 | s = Characters[i % len] + s; 31 | i /= len; 32 | } while (i > 0); 33 | 34 | return s; 35 | } 36 | 37 | public static void Run(IndexCommand args) 38 | { 39 | FileSystemUtil.AssertDirectoryExists(args.SearchDirectory); 40 | 41 | using var db = new LiteDatabase(args.IndexFilename); 42 | 43 | var nameCollection = db.GetCollection("filenames"); 44 | nameCollection.EnsureIndex(document => document.CollectionName); 45 | nameCollection.EnsureIndex(document => document.Filename); 46 | 47 | var files = Directory.GetFiles(args.SearchDirectory, "*.forge"); 48 | for (var fileIdx = 0; fileIdx < files.Length; fileIdx++) 49 | { 50 | var forgeFile = files[fileIdx]; 51 | 52 | using var forgeStream = new BinaryReader(File.Open(forgeFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); 53 | var forge = Forge.Read(forgeStream); 54 | 55 | var forgeFileName = Path.GetFileNameWithoutExtension(forgeFile); 56 | var forgeFileIdent = GetFilename(fileIdx); 57 | 58 | Console.WriteLine($"{forgeFileName} => {forgeFileIdent}"); 59 | 60 | nameCollection.Insert(new FilenameDocument(forgeFileName, forgeFileIdent)); 61 | 62 | var collection = db.GetCollection(forgeFileIdent, BsonAutoId.Int64); 63 | 64 | collection.EnsureIndex(document => document.Uid); 65 | collection.EnsureIndex(document => document.FileType); 66 | 67 | for (var entryIdx = 0; entryIdx < forge.NumEntries; entryIdx++) 68 | { 69 | var entry = forge.Entries[entryIdx]; 70 | 71 | collection.Insert(EntryDocument.For(entry)); 72 | } 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/ShaderUniform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace RainbowForge.RenderPipeline 6 | { 7 | public class ShaderUniform 8 | { 9 | public ulong InternalUid { get; } 10 | public string Name { get; } 11 | public UniformType UniformType { get; } 12 | public uint Var1 { get; } 13 | public uint Var2 { get; } 14 | public byte[] ExtraData { get; } 15 | 16 | private ShaderUniform(ulong internalUid, string name, UniformType uniformType, uint var1, uint var2, byte[] extraData) 17 | { 18 | InternalUid = internalUid; 19 | Name = name; 20 | UniformType = uniformType; 21 | Var1 = var1; 22 | Var2 = var2; 23 | ExtraData = extraData; 24 | } 25 | 26 | public static ShaderUniform Read(BinaryReader r) 27 | { 28 | var extraByte = r.ReadByte(); // ??? 29 | 30 | var internalUid = r.ReadUInt64(); 31 | 32 | var uniformType = (UniformType)r.ReadUInt32(); 33 | 34 | var nameLength = r.ReadInt32(); 35 | var name = Encoding.UTF8.GetString(r.ReadBytes(nameLength)); 36 | r.ReadByte(); // null terminator 37 | 38 | r.ReadBytes(16); // padding 39 | 40 | var var1 = r.ReadUInt32(); 41 | var var2 = r.ReadUInt32(); 42 | 43 | if (!Enum.IsDefined(typeof(UniformType), uniformType)) 44 | throw new NotSupportedException(); 45 | 46 | var extraDataLength = uniformType switch 47 | { 48 | UniformType.ShaderCodeVariableFloat => 8, 49 | UniformType.ShaderCodeVariableTexture => 12, 50 | UniformType.ShaderCodeVariableColor => 20, 51 | _ => throw new NotSupportedException($"Unsupported shader uniform type: {uniformType}") 52 | }; 53 | 54 | var extraData = r.ReadBytes(extraDataLength); 55 | 56 | // uni. type | uniform data 57 | // 5C 0E EC BB | 4E D9 DE 2F 00 00 00 3F 00 00 00 3F 00 00 00 3F 00 00 00 00 (MaskRed_Color) 58 | // 90 6E 6B 84 | C2 3E 8D 7D A7 47 CC 19 01 00 00 00 (MaskRed_Camo_Texture) 59 | // 20 18 1F 14 | FB 89 AA 1A 00 00 80 40 (MaskRed_Camo_UV_Factor) 60 | // 20 18 1F 14 | FD BE DE 9D 00 00 80 40 (MaskRed_Camo_UV_Factor_V) 61 | // 20 18 1F 14 | 04 E0 7C F0 00 00 00 00 (MaskRed_Camo_UV_Rot) 62 | // 20 18 1F 14 | D9 40 5A 45 00 00 00 00 (MaskRed_Camo_Gloss) 63 | // 20 18 1F 14 | 45 26 6D 97 00 00 00 00 (MaskRed_Camo_Metal) 64 | // 90 6E 6B 84 | 49 C1 FD 66 D3 73 39 40 15 00 00 00 (MaskRed_Detail) 65 | // 20 18 1F 14 | 25 D9 C9 CF 00 00 00 41 (MaskRed_Detail_UV_Factor) 66 | 67 | return new ShaderUniform(internalUid, name, uniformType, var1, var2, extraData); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /RainbowForge/Link/UidLinkContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RainbowForge.Link 5 | { 6 | public class UidLinkContainer 7 | { 8 | public Magic ContainerMagic { get; } 9 | public ulong InternalUid { get; } 10 | public byte[] Data1 { get; } 11 | public UidLinkDataEntry[] DataEntries { get; } 12 | public UidLinkDataEntry[] DataEntries2 { get; } 13 | public UidLinkEntry[] UidLinkEntries { get; } 14 | 15 | private UidLinkContainer(Magic magic, ulong internalUid, byte[] data1, UidLinkDataEntry[] dataEntries, UidLinkDataEntry[] dataEntries2, UidLinkEntry[] uidLinkEntries) 16 | { 17 | ContainerMagic = magic; 18 | InternalUid = internalUid; 19 | Data1 = data1; 20 | DataEntries = dataEntries; 21 | DataEntries2 = dataEntries2; 22 | UidLinkEntries = uidLinkEntries; 23 | } 24 | 25 | public static UidLinkContainer Read(BinaryReader r, uint containerType) 26 | { 27 | // TODO: flags? 28 | var hasDoubles = containerType == 0x156 || 29 | containerType == 0x35E || 30 | containerType == 0x462 || 31 | containerType == 0x728 || 32 | containerType == 0x76E || 33 | containerType == 0xA7A; 34 | 35 | var hasLargeEntries = containerType == 1236; 36 | 37 | var magic = (Magic)r.ReadUInt32(); 38 | 39 | var internalUid = r.ReadUInt64(); 40 | var magic2 = (Magic)r.ReadUInt32(); 41 | 42 | var headerBytes = magic2 switch 43 | { 44 | Magic.SpaceManager => 0, 45 | Magic.RowSelector => 8, 46 | Magic.LocalizedString => 9, 47 | _ => throw new NotSupportedException($"Unknown header magic {magic2} (0x{(uint)magic2:X8})") 48 | }; 49 | 50 | var data1 = r.ReadBytes(headerBytes); 51 | 52 | var numDataEntries = r.ReadUInt32(); 53 | 54 | var dataEntries = new UidLinkDataEntry[numDataEntries]; 55 | for (var i = 0; i < dataEntries.Length; i++) 56 | dataEntries[i] = UidLinkDataEntry.Read(r, hasDoubles, hasLargeEntries); 57 | 58 | var numDataEntries2 = r.ReadUInt32(); 59 | 60 | var dataEntries2 = new UidLinkDataEntry[numDataEntries2]; 61 | for (var i = 0; i < dataEntries2.Length; i++) 62 | dataEntries2[i] = UidLinkDataEntry.Read(r, hasDoubles, hasLargeEntries); 63 | 64 | var numUidLinkEntries = r.ReadUInt32(); 65 | var uidLinkEntries = new UidLinkEntry[numUidLinkEntries]; 66 | for (var i = 0; i < uidLinkEntries.Length; i++) 67 | uidLinkEntries[i] = UidLinkEntry.Read(r, hasDoubles); 68 | 69 | var padding = r.ReadBytes(2); // padding? 70 | 71 | return new UidLinkContainer(magic, internalUid, data1, dataEntries, dataEntries2, uidLinkEntries); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/IScimitarFileData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using RainbowScimitar.Extensions; 5 | 6 | namespace RainbowScimitar.Scimitar 7 | { 8 | public interface IScimitarFileData 9 | { 10 | private static readonly HashSet KnownMagics = new() 11 | { 12 | 0x1014FA9957FBAA34, 0x1015FA9957FBAA36 13 | }; 14 | 15 | public Stream GetStream(Stream bundleStream); 16 | 17 | public static IScimitarFileData Read(Stream bundleStream) 18 | { 19 | var r = new BinaryReader(bundleStream); 20 | 21 | var magic = r.ReadUInt64(); 22 | 23 | if (magic - 0x1015FA9957FBAA35u >= 2 && magic != 0x1004FA9957FBAA33 && magic != 0x1014FA9957FBAA34) 24 | { 25 | // Not sure how this check works in practice but it's the one the game uses 26 | throw new InvalidDataException($"Expected file magic, got 0x{magic:X16}"); 27 | } 28 | 29 | // if ((magic & 0xFF00FFFFFFFFFF00) != 0x1000FA9957FBAA00) 30 | // throw new InvalidDataException($"Expected file magic, got 0x{magic:X16}"); 31 | 32 | var header = r.ReadStruct(); 33 | 34 | switch (header.PackMethod) 35 | { 36 | case ScimitarFilePackMethod.BlockZstd: 37 | return ScimitarBlockPackedData.Read(bundleStream, CompressionMethod.Zstd); 38 | case ScimitarFilePackMethod.BlockOodle: 39 | return ScimitarBlockPackedData.Read(bundleStream, CompressionMethod.Oodle); 40 | case ScimitarFilePackMethod.Streaming: 41 | return ScimitarStreamingPackedData.Read(bundleStream); 42 | default: 43 | { 44 | throw new ArgumentOutOfRangeException(nameof(header.PackMethod), $"Unknown pack method 0x{(uint)header.PackMethod:X}"); 45 | } 46 | } 47 | } 48 | 49 | public static void Write(Stream dataStream, Stream bundleStream, ScimitarFilePackMethod packMethod) 50 | { 51 | var w = new BinaryWriter(bundleStream); 52 | 53 | w.Write(0x1014FA9957FBAA34uL); 54 | w.WriteStruct(new ScimitarFileHeader(packMethod)); 55 | 56 | switch (packMethod) 57 | { 58 | case ScimitarFilePackMethod.BlockZstd: 59 | ScimitarBlockPackedData.Write(dataStream, bundleStream, CompressionMethod.Zstd); 60 | break; 61 | case ScimitarFilePackMethod.BlockOodle: 62 | ScimitarBlockPackedData.Write(dataStream, bundleStream, CompressionMethod.Oodle); 63 | break; 64 | case ScimitarFilePackMethod.Streaming: 65 | ScimitarStreamingPackedData.Write(dataStream, bundleStream); 66 | break; 67 | default: 68 | throw new ArgumentOutOfRangeException(nameof(packMethod), packMethod, null); 69 | } 70 | } 71 | } 72 | 73 | public enum CompressionMethod 74 | { 75 | Zstd, 76 | Oodle 77 | } 78 | } -------------------------------------------------------------------------------- /Prism/PrismSettings.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.IO; 3 | using Newtonsoft.Json; 4 | 5 | namespace Prism 6 | { 7 | public class PrismSettings 8 | { 9 | [ 10 | Category("3D Viewport"), 11 | DisplayName("Use reflections"), 12 | Description("Whether or not skybox reflections are enabled in the 3D viewport."), 13 | DefaultValue(false) 14 | ] 15 | public bool Use3DReflections { get; set; } = false; 16 | 17 | [ 18 | Category("3D Viewport"), 19 | DisplayName("Use checkerboard texture"), 20 | Description("Whether or not models in the 3D viewport render using the checkerboard debug texture."), 21 | DefaultValue(false) 22 | ] 23 | public bool Use3DCheckerboard { get; set; } = false; 24 | 25 | [ 26 | Category("Export"), 27 | DisplayName("Quick Export Directory"), 28 | Description("The directory to which \"Quick Export\" actions will output files."), 29 | DefaultValue("Quick Exports") 30 | ] 31 | public string QuickExportLocation { get; set; } = "Quick Exports"; 32 | 33 | [ 34 | Category("Model Export"), 35 | DisplayName("Export All Model LODs"), 36 | Description("If the exported models should include all levels of detail present in the game files instead of just the highest."), 37 | DefaultValue(false) 38 | ] 39 | public bool ExportAllModelLods { get; set; } = false; 40 | 41 | [ 42 | Category("PNG Export"), 43 | DisplayName("Horizontally flip entire image"), 44 | Description("Make exported PNGs vertically flipped, which transforms texture maps from DirectX texture coordinate space to the texture coordinate space of exported models."), 45 | DefaultValue(true) 46 | ] 47 | public bool FlipPngSpace { get; set; } = true; 48 | 49 | [ 50 | Category("PNG Export"), 51 | DisplayName("Flip normal map green (Y) channel"), 52 | Description("Make exported PNGs have the green (Y) channel flipped, which transforms normal maps from DirectX normal space to OpenGL normal space."), 53 | DefaultValue(true) 54 | ] 55 | public bool FlipPngGreenChannel { get; set; } = true; 56 | 57 | [ 58 | Category("PNG Export"), 59 | DisplayName("Recalculate normal maps blue (Z) channel"), 60 | Description("Make exported PNGs have their blue (Z) channel recalculated."), 61 | DefaultValue(true) 62 | ] 63 | public bool RecalculatePngBlueChannel { get; set; } = true; 64 | 65 | public void Save(string filename) 66 | { 67 | File.WriteAllText(filename, JsonConvert.SerializeObject(this)); 68 | } 69 | 70 | public static PrismSettings Load(string filename) 71 | { 72 | return !File.Exists(filename) ? new PrismSettings() : JsonConvert.DeserializeObject(File.ReadAllText(filename)); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /RainbowForge/RenderPipeline/Mesh.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge.Model; 3 | using RainbowForge.Structs; 4 | 5 | namespace RainbowForge.RenderPipeline 6 | { 7 | public class Mesh 8 | { 9 | public uint Var1 { get; } 10 | public ulong InternalUid { get; } 11 | public byte[] Data { get; } 12 | public MeshBone[] Bones { get; } 13 | public uint Magic2 { get; } 14 | public uint Var2 { get; } 15 | public ulong CompiledMeshObjectUid { get; } 16 | public ulong[] Materials { get; } 17 | public ulong InternalUid2 { get; } 18 | public byte[] Data2 { get; } 19 | 20 | private Mesh(uint var1, ulong internalUid, byte[] data, MeshBone[] bones, uint magic2, uint var2, ulong compiledMeshObjectUid, ulong[] materials, ulong internalUid2, byte[] data2) 21 | { 22 | Var1 = var1; 23 | InternalUid = internalUid; 24 | Data = data; 25 | Bones = bones; 26 | Magic2 = magic2; 27 | Var2 = var2; 28 | CompiledMeshObjectUid = compiledMeshObjectUid; 29 | Materials = materials; 30 | InternalUid2 = internalUid2; 31 | Data2 = data2; 32 | } 33 | 34 | public static Mesh Read(BinaryReader r) 35 | { 36 | var magic = r.ReadUInt32(); 37 | MagicHelper.AssertEquals(Magic.Mesh, magic); 38 | 39 | var var1 = r.ReadUInt32(); 40 | 41 | var internalUid = r.ReadUInt64(); 42 | 43 | var data = r.ReadBytes(32); 44 | 45 | var numBones = r.ReadUInt32(); 46 | var bones = new MeshBone[numBones]; 47 | for (var i = 0; i < bones.Length; i++) 48 | bones[i] = MeshBone.Read(r); 49 | 50 | var magic2 = r.ReadUInt32(); 51 | var var2 = r.ReadUInt32(); 52 | 53 | var meshUid = r.ReadUInt64(); 54 | 55 | var numMaterialContainers = r.ReadUInt32(); 56 | var materialContainers = new ulong[numMaterialContainers]; 57 | for (var i = 0; i < materialContainers.Length; i++) 58 | materialContainers[i] = r.ReadUInt64(); 59 | 60 | var internalUid2 = r.ReadUInt64(); 61 | var data2 = r.ReadBytes(25); 62 | 63 | return new Mesh(var1, internalUid, data, bones, magic2, var2, meshUid, materialContainers, internalUid2, data2); 64 | } 65 | } 66 | 67 | public class MeshBone 68 | { 69 | public Matrix4F Transformation { get; } 70 | public BoneId Id { get; } 71 | 72 | private MeshBone(Matrix4F transformation, BoneId id) 73 | { 74 | Transformation = transformation; 75 | Id = id; 76 | } 77 | 78 | public static MeshBone Read(BinaryReader r) 79 | { 80 | var padding = r.ReadBytes(8); // padding? 81 | var magic = r.ReadUInt32(); 82 | MagicHelper.AssertEquals(Magic.MeshBone, magic); 83 | 84 | var transformation = r.ReadStruct(Matrix4F.SizeInBytes); 85 | var id = (BoneId)r.ReadUInt32(); 86 | 87 | return new MeshBone(transformation, id); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /RainbowScimitar/DataTypes/World.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge; 3 | using RainbowScimitar.Extensions; 4 | using RainbowScimitar.Scimitar; 5 | 6 | namespace RainbowScimitar.DataTypes 7 | { 8 | public record World(SpaceManager SpaceManager, SoundState SoundState, ScimitarId WorldDivisionContainerUid, WorldLoader WorldLoader, ScimitarId DefaultScenarioUid, WorldGraphicData GraphicData, 9 | BoundingVolume BoundingVolume, GIBoundingVolume GiBoundingVolume, GIBoundingVolume GiBoundingVolume2, IColor Color, WindDefinition WindDefinition, ScimitarId[] Unknown1, 10 | ScimitarId SoundPropogationMapUid, float[] Unknown2) 11 | { 12 | public static World Read(BinaryReader r) 13 | { 14 | r.ReadMagic(Magic.World); 15 | 16 | var spaceManagerUid = r.ReadUid(); 17 | var spaceManager = SpaceManager.Read(r); 18 | 19 | var soundStateUid = r.ReadUid(); 20 | var soundState = SoundState.Read(r); 21 | 22 | var worldDivisionContainerUid = r.ReadUid(); 23 | var zero = r.ReadByte(); 24 | 25 | var worldLoaderUid = r.ReadUid(); 26 | var worldLoader = WorldLoader.Read(r); 27 | 28 | var defaultScenarioUid = r.ReadUid(); 29 | var zero2 = r.ReadByte(); 30 | 31 | var graphicDataUid = r.ReadUid(); 32 | var graphicData = WorldGraphicData.Read(r); 33 | 34 | var boundingVolumeUid = r.ReadUid(); 35 | var boundingVolume = BoundingVolume.Read(r); 36 | 37 | // TODO: only seems to be 0 or 3, 0 on all maps except Tower, Italy, Morocco, HerefordRework, Austrailia, and ThemePark_V2 38 | var giBoundingVolumeFlags = r.ReadByte(); 39 | ScimitarId giBoundingVolumeUid; 40 | GIBoundingVolume giBoundingVolume = null; 41 | if (giBoundingVolumeFlags == 0) 42 | { 43 | giBoundingVolumeUid = r.ReadUid(); 44 | giBoundingVolume = GIBoundingVolume.Read(r); 45 | } 46 | 47 | var giBoundingVolumeFlags2 = r.ReadByte(); 48 | ScimitarId giBoundingVolume2Uid; 49 | GIBoundingVolume giBoundingVolume2 = null; 50 | if (giBoundingVolumeFlags2 == 0) 51 | { 52 | giBoundingVolume2Uid = r.ReadUid(); 53 | giBoundingVolume2 = GIBoundingVolume.Read(r); 54 | } 55 | 56 | var iColorUid = r.ReadUid(); 57 | var iColor = IColor.Read(r); 58 | 59 | var windDefinitionUid = r.ReadUid(); 60 | var windDefinition = WindDefinition.Read(r); 61 | 62 | var extraUids = r.ReadLengthPrefixedStructs(); 63 | 64 | var soundPropogationMapUid = r.ReadUid(); 65 | 66 | var unk2 = r.ReadByte(); 67 | 68 | var floats = r.ReadStructs(23); 69 | 70 | return new World(spaceManager, soundState, worldDivisionContainerUid, worldLoader, defaultScenarioUid, graphicData, boundingVolume, giBoundingVolume, giBoundingVolume2, iColor, 71 | windDefinition, extraUids, soundPropogationMapUid, floats); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /DumpTool/InspectCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CommandLine; 5 | using RainbowForge; 6 | using RainbowForge.Core; 7 | using RainbowForge.Core.Container; 8 | 9 | namespace DumpTool 10 | { 11 | [Verb("inspect", HelpText = "Get details on the given asset")] 12 | public class InspectCommand 13 | { 14 | [Value(0, HelpText = "The forge file to reference")] 15 | public string ForgeFilename { get; set; } 16 | 17 | [Value(1, HelpText = "The UID to inspect")] 18 | public ulong Uid { get; set; } 19 | 20 | public static void Run(InspectCommand args) 21 | { 22 | var forge = Forge.GetForge(args.ForgeFilename); 23 | 24 | try 25 | { 26 | var entry = forge.GetContainer(args.Uid); 27 | var metaEntry = forge.Entries.First(entry1 => entry1.Uid == args.Uid); 28 | var magic = MagicHelper.GetFiletype(metaEntry.MetaData.FileType); 29 | 30 | Console.WriteLine($"UID: {metaEntry.Uid}"); 31 | Console.WriteLine($"Offset: 0x{metaEntry.Offset:X}"); 32 | Console.WriteLine($"End: 0x{metaEntry.End:X}"); 33 | Console.WriteLine($"Size: 0x{metaEntry.Size:X}"); 34 | Console.WriteLine("Name Table:"); 35 | 36 | Console.WriteLine($"\tFile Magic: {magic}"); 37 | var date = DateTimeOffset.FromUnixTimeSeconds(metaEntry.MetaData.Timestamp).DateTime; 38 | Console.WriteLine($"\tTimestamp: {date} (epoch: {metaEntry.MetaData.Timestamp})"); 39 | 40 | switch (entry) 41 | { 42 | case Descriptor d: 43 | { 44 | Console.WriteLine("Container: Descriptor"); 45 | 46 | Console.WriteLine("String data:"); 47 | foreach (var (k, v) in d.StringData) 48 | Console.WriteLine($"\"{k}\" = \"{v}\""); 49 | 50 | Console.WriteLine("Uint data:"); 51 | foreach (var (k, v) in d.UIntData) 52 | Console.WriteLine($"\"{k}\" = {v}"); 53 | 54 | Console.WriteLine("Ulong data:"); 55 | foreach (var (k, v) in d.ULongData) 56 | Console.WriteLine($"\"{k}\" = {v}"); 57 | 58 | break; 59 | } 60 | case Hash h: 61 | { 62 | Console.WriteLine("Container: Hash"); 63 | Console.WriteLine($"Name: \"{h.Name}\""); 64 | Console.WriteLine($"Hash 1: \"{h.Hash1}\""); 65 | Console.WriteLine($"Hash 2: \"{h.Hash2}\""); 66 | break; 67 | } 68 | case ForgeAsset fa: 69 | { 70 | Console.WriteLine("Container: Forge Asset"); 71 | Console.WriteLine($"Has Metadata Block: {fa.HasMeta}"); 72 | break; 73 | } 74 | default: 75 | { 76 | Console.WriteLine("Unknown Container"); 77 | break; 78 | } 79 | } 80 | } 81 | catch (KeyNotFoundException) 82 | { 83 | Console.Error.WriteLine($"No asset with UID {args.Uid} present in \"{args.ForgeFilename}\""); 84 | } 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /AssetCatalog/App.xaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 29 | 30 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /RainbowForge/Model/Skeleton.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using RainbowForge.Structs; 3 | 4 | namespace RainbowForge.Model 5 | { 6 | public class Skeleton 7 | { 8 | public static Skeleton Read(BinaryReader r) 9 | { 10 | var magic = r.ReadUInt32(); 11 | MagicHelper.AssertEquals(Magic.Skeleton, magic); 12 | 13 | var skeletonMirrorData = SkeletonMirrorData.Read(r); 14 | 15 | var numBones = r.ReadInt32(); 16 | 17 | var uid = r.ReadUInt64(); 18 | 19 | var unk1 = r.ReadByte(); 20 | 21 | var bones = new Bone[numBones]; 22 | for (var i = 0; i < numBones; i++) 23 | { 24 | bones[i] = Bone.Read(r); 25 | } 26 | 27 | return null; 28 | } 29 | } 30 | 31 | public class Bone 32 | { 33 | public static Bone Read(BinaryReader r) 34 | { 35 | var magic = r.ReadUInt32(); 36 | MagicHelper.AssertEquals(Magic.Bone, magic); 37 | 38 | var unk2 = r.ReadInt32(); 39 | var unk3 = r.ReadInt16(); 40 | 41 | var internalUid = r.ReadUInt64(); 42 | 43 | var initialTransform = BoneInitialTransforms.Read(r); 44 | 45 | var numModifiers = r.ReadInt32(); 46 | 47 | var unk4 = r.ReadByte(); 48 | 49 | for (var i = 0; i < numModifiers; i++) 50 | { 51 | var mod = BoneModifier.Read(r); 52 | } 53 | 54 | var unk5 = r.ReadBytes(3); 55 | 56 | var unk6 = r.ReadUInt32(); 57 | var unk7 = r.ReadUInt32(); 58 | 59 | var boneId = (BoneId)r.ReadUInt32(); 60 | 61 | var unk8 = r.ReadBytes(19); 62 | 63 | return null; 64 | } 65 | } 66 | 67 | public class BoneModifier 68 | { 69 | public static BoneModifier Read(BinaryReader r) 70 | { 71 | var magic = (Magic)r.ReadUInt32(); 72 | 73 | return magic switch 74 | { 75 | Magic.BallJointBoneModifier => BallJointBoneModifier.ReadData(r), 76 | _ => null 77 | }; 78 | } 79 | } 80 | 81 | public class BallJointBoneModifier : BoneModifier 82 | { 83 | public ulong Uid { get; } 84 | public byte[] Unk { get; } 85 | public ulong Uid2 { get; } 86 | public byte[] Unk2 { get; } 87 | public Matrix4F Matrix { get; } 88 | public byte[] Unk3 { get; } 89 | 90 | private BallJointBoneModifier(ulong uid, byte[] unk, ulong uid2, byte[] unk2, Matrix4F matrix, byte[] unk3) 91 | { 92 | Uid = uid; 93 | Unk = unk; 94 | Uid2 = uid2; 95 | Unk2 = unk2; 96 | Matrix = matrix; 97 | Unk3 = unk3; 98 | } 99 | 100 | public static BallJointBoneModifier ReadData(BinaryReader r) 101 | { 102 | var uid = r.ReadUInt64(); 103 | 104 | var unk = r.ReadBytes(10); 105 | 106 | var uid2 = r.ReadUInt64(); 107 | 108 | var unk2 = r.ReadBytes(2); 109 | 110 | var matrix = r.ReadStruct(Matrix4F.SizeInBytes); 111 | 112 | var unk3 = r.ReadBytes(13); 113 | 114 | return new BallJointBoneModifier(uid, unk, uid2, unk2, matrix, unk3); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /RainbowForge/Core/DataBlock/ChunkedDataBlock.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using RainbowForge.Compression; 4 | using Zstandard.Net; 5 | 6 | namespace RainbowForge.Core.DataBlock 7 | { 8 | public class ChunkedDataBlock : IAssetBlock 9 | { 10 | public ChunkedData[] Chunks { get; } 11 | public bool IsPacked { get; } 12 | public uint PackedLength { get; } 13 | public uint UnpackedLength { get; } 14 | public bool UseOodle { get; } 15 | 16 | public ChunkedDataBlock(ChunkedData[] chunks, bool isPacked, uint packedLength, uint unpackedLength, bool useOodle) 17 | { 18 | Chunks = chunks; 19 | IsPacked = isPacked; 20 | PackedLength = packedLength; 21 | UnpackedLength = unpackedLength; 22 | UseOodle = useOodle; 23 | } 24 | 25 | public Stream GetDataStream(BinaryReader r) 26 | { 27 | var ms = new MemoryStream(); 28 | 29 | foreach (var chunk in Chunks) 30 | { 31 | r.BaseStream.Seek(chunk.Offset, SeekOrigin.Begin); 32 | 33 | if (chunk.IsCompressed) 34 | { 35 | if (UseOodle) 36 | { 37 | OodleHelper.EnsureOodleLoaded(); 38 | 39 | // Contents are compressed 40 | var compressed = r.ReadBytes((int)chunk.SerializedLength); 41 | var decompressed = Oodle2Core8.Decompress(compressed, (int)chunk.DataLength); 42 | ms.Write(decompressed, 0, decompressed.Length); 43 | } 44 | else 45 | { 46 | using var dctx = new ZstandardStream(r.BaseStream, CompressionMode.Decompress, true); 47 | // TODO: make sure this reads exactly {chunk.SerializedLength} bytes -- it should, 48 | // but reading {chunk.DataLength} bytes from a decompression stream is 49 | // a weird way to do it 50 | dctx.CopyStream(ms, (int)chunk.DataLength); 51 | } 52 | } 53 | else 54 | { 55 | r.BaseStream.CopyStream(ms, (int)chunk.SerializedLength); 56 | } 57 | } 58 | 59 | ms.Seek(0, SeekOrigin.Begin); 60 | return ms; 61 | } 62 | 63 | public static ChunkedDataBlock Read(BinaryReader r, bool useOodle = false) 64 | { 65 | // numChunks used to be an int32, but it caused read errors 66 | // TODO: is it actually an int16? 67 | var numChunks = r.ReadUInt16(); 68 | var u2 = r.ReadUInt16(); 69 | 70 | var serializedLength = 0u; 71 | var dataLength = 0u; 72 | var isCompressed = false; 73 | 74 | var chunks = new ChunkedData[numChunks]; 75 | 76 | for (var i = 0; i < numChunks; i++) 77 | { 78 | var chunk = ChunkedData.Read(r); 79 | 80 | serializedLength += chunk.SerializedLength; 81 | dataLength += chunk.DataLength; 82 | 83 | isCompressed |= chunk.IsCompressed; 84 | 85 | chunks[i] = chunk; 86 | } 87 | 88 | foreach (var chunk in chunks) 89 | { 90 | chunk.Finalize(r); 91 | r.BaseStream.Seek(chunk.SerializedLength, SeekOrigin.Current); 92 | } 93 | 94 | return new ChunkedDataBlock(chunks, isCompressed, serializedLength, dataLength, useOodle); 95 | } 96 | } 97 | } -------------------------------------------------------------------------------- /Prism/Prism.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0-windows 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | UserControl 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | PreserveNewest 45 | 46 | 47 | PreserveNewest 48 | 49 | 50 | PreserveNewest 51 | 52 | 53 | 54 | PreserveNewest 55 | 56 | 57 | PreserveNewest 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /Prism/Extensions/IImageExt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using System.Runtime.InteropServices; 5 | using Pfim; 6 | using SkiaSharp; 7 | using ImageFormat = Pfim.ImageFormat; 8 | 9 | namespace Prism.Extensions 10 | { 11 | public static class IImageExt 12 | { 13 | public static Bitmap CreateBitmap(this IImage image, PixelFormat pixelFormat = PixelFormat.Format32bppArgb) 14 | { 15 | var format = image.Format switch 16 | { 17 | ImageFormat.Rgb24 => PixelFormat.Format24bppRgb, 18 | ImageFormat.Rgba32 => PixelFormat.Format32bppArgb, 19 | ImageFormat.R5g5b5 => PixelFormat.Format16bppRgb555, 20 | ImageFormat.R5g6b5 => PixelFormat.Format16bppRgb565, 21 | ImageFormat.R5g5b5a1 => PixelFormat.Format16bppArgb1555, 22 | ImageFormat.Rgb8 => PixelFormat.Format8bppIndexed, 23 | _ => throw new NotImplementedException() 24 | }; 25 | 26 | var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(image.Data, 0); 27 | using var bitmap = new Bitmap(image.Width, image.Height, image.Stride, format, ptr); 28 | 29 | // Generate greyscale indexed palette data 30 | if (format == PixelFormat.Format8bppIndexed) 31 | { 32 | var palette = bitmap.Palette; 33 | for (var i = 0; i < 256; i++) 34 | palette.Entries[i] = Color.FromArgb((byte)i, (byte)i, (byte)i); 35 | bitmap.Palette = palette; 36 | } 37 | 38 | return bitmap.Clone(new Rectangle(Point.Empty, bitmap.Size), pixelFormat); 39 | } 40 | 41 | public static SKImage CreateSkImage(this IImage image) 42 | { 43 | var newData = image.Data; 44 | var newDataLen = image.DataLen; 45 | var stride = image.Stride; 46 | SKColorType colorType; 47 | switch (image.Format) 48 | { 49 | case ImageFormat.Rgb8: 50 | colorType = SKColorType.Gray8; 51 | break; 52 | case ImageFormat.R5g6b5: 53 | // color channels still need to be swapped 54 | colorType = SKColorType.Rgb565; 55 | break; 56 | case ImageFormat.Rgba16: 57 | // color channels still need to be swapped 58 | colorType = SKColorType.Argb4444; 59 | break; 60 | case ImageFormat.Rgb24: 61 | { 62 | // Skia has no 24bit pixels, so we upscale to 32bit 63 | var pixels = image.DataLen / 3; 64 | newDataLen = pixels * 4; 65 | newData = new byte[newDataLen]; 66 | for (var i = 0; i < pixels; i++) 67 | { 68 | newData[i * 4] = image.Data[i * 3]; 69 | newData[i * 4 + 1] = image.Data[i * 3 + 1]; 70 | newData[i * 4 + 2] = image.Data[i * 3 + 2]; 71 | newData[i * 4 + 3] = 255; 72 | } 73 | 74 | stride = image.Width * 4; 75 | colorType = SKColorType.Bgra8888; 76 | break; 77 | } 78 | case ImageFormat.Rgba32: 79 | colorType = SKColorType.Bgra8888; 80 | break; 81 | default: 82 | throw new ArgumentException($"Skia unable to interpret pfim format: {image.Format}"); 83 | } 84 | 85 | var imageInfo = new SKImageInfo(image.Width, image.Height, colorType, SKAlphaType.Unpremul); 86 | var handle = GCHandle.Alloc(newData, GCHandleType.Pinned); 87 | var ptr = Marshal.UnsafeAddrOfPinnedArrayElement(newData, 0); 88 | 89 | using var data = SKData.Create(ptr, newDataLen, (address, context) => handle.Free()); 90 | return SKImage.FromPixels(imageInfo, data, stride); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /Prism/Render/Framebuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Graphics.OpenGL; 3 | 4 | namespace Prism.Render 5 | { 6 | public class Framebuffer : IDisposable 7 | { 8 | private readonly int _unboundFbo; 9 | 10 | private int _samples; 11 | public int FboId { get; set; } 12 | public int Texture { get; set; } 13 | public int DepthId { get; set; } 14 | public int Width { get; private set; } 15 | public int Height { get; private set; } 16 | 17 | public int Samples 18 | { 19 | get => _samples; 20 | set 21 | { 22 | _samples = value; 23 | Init(Width, Height); 24 | } 25 | } 26 | 27 | public Framebuffer(int samples, int unboundFbo = 0) 28 | { 29 | _unboundFbo = unboundFbo; 30 | _samples = samples; 31 | FboId = GL.GenFramebuffer(); 32 | Texture = GL.GenTexture(); 33 | DepthId = GL.GenRenderbuffer(); 34 | } 35 | 36 | public void Init(int width, int height) 37 | { 38 | if (width == 0 || height == 0) 39 | return; 40 | 41 | Width = width; 42 | Height = height; 43 | 44 | Use(); 45 | 46 | if (_samples == 1) 47 | { 48 | GL.BindTexture(TextureTarget.Texture2D, Texture); 49 | 50 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); 51 | GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, 52 | TextureTarget.Texture2D, Texture, 0); 53 | } 54 | else 55 | { 56 | GL.BindTexture(TextureTarget.Texture2DMultisample, Texture); 57 | 58 | GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, _samples, PixelInternalFormat.Rgba, 59 | width, height, true); 60 | GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, 61 | TextureTarget.Texture2DMultisample, Texture, 0); 62 | } 63 | 64 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, DepthId); 65 | GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, _samples, 66 | RenderbufferStorage.Depth24Stencil8, width, height); 67 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); 68 | GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, 69 | RenderbufferTarget.Renderbuffer, DepthId); 70 | 71 | var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer); 72 | if (status != FramebufferErrorCode.FramebufferComplete) 73 | throw new ApplicationException( 74 | $"Framebuffer status expected to be FramebufferComplete, instead was {status}"); 75 | 76 | GL.BindTexture(TextureTarget.Texture2DMultisample, 0); 77 | Release(); 78 | } 79 | 80 | public void Use() 81 | { 82 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, FboId); 83 | } 84 | 85 | public void Release() 86 | { 87 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, _unboundFbo); 88 | } 89 | 90 | private void ReleaseUnmanagedResources() 91 | { 92 | GL.DeleteFramebuffer(FboId); 93 | GL.DeleteTexture(Texture); 94 | GL.DeleteRenderbuffer(DepthId); 95 | } 96 | 97 | private void Dispose(bool disposing) 98 | { 99 | ReleaseUnmanagedResources(); 100 | } 101 | 102 | public void Dispose() 103 | { 104 | Dispose(true); 105 | GC.SuppressFinalize(this); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Framebuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using OpenTK.Graphics.OpenGL; 3 | 4 | namespace AssetCatalog.Render 5 | { 6 | public class Framebuffer : IDisposable 7 | { 8 | private readonly int _unboundFbo; 9 | 10 | private int _samples; 11 | public int FboId { get; set; } 12 | public int Texture { get; set; } 13 | public int DepthId { get; set; } 14 | public int Width { get; private set; } 15 | public int Height { get; private set; } 16 | 17 | public int Samples 18 | { 19 | get => _samples; 20 | set 21 | { 22 | _samples = value; 23 | Init(Width, Height); 24 | } 25 | } 26 | 27 | public Framebuffer(int samples, int unboundFbo = 0) 28 | { 29 | _unboundFbo = unboundFbo; 30 | _samples = samples; 31 | FboId = GL.GenFramebuffer(); 32 | Texture = GL.GenTexture(); 33 | DepthId = GL.GenRenderbuffer(); 34 | } 35 | 36 | public void Init(int width, int height) 37 | { 38 | if (width == 0 || height == 0) 39 | return; 40 | 41 | Width = width; 42 | Height = height; 43 | 44 | Use(); 45 | 46 | if (_samples == 1) 47 | { 48 | GL.BindTexture(TextureTarget.Texture2D, Texture); 49 | 50 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero); 51 | GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, 52 | TextureTarget.Texture2D, Texture, 0); 53 | } 54 | else 55 | { 56 | GL.BindTexture(TextureTarget.Texture2DMultisample, Texture); 57 | 58 | GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, _samples, PixelInternalFormat.Rgba, 59 | width, height, true); 60 | GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, 61 | TextureTarget.Texture2DMultisample, Texture, 0); 62 | } 63 | 64 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, DepthId); 65 | GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, _samples, 66 | RenderbufferStorage.Depth24Stencil8, width, height); 67 | GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0); 68 | GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, 69 | RenderbufferTarget.Renderbuffer, DepthId); 70 | 71 | var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer); 72 | if (status != FramebufferErrorCode.FramebufferComplete) 73 | throw new ApplicationException( 74 | $"Framebuffer status expected to be FramebufferComplete, instead was {status}"); 75 | 76 | GL.BindTexture(TextureTarget.Texture2DMultisample, 0); 77 | Release(); 78 | } 79 | 80 | public void Use() 81 | { 82 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, FboId); 83 | } 84 | 85 | public void Release() 86 | { 87 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, _unboundFbo); 88 | } 89 | 90 | private void ReleaseUnmanagedResources() 91 | { 92 | GL.DeleteFramebuffer(FboId); 93 | GL.DeleteTexture(Texture); 94 | GL.DeleteRenderbuffer(DepthId); 95 | } 96 | 97 | private void Dispose(bool disposing) 98 | { 99 | ReleaseUnmanagedResources(); 100 | } 101 | 102 | public void Dispose() 103 | { 104 | Dispose(true); 105 | GC.SuppressFinalize(this); 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /RainbowScimitar/Scimitar/ScimitarStreamingPackedData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using RainbowForge; 4 | using RainbowScimitar.Extensions; 5 | using RainbowScimitar.Helper; 6 | 7 | namespace RainbowScimitar.Scimitar 8 | { 9 | public record ScimitarStreamingPackedData(ushort Unknown1, ScimitarChunkSizeInfo[] SizeInfo, uint[] ChecksumInfo, long[] Offsets) : IScimitarFileData 10 | { 11 | /// 12 | public Stream GetStream(Stream bundleStream) 13 | { 14 | var ms = StreamHelper.MemoryStreamManager.GetStream("ScimitarBlockPackedData.GetStream"); 15 | 16 | for (var i = 0; i < SizeInfo.Length; i++) 17 | { 18 | var size = SizeInfo[i]; 19 | 20 | bundleStream.Seek(Offsets[i], SeekOrigin.Begin); 21 | 22 | if (size.PayloadSize > size.SerializedSize) 23 | { 24 | // Is this supported in the wild? 25 | throw new NotSupportedException("Compressed streaming blocks are not supported!"); 26 | } 27 | else 28 | { 29 | // Contents are not compressed 30 | bundleStream.CopyStreamTo(ms, size.PayloadSize); 31 | } 32 | } 33 | 34 | ms.Position = 0; 35 | return ms; 36 | } 37 | 38 | public static void Write(Stream dataStream, Stream bundleStream) 39 | { 40 | var w = new BinaryWriter(bundleStream); 41 | 42 | // 256kb chunks 43 | const int chunkSize = 256 * 1024; 44 | var numChunks = dataStream.Length / chunkSize + 1; 45 | 46 | w.Write((short)numChunks); 47 | 48 | // w.Write(Unknown1); 49 | w.Write((short)0); 50 | 51 | using var msChunks = StreamHelper.MemoryStreamManager.GetStream("ScimitarStreamingPackedData.Write/Chunks"); 52 | 53 | using var msChecksums = StreamHelper.MemoryStreamManager.GetStream("ScimitarStreamingPackedData.Write/Checksums"); 54 | var wChecksums = new BinaryWriter(msChecksums); 55 | 56 | while (dataStream.Position < dataStream.Length) 57 | { 58 | using var msChunk = StreamHelper.MemoryStreamManager.GetStream("ScimitarStreamingPackedData.Write/TempChunk"); 59 | var payloadSize = dataStream.CopyStreamTo(msChunk, chunkSize); 60 | 61 | msChunk.Position = 0; 62 | wChecksums.Write(Crc32.Compute(msChunk, msChunk.Length)); 63 | 64 | var startPos = msChunks.Position; 65 | 66 | msChunk.Position = 0; 67 | msChunk.CopyTo(msChunks); 68 | 69 | var serializedSize = (int)(msChunks.Position - startPos); 70 | 71 | w.Write(payloadSize); 72 | w.Write(serializedSize); 73 | } 74 | 75 | msChecksums.Position = 0; 76 | msChecksums.CopyTo(bundleStream); 77 | 78 | msChunks.Position = 0; 79 | msChunks.CopyTo(bundleStream); 80 | } 81 | 82 | public static ScimitarStreamingPackedData Read(Stream bundleStream) 83 | { 84 | var r = new BinaryReader(bundleStream); 85 | 86 | var numChunks = r.ReadUInt16(); 87 | var unk1 = r.ReadUInt16(); 88 | 89 | var sizeData = r.ReadStructs(numChunks); 90 | var checksumData = new uint[numChunks]; 91 | 92 | for (var i = 0; i < numChunks; i++) 93 | checksumData[i] = r.ReadUInt32(); 94 | 95 | var offsets = new long[numChunks]; 96 | for (var i = 0; i < sizeData.Length; i++) 97 | { 98 | var sizeInfo = sizeData[i]; 99 | 100 | offsets[i] = r.BaseStream.Position; 101 | r.BaseStream.Seek(sizeInfo.SerializedSize, SeekOrigin.Current); 102 | } 103 | 104 | return new ScimitarStreamingPackedData(unk1, sizeData, checksumData, offsets); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /Prism/Render/Shader/ShaderProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using OpenTK; 5 | using OpenTK.Graphics.OpenGL; 6 | 7 | namespace Prism.Render.Shader 8 | { 9 | public class ShaderProgram 10 | { 11 | private readonly string _fProg; 12 | private readonly string _vProg; 13 | protected readonly Dictionary CacheLoc; 14 | protected int FsId; 15 | protected int PgmId; 16 | 17 | public UniformCollection Uniforms; 18 | protected int VsId; 19 | 20 | public ShaderProgram(string fProg, string vProg) 21 | { 22 | _fProg = fProg; 23 | _vProg = vProg; 24 | CacheLoc = new Dictionary(); 25 | Uniforms = new UniformCollection(); 26 | PgmId = GL.CreateProgram(); 27 | Init(); 28 | } 29 | 30 | private void Init() 31 | { 32 | LoadShader(_fProg, ShaderType.FragmentShader, PgmId, out FsId); 33 | LoadShader(_vProg, ShaderType.VertexShader, PgmId, out VsId); 34 | 35 | GL.LinkProgram(PgmId); 36 | Log(GL.GetProgramInfoLog(PgmId)); 37 | } 38 | 39 | public void Use(UniformCollection uniforms) 40 | { 41 | Use(); 42 | SetupUniforms(uniforms); 43 | } 44 | 45 | public void Use() 46 | { 47 | GL.UseProgram(PgmId); 48 | SetupUniforms(Uniforms); 49 | } 50 | 51 | public void Release() 52 | { 53 | GL.UseProgram(0); 54 | } 55 | 56 | protected virtual void SetupUniforms(UniformCollection uniforms) 57 | { 58 | foreach (var uniform in uniforms) 59 | { 60 | var loc = GetCachedUniformLoc(uniform.Name); 61 | var val = uniform.GetValue(); 62 | var type = uniform.UniformType; 63 | if (type == typeof(float)) 64 | { 65 | GL.Uniform1(loc, (float)val); 66 | } 67 | else if (type == typeof(double)) 68 | { 69 | GL.Uniform1(loc, (double)val); 70 | } 71 | else if (type == typeof(int)) 72 | { 73 | GL.Uniform1(loc, (int)val); 74 | } 75 | else if (type == typeof(uint)) 76 | { 77 | GL.Uniform1(loc, (uint)val); 78 | } 79 | else if (type == typeof(Vector2)) 80 | { 81 | var vec2 = (Vector2)val; 82 | GL.Uniform2(loc, vec2.X, vec2.Y); 83 | } 84 | else if (type == typeof(Vector3)) 85 | { 86 | var vec3 = (Vector3)val; 87 | GL.Uniform3(loc, vec3.X, vec3.Y, vec3.Z); 88 | } 89 | else if (type == typeof(Matrix4)) 90 | { 91 | var mat4 = (Matrix4)val; 92 | GL.UniformMatrix4(loc, false, ref mat4); 93 | } 94 | else 95 | { 96 | throw new ArgumentException($"Unsupported uniform type: {type} (\"{uniform.Name}\")"); 97 | } 98 | } 99 | } 100 | 101 | private int GetCachedUniformLoc(string uniformName) 102 | { 103 | if (CacheLoc.ContainsKey(uniformName)) return CacheLoc[uniformName]; 104 | 105 | var loc = GL.GetUniformLocation(PgmId, uniformName); 106 | CacheLoc.Add(uniformName, loc); 107 | if (loc == -1) 108 | Log($"Shader uniform \"{uniformName}\" does not correspond to an active uniform variable in program {PgmId}"); 109 | 110 | return loc; 111 | } 112 | 113 | protected void LoadShader(string source, ShaderType type, int program, out int address) 114 | { 115 | address = GL.CreateShader(type); 116 | GL.ShaderSource(address, source); 117 | GL.CompileShader(address); 118 | GL.AttachShader(program, address); 119 | Log(GL.GetShaderInfoLog(address)); 120 | } 121 | 122 | protected void Log(string msg) 123 | { 124 | msg = msg.Trim(); 125 | if (msg.Length > 0) 126 | Debug.WriteLine("GLSL", msg); 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /AssetCatalog/Render/Shader/ShaderProgram.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using OpenTK.Graphics.OpenGL; 5 | using OpenTK.Mathematics; 6 | 7 | namespace AssetCatalog.Render.Shader 8 | { 9 | public class ShaderProgram 10 | { 11 | private readonly string _fProg; 12 | private readonly string _vProg; 13 | protected readonly Dictionary CacheLoc; 14 | protected int FsId; 15 | protected int PgmId; 16 | 17 | public UniformCollection Uniforms; 18 | protected int VsId; 19 | 20 | public ShaderProgram(string fProg, string vProg) 21 | { 22 | _fProg = fProg; 23 | _vProg = vProg; 24 | CacheLoc = new Dictionary(); 25 | Uniforms = new UniformCollection(); 26 | PgmId = GL.CreateProgram(); 27 | Init(); 28 | } 29 | 30 | private void Init() 31 | { 32 | LoadShader(_fProg, ShaderType.FragmentShader, PgmId, out FsId); 33 | LoadShader(_vProg, ShaderType.VertexShader, PgmId, out VsId); 34 | 35 | GL.LinkProgram(PgmId); 36 | Log(GL.GetProgramInfoLog(PgmId)); 37 | } 38 | 39 | public void Use(UniformCollection uniforms) 40 | { 41 | Use(); 42 | SetupUniforms(uniforms); 43 | } 44 | 45 | public void Use() 46 | { 47 | GL.UseProgram(PgmId); 48 | SetupUniforms(Uniforms); 49 | } 50 | 51 | public void Release() 52 | { 53 | GL.UseProgram(0); 54 | } 55 | 56 | protected virtual void SetupUniforms(UniformCollection uniforms) 57 | { 58 | foreach (var uniform in uniforms) 59 | { 60 | var loc = GetCachedUniformLoc(uniform.Name); 61 | var val = uniform.GetValue(); 62 | var type = uniform.UniformType; 63 | if (type == typeof(float)) 64 | { 65 | GL.Uniform1(loc, (float) val); 66 | } 67 | else if (type == typeof(double)) 68 | { 69 | GL.Uniform1(loc, (double) val); 70 | } 71 | else if (type == typeof(int)) 72 | { 73 | GL.Uniform1(loc, (int) val); 74 | } 75 | else if (type == typeof(uint)) 76 | { 77 | GL.Uniform1(loc, (uint) val); 78 | } 79 | else if (type == typeof(Vector2)) 80 | { 81 | var vec2 = (Vector2) val; 82 | GL.Uniform2(loc, vec2.X, vec2.Y); 83 | } 84 | else if (type == typeof(Vector3)) 85 | { 86 | var vec3 = (Vector3) val; 87 | GL.Uniform3(loc, vec3.X, vec3.Y, vec3.Z); 88 | } 89 | else if (type == typeof(Matrix4)) 90 | { 91 | var mat4 = (Matrix4) val; 92 | GL.UniformMatrix4(loc, false, ref mat4); 93 | } 94 | else 95 | { 96 | throw new ArgumentException($"Unsupported uniform type: {type} (\"{uniform.Name}\")"); 97 | } 98 | } 99 | } 100 | 101 | private int GetCachedUniformLoc(string uniformName) 102 | { 103 | if (CacheLoc.ContainsKey(uniformName)) return CacheLoc[uniformName]; 104 | 105 | var loc = GL.GetUniformLocation(PgmId, uniformName); 106 | CacheLoc.Add(uniformName, loc); 107 | if (loc == -1) 108 | Log($"Shader uniform \"{uniformName}\" does not correspond to an active uniform variable in program {PgmId}"); 109 | 110 | return loc; 111 | } 112 | 113 | protected void LoadShader(string source, ShaderType type, int program, out int address) 114 | { 115 | address = GL.CreateShader(type); 116 | GL.ShaderSource(address, source); 117 | GL.CompileShader(address); 118 | GL.AttachShader(program, address); 119 | Log(GL.GetShaderInfoLog(address)); 120 | } 121 | 122 | protected void Log(string msg) 123 | { 124 | msg = msg.Trim(); 125 | if (msg.Length > 0) 126 | Debug.WriteLine("GLSL", msg); 127 | } 128 | } 129 | } --------------------------------------------------------------------------------