├── .gitattributes ├── .gitignore ├── README.md ├── XBC2ModelDecomp.sln └── XBC2ModelDecomp ├── App.config ├── App.xaml ├── App.xaml.cs ├── FormatTools.cs ├── MainFormTest.xaml ├── MainFormTest.xaml.cs ├── MapTools.cs ├── ModelTools.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Structs.cs ├── TextureTools.cs ├── XBC2ModelDecomp.csproj └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XBC2ModelDecomp 2 | A reformatted fork of a [project created by daemon1](https://forum.xentax.com/viewtopic.php?f=16&t=18087) on the XeNTaX forums. All credit to them and to [PredatorCZ/Lukas Cone](https://lukascone.wordpress.com/2018/05/06/xenoblade-chronicles-import-tool/) for publicizing tools to reverse the game's various model formats. Special thanks to Turk645 as well for working on the map formats and for bouncing ideas back and forth with. 3 | 4 | ## Features 5 | * Dump models (including bones and flexes) to XNALara ascii or glTF 6 | * Dump maps (including props) to XNALara ascii or glTF 7 | * Save specified LOD values for both props and maps 8 | * Dump all textures from files (including mesh textures/main chunk textures) 9 | * Dumps raw files and animations for research 10 | 11 | ## Running 12 | Simply run the executable, and pick an input file. An output folder will be created in the path you choose your file(s) in, but you can override this by picking a output folder manually. Each file will have its own folder in the output folder. Then, configure your output settings at the bottom and hit Extract. The file should export to the output path in the format you chose. 13 | 14 | If you are using Blender, it does not natively support the XNALara format; I recommend [johnzero7's plugin,](https://github.com/johnzero7/XNALaraMesh) however many other plugins exist for other modeling programs. The glTF format is also [open source](https://github.com/KhronosGroup/glTF) and used by many modeling tools and game engines. However, the current version of Blender does not support bones or facial flexes, so I have not yet implemented them into the tool. 15 | 16 | ## Compiling 17 | Every NuGet package should be included in the solution, but if for whatever reason they are missing, download the latest [zlib](https://www.nuget.org/packages/zlib.net/1.0.4) (model decompression), [GitInfo](https://github.com/kzu/GitInfo) (commit hash in title), [SharpGLTF](https://github.com/vpenades/SharpGLTF) (glTF export), and [WindowsAPICodePack-Shell](https://github.com/contre/Windows-API-Code-Pack-1.1) (non-infuriating folder selection.) 18 | 19 | I've personally used Visual Studio 2017 to write the majority of this, but Visual Studio 2019 works as well. -------------------------------------------------------------------------------- /XBC2ModelDecomp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XBC2ModelDecomp", "XBC2ModelDecomp\XBC2ModelDecomp.csproj", "{63858B8D-24A0-4E76-83F6-F792C2117318}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|x64.ActiveCfg = Debug|x64 21 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|x64.Build.0 = Debug|x64 22 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Debug|x86.Build.0 = Debug|Any CPU 24 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|x64.ActiveCfg = Release|x64 27 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|x64.Build.0 = Release|x64 28 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|x86.ActiveCfg = Release|Any CPU 29 | {63858B8D-24A0-4E76-83F6-F792C2117318}.Release|x86.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {82F50C24-F234-427A-A3F2-2DCFEE59AC2B} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /XBC2ModelDecomp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /XBC2ModelDecomp/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /XBC2ModelDecomp/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Threading; 10 | 11 | namespace XBC2ModelDecomp 12 | { 13 | /// 14 | /// Interaction logic for App.xaml 15 | /// 16 | public partial class App : Application 17 | { 18 | public static string[] FilePaths; 19 | public static string[] OutputPaths; 20 | public static int FileIndex; 21 | 22 | public static string CurFile { get { return FilePaths[FileIndex]; } } 23 | public static string CurOutputPath { get { return OutputPaths[FileIndex]; } } 24 | public static string CurFilePath { get { return Path.GetDirectoryName(FilePaths[FileIndex]); } } 25 | public static string CurFileNameNoExt { get { return Path.GetFileNameWithoutExtension(FilePaths[FileIndex]); } } 26 | public static string CurFilePathAndName { get { return $@"{CurFilePath}\{CurFileNameNoExt}"; } } 27 | 28 | public static bool ExportTextures; 29 | public static bool ExportFlexes; 30 | public static bool ExportAnims; 31 | public static bool ExportOutlines; 32 | public static bool ExportMapMesh; 33 | public static bool ExportMapProps; 34 | public static bool ShowInfo; 35 | public static int LOD; 36 | public static int PropSplitCount; 37 | public static Structs.ExportFormat ExportFormat = Structs.ExportFormat.XNALara; 38 | 39 | public delegate void Log(object logMessage); 40 | public static event Log LogEvent; 41 | 42 | public static void PushLog(object logMessage) 43 | { 44 | Application.Current.Dispatcher.Invoke(() => 45 | { 46 | Console.WriteLine(logMessage.ToString()); 47 | LogEvent?.Invoke(logMessage.ToString()); 48 | }); 49 | } 50 | 51 | [STAThread] 52 | public static void Main(string[] args) 53 | { 54 | App app = new App(); 55 | app.InitializeComponent(); 56 | app.Run(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /XBC2ModelDecomp/FormatTools.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Numerics; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows; 9 | using System.Windows.Media; 10 | using SharpGLTF.Schema2; 11 | using SharpGLTF.Geometry; 12 | using SharpGLTF.Geometry.VertexTypes; 13 | using SharpGLTF.Transforms; 14 | using zlib; 15 | using SharpGLTF.Materials; 16 | using SharpGLTF.Scenes; 17 | 18 | namespace XBC2ModelDecomp 19 | { 20 | using GLTFVert = VertexBuilder; 21 | 22 | public class FormatTools 23 | { 24 | public static string ReadNullTerminatedString(BinaryReader br) 25 | { 26 | string text = ""; 27 | byte b; 28 | while ((b = br.ReadByte()) > 0) 29 | { 30 | text += (char)b; 31 | } 32 | return text; 33 | } 34 | 35 | public static Vector4 ColorToVector4(Color col) 36 | { 37 | return new Vector4(col.R / 255f, col.G / 255f, col.B / 255f, col.A / 255f); 38 | } 39 | 40 | public void SaveStreamToFile(Stream stream, string fileName, string filePath) 41 | { 42 | if (!string.IsNullOrWhiteSpace(fileName) && !string.IsNullOrWhiteSpace(filePath)) 43 | { 44 | if (fileName[1] == ':') 45 | fileName = fileName.Substring(3); 46 | 47 | filePath += $@"{string.Join("/", fileName.Split('/').Reverse().Skip(1).Reverse())}"; 48 | fileName = fileName.Split('/').Last(); 49 | 50 | if (!string.IsNullOrWhiteSpace(fileName) && !string.IsNullOrWhiteSpace(filePath)) 51 | { 52 | if (!Directory.Exists(filePath)) 53 | Directory.CreateDirectory(filePath); 54 | FileStream outputter = new FileStream($@"{filePath}\{fileName}", FileMode.OpenOrCreate); 55 | stream.CopyTo(outputter); 56 | outputter.Flush(); 57 | outputter.Close(); 58 | 59 | stream.Seek(0, SeekOrigin.Begin); 60 | } 61 | } 62 | else 63 | { 64 | App.PushLog("No filename or file path given to SaveStreamToFile()!"); 65 | } 66 | } 67 | 68 | public static void ReadXBC1Datas(Stream sXBC1s, ref Structs.XBC1[] XBC1s, bool forceReread = false) 69 | { 70 | for (int i = 0; i < XBC1s.Length; i++) 71 | { 72 | if (XBC1s[i].Data == null || forceReread) 73 | XBC1s[i].Data = ReadZlib(sXBC1s, XBC1s[i].OffsetInFile, XBC1s[i].FileSize, XBC1s[i].CompressedSize); 74 | } 75 | } 76 | 77 | public static MemoryStream ReadZlib(Stream sZlib, int Offset, int FileSize, int CompressedSize) 78 | { 79 | sZlib.Seek(Offset, SeekOrigin.Begin); 80 | byte[] fileBuffer = new byte[FileSize >= CompressedSize ? FileSize : CompressedSize]; 81 | 82 | MemoryStream msFile = new MemoryStream(); 83 | sZlib.Read(fileBuffer, 0, CompressedSize); 84 | 85 | ZOutputStream ZOutFile = new ZOutputStream(msFile); 86 | ZOutFile.Write(fileBuffer, 0, CompressedSize); 87 | ZOutFile.Flush(); 88 | 89 | msFile.Seek(0L, SeekOrigin.Begin); 90 | return msFile; 91 | } 92 | 93 | public List[] VerifyMeshes(Structs.Mesh Mesh, Structs.MXMD MXMD) 94 | { 95 | if (MXMD.Version == Int32.MaxValue) 96 | return new List[] { new List { 0 } }; 97 | 98 | List[] VerifiedMeshes = new List[MXMD.ModelStruct.MeshesCount]; 99 | 100 | for (int i = 0; i < MXMD.ModelStruct.MeshesCount; i++) 101 | { 102 | int ModifiableLOD = App.LOD; 103 | if (MXMD.ModelStruct.Meshes[i].Descriptors.Count(x => x.LOD == ModifiableLOD || ModifiableLOD == -1) == 0) 104 | { 105 | App.PushLog($"An LOD value of {ModifiableLOD} returns 0 meshes, checking for the highest available LOD..."); 106 | for (int j = 0; j <= 3; j++) 107 | { 108 | if (MXMD.ModelStruct.Meshes[i].Descriptors.Count(x => x.LOD == j) > 0) 109 | { 110 | ModifiableLOD = j; 111 | App.PushLog($"LOD set to {j}."); 112 | break; 113 | } 114 | } 115 | } 116 | 117 | List ValidMeshes = new List { ModifiableLOD }; //stupid hack to preserve LOD values back into functions 118 | for (int j = 0; j < MXMD.ModelStruct.Meshes[i].TableCount; j++) 119 | { 120 | if (MXMD.ModelStruct.Meshes[i].Descriptors[j].LOD == ModifiableLOD || ModifiableLOD == -1) 121 | { 122 | if (App.ExportOutlines || (!App.ExportOutlines && !MXMD.Materials[MXMD.ModelStruct.Meshes[i].Descriptors[j].MaterialID].Name.Contains("outline"))) 123 | { 124 | ValidMeshes.Add(j); 125 | if (App.ExportFormat == Structs.ExportFormat.XNALara && App.ExportFlexes && Mesh.MorphDataOffset > 0) 126 | { 127 | List descs = Mesh.MorphData.MorphDescriptors.Where(x => x.BufferID == MXMD.ModelStruct.Meshes[i].Descriptors[j].VertTableIndex).ToList(); 128 | if (descs.Count > 0) 129 | for (int k = 1; k < descs[0].TargetCounts; k++) 130 | ValidMeshes.Add(j); 131 | } 132 | } 133 | } 134 | } 135 | 136 | VerifiedMeshes[i] = ValidMeshes; 137 | } 138 | 139 | return VerifiedMeshes; 140 | } 141 | 142 | public Structs.XBC1 ReadXBC1(Stream sXBC1, BinaryReader brXBC1, int offset, bool saveStream = false) 143 | { 144 | if (sXBC1 == null || brXBC1 == null || offset > sXBC1.Length || offset < 0) 145 | return new Structs.XBC1 { Version = Int32.MaxValue }; 146 | 147 | sXBC1.Seek(offset, SeekOrigin.Begin); 148 | int XBC1Magic = brXBC1.ReadInt32(); //nice meme 149 | if (XBC1Magic != 0x31636278) 150 | { 151 | App.PushLog("XBC1 header invalid!"); 152 | return new Structs.XBC1 { Version = Int32.MaxValue }; 153 | } 154 | 155 | Structs.XBC1 XBC1 = new Structs.XBC1 156 | { 157 | Version = brXBC1.ReadInt32(), 158 | FileSize = brXBC1.ReadInt32(), 159 | CompressedSize = brXBC1.ReadInt32(), 160 | Unknown1 = brXBC1.ReadInt32(), 161 | Name = ReadNullTerminatedString(brXBC1), 162 | OffsetInFile = offset 163 | }; 164 | 165 | if (saveStream) 166 | { 167 | XBC1.Data = ReadZlib(sXBC1, offset + 0x30, XBC1.FileSize, XBC1.CompressedSize); 168 | } 169 | 170 | return XBC1; 171 | } 172 | 173 | public Structs.SAR1 ReadSAR1(Stream sSAR1, BinaryReader brSAR1, string folderPath, bool folderConditional) 174 | { 175 | App.PushLog("Parsing SAR1..."); 176 | sSAR1.Seek(0, SeekOrigin.Begin); 177 | int SAR1Magic = brSAR1.ReadInt32(); 178 | if (SAR1Magic != 0x53415231) 179 | { 180 | App.PushLog("SAR1 is corrupt (or wrong endianness)!"); 181 | return new Structs.SAR1 { Version = Int32.MaxValue }; 182 | } 183 | 184 | Structs.SAR1 SAR1 = new Structs.SAR1 185 | { 186 | FileSize = brSAR1.ReadInt32(), 187 | Version = brSAR1.ReadInt32(), 188 | NumFiles = brSAR1.ReadInt32(), 189 | TOCOffset = brSAR1.ReadInt32(), 190 | DataOffset = brSAR1.ReadInt32(), 191 | Unknown1 = brSAR1.ReadInt32(), 192 | Unknown2 = brSAR1.ReadInt32(), 193 | Path = ReadNullTerminatedString(brSAR1) 194 | }; 195 | 196 | string safePath = App.CurOutputPath + folderPath; 197 | if (SAR1.Path[1] == ':') 198 | safePath += SAR1.Path.Substring(3); 199 | else if (SAR1.Path[0] == '/') 200 | safePath += SAR1.Path.Substring(1); 201 | else 202 | safePath += SAR1.Path; 203 | 204 | if (folderConditional && !Directory.Exists(safePath)) 205 | Directory.CreateDirectory(safePath); 206 | 207 | SAR1.TOCItems = new Structs.SARTOC[SAR1.NumFiles]; 208 | for (int i = 0; i < SAR1.NumFiles; i++) 209 | { 210 | sSAR1.Seek(SAR1.TOCOffset + (i * 0x40), SeekOrigin.Begin); 211 | SAR1.TOCItems[i] = new Structs.SARTOC 212 | { 213 | Offset = brSAR1.ReadInt32(), 214 | Size = brSAR1.ReadInt32(), 215 | Unknown1 = brSAR1.ReadInt32(), 216 | Filename = ReadNullTerminatedString(brSAR1) 217 | }; 218 | } 219 | 220 | SAR1.BCItems = new Structs.SARBC[SAR1.NumFiles]; 221 | long nextPosition = SAR1.DataOffset; 222 | 223 | for (int i = 0; i < SAR1.NumFiles; i++) 224 | { 225 | sSAR1.Seek(SAR1.TOCItems[i].Offset, SeekOrigin.Begin); 226 | int BCMagic = brSAR1.ReadInt32(); 227 | if (BCMagic == 0x4C434843) //CHCL 228 | continue; 229 | if (BCMagic != 0x00004342) 230 | { 231 | App.PushLog("BC is corrupt (or wrong endianness)!"); 232 | return new Structs.SAR1 { Version = Int32.MaxValue }; 233 | } 234 | SAR1.BCItems[i] = new Structs.SARBC 235 | { 236 | BlockCount = brSAR1.ReadInt32(), 237 | FileSize = brSAR1.ReadInt32(), 238 | PointerCount = brSAR1.ReadInt32(), 239 | OffsetToData = brSAR1.ReadInt32(), 240 | }; 241 | 242 | sSAR1.Seek(SAR1.TOCItems[i].Offset + SAR1.BCItems[i].OffsetToData + 0x4, SeekOrigin.Begin); 243 | 244 | SAR1.BCItems[i].Data = new MemoryStream(SAR1.BCItems[i].FileSize - SAR1.BCItems[i].OffsetToData); 245 | sSAR1.CopyTo(SAR1.BCItems[i].Data); 246 | 247 | if (folderConditional) 248 | { 249 | FileStream outputter = new FileStream($@"{safePath}\{SAR1.TOCItems[i].Filename}", FileMode.OpenOrCreate); 250 | SAR1.BCItems[i].Data.WriteTo(outputter); 251 | outputter.Flush(); 252 | outputter.Close(); 253 | } 254 | 255 | nextPosition += SAR1.BCItems[i].FileSize; 256 | } 257 | 258 | return SAR1; 259 | } 260 | 261 | public Structs.SKEL ReadSKEL(Stream sSKEL, BinaryReader brSKEL) 262 | { 263 | App.PushLog("Parsing SKEL..."); 264 | sSKEL.Seek(0, SeekOrigin.Begin); 265 | int SKELMagic = brSKEL.ReadInt32(); 266 | if (SKELMagic != 0x4C454B53) 267 | { 268 | App.PushLog("SKEL is corrupt (or wrong endianness)!"); 269 | return new Structs.SKEL { Parents = new short[0] }; 270 | } 271 | 272 | Structs.SKEL SKEL = new Structs.SKEL 273 | { 274 | Unknown1 = brSKEL.ReadInt32(), 275 | Unknown2 = brSKEL.ReadInt32(), 276 | 277 | TOCItems = new Structs.SKELTOC[9], 278 | 279 | NodeNames = new Dictionary() 280 | }; 281 | 282 | for (int i = 0; i < SKEL.TOCItems.Length; i++) 283 | { 284 | SKEL.TOCItems[i] = new Structs.SKELTOC 285 | { 286 | Offset = brSKEL.ReadInt32(), 287 | Unknown1 = brSKEL.ReadInt32(), 288 | Count = brSKEL.ReadInt32(), 289 | Unknown2 = brSKEL.ReadInt32() 290 | }; 291 | } 292 | 293 | SKEL.Parents = new short[SKEL.TOCItems[2].Count]; 294 | sSKEL.Seek(SKEL.TOCItems[2].Offset - 0x24, SeekOrigin.Begin); 295 | for (int i = 0; i < SKEL.Parents.Length; i++) 296 | { 297 | SKEL.Parents[i] = brSKEL.ReadInt16(); 298 | } 299 | 300 | SKEL.Nodes = new Structs.SKELNodes[SKEL.TOCItems[3].Count]; 301 | for (int i = 0; i < SKEL.Nodes.Length; i++) 302 | { 303 | sSKEL.Seek(SKEL.TOCItems[3].Offset - 0x24 + (i * 0x10), SeekOrigin.Begin); 304 | SKEL.Nodes[i] = new Structs.SKELNodes 305 | { 306 | Offset = brSKEL.ReadInt32(), 307 | Unknown1 = brSKEL.ReadBytes(0xC) 308 | }; 309 | sSKEL.Seek(SKEL.Nodes[i].Offset - 0x24, SeekOrigin.Begin); 310 | SKEL.Nodes[i].Name = ReadNullTerminatedString(brSKEL); 311 | } 312 | 313 | SKEL.Transforms = new Structs.SKELTransforms[SKEL.TOCItems[4].Count]; 314 | sSKEL.Seek(SKEL.TOCItems[4].Offset - 0x24, SeekOrigin.Begin); 315 | for (int i = 0; i < SKEL.Transforms.Length; i++) 316 | { 317 | SKEL.Transforms[i] = new Structs.SKELTransforms 318 | { 319 | Position = new Quaternion(brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle()), 320 | Rotation = new Quaternion(brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle()), 321 | Scale = new Quaternion(brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle(), brSKEL.ReadSingle()) 322 | }; 323 | } 324 | 325 | for (int i = 0; i < SKEL.TOCItems[2].Count; i++) 326 | { 327 | Quaternion posQuat = SKEL.Transforms[i].Position; 328 | Vector3 posVector = new Vector3(posQuat.X, posQuat.Y, posQuat.Z); 329 | SKEL.Transforms[i].RealPosition = posVector; 330 | 331 | SKEL.NodeNames.Add(SKEL.Nodes[i].Name, i); 332 | 333 | if (SKEL.Parents[i] < 0) //is root 334 | { 335 | SKEL.Transforms[i].RealPosition = posVector; 336 | SKEL.Transforms[i].RealRotation = SKEL.Transforms[i].Rotation; 337 | } 338 | else 339 | { 340 | int curParentIndex = SKEL.Parents[i]; 341 | //add rotation of parent 342 | SKEL.Transforms[i].RealRotation = SKEL.Transforms[curParentIndex].RealRotation * SKEL.Transforms[i].Rotation; 343 | //make position a quaternion again (for later operations) 344 | Quaternion bonePosQuat = new Quaternion(posVector, 0f); 345 | //multiply position and rotation (?) 346 | Quaternion bonePosRotQuat = SKEL.Transforms[curParentIndex].RealRotation * bonePosQuat; 347 | //do something or other i dunno 348 | Quaternion newPosition = bonePosRotQuat * new Quaternion(-SKEL.Transforms[curParentIndex].RealRotation.X, -SKEL.Transforms[curParentIndex].RealRotation.Y, -SKEL.Transforms[curParentIndex].RealRotation.Z, SKEL.Transforms[curParentIndex].RealRotation.W); 349 | //add position to parent's position 350 | SKEL.Transforms[i].RealPosition = new Vector3(newPosition.X, newPosition.Y, newPosition.Z) + SKEL.Transforms[curParentIndex].RealPosition; 351 | } 352 | } 353 | 354 | return SKEL; 355 | } 356 | 357 | public Structs.LBIM ReadLBIM(Stream sLBIM, BinaryReader brLBIM, int Offset, int Size, Structs.MSRDDataItem dataItem = default(Structs.MSRDDataItem)) 358 | { 359 | sLBIM.Seek(Offset + Size - 0x4, SeekOrigin.Begin); 360 | if (brLBIM.ReadInt32() != 0x4D49424C) 361 | { 362 | App.PushLog($"Texture magic is incorrect! Offset {Offset:X}, size {Size:X}, cur position {sLBIM.Position:X}"); 363 | return default(Structs.LBIM); 364 | } 365 | 366 | sLBIM.Seek(Offset + Size - 0x28, SeekOrigin.Begin); 367 | Structs.LBIM LBIM = new Structs.LBIM 368 | { 369 | Data = new MemoryStream(dataItem.Size), 370 | 371 | Unknown5 = brLBIM.ReadInt32(), 372 | Unknown4 = brLBIM.ReadInt32(), 373 | 374 | Width = brLBIM.ReadInt32(), 375 | Height = brLBIM.ReadInt32(), 376 | 377 | Unknown3 = brLBIM.ReadInt32(), 378 | Unknown2 = brLBIM.ReadInt32(), 379 | 380 | Type = brLBIM.ReadInt32(), 381 | Unknown1 = brLBIM.ReadInt32(), 382 | Version = brLBIM.ReadInt32() 383 | }; 384 | 385 | 386 | 387 | if (dataItem.Size != default(Structs.MSRDDataItem).Size) 388 | LBIM.DataItem = dataItem; 389 | sLBIM.Seek(Offset, SeekOrigin.Begin); 390 | sLBIM.CopyTo(LBIM.Data, Size); 391 | 392 | return LBIM; 393 | } 394 | 395 | public Structs.MSRD ReadMSRD(Stream sMSRD, BinaryReader brMSRD) 396 | { 397 | App.PushLog("Parsing MSRD..."); 398 | sMSRD.Seek(0, SeekOrigin.Begin); 399 | int MSRDMagic = brMSRD.ReadInt32(); 400 | if (MSRDMagic != 0x4D535244) 401 | { 402 | App.PushLog("MSRD is corrupt (or wrong endianness)!"); 403 | return new Structs.MSRD { Version = Int32.MaxValue }; 404 | } 405 | 406 | Structs.MSRD MSRD = new Structs.MSRD 407 | { 408 | Version = brMSRD.ReadInt32(), 409 | HeaderSize = brMSRD.ReadInt32(), 410 | MainOffset = brMSRD.ReadInt32(), 411 | 412 | Tag = brMSRD.ReadInt32(), 413 | Revision = brMSRD.ReadInt32(), 414 | 415 | DataItemsCount = brMSRD.ReadInt32(), 416 | DataItemsOffset = brMSRD.ReadInt32(), 417 | FileCount = brMSRD.ReadInt32(), 418 | TOCOffset = brMSRD.ReadInt32(), 419 | 420 | Unknown1 = brMSRD.ReadBytes(0x1C), 421 | 422 | TextureIdsCount = brMSRD.ReadInt32(), 423 | TextureIdsOffset = brMSRD.ReadInt32(), 424 | TextureCountOffset = brMSRD.ReadInt32() 425 | }; 426 | 427 | if (MSRD.DataItemsOffset != 0) 428 | { 429 | MSRD.DataItems = new Structs.MSRDDataItem[MSRD.DataItemsCount]; 430 | sMSRD.Seek(MSRD.MainOffset + MSRD.DataItemsOffset, SeekOrigin.Begin); 431 | for (int i = 0; i < MSRD.DataItemsCount; i++) 432 | { 433 | MSRD.DataItems[i] = new Structs.MSRDDataItem 434 | { 435 | Offset = brMSRD.ReadInt32(), 436 | Size = brMSRD.ReadInt32(), 437 | TOCIndex = brMSRD.ReadInt16(), 438 | Type = (Structs.MSRDDataItemTypes)brMSRD.ReadInt16() 439 | }; 440 | sMSRD.Seek(0x8, SeekOrigin.Current); 441 | } 442 | } 443 | 444 | if (MSRD.TextureIdsOffset != 0) 445 | { 446 | sMSRD.Seek(MSRD.MainOffset + MSRD.TextureIdsOffset, SeekOrigin.Begin); 447 | MSRD.TextureIds = new short[MSRD.TextureIdsCount]; 448 | for (int i = 0; i < MSRD.TextureIdsCount; i++) 449 | { 450 | MSRD.TextureIds[i] = brMSRD.ReadInt16(); 451 | } 452 | } 453 | 454 | if (MSRD.TextureCountOffset != 0) 455 | { 456 | sMSRD.Seek(MSRD.MainOffset + MSRD.TextureCountOffset, SeekOrigin.Begin); 457 | MSRD.TextureCount = brMSRD.ReadInt32(); //0x25E 458 | MSRD.TextureChunkSize = brMSRD.ReadInt32(); 459 | MSRD.Unknown2 = brMSRD.ReadInt32(); 460 | MSRD.TextureStringBufferOffset = brMSRD.ReadInt32(); 461 | 462 | MSRD.TextureInfo = new Structs.MSRDTextureInfo[MSRD.TextureCount]; 463 | for (int i = 0; i < MSRD.TextureCount; i++) 464 | { 465 | MSRD.TextureInfo[i].Unknown1 = brMSRD.ReadInt32(); 466 | MSRD.TextureInfo[i].Size = brMSRD.ReadInt32(); 467 | MSRD.TextureInfo[i].Offset = brMSRD.ReadInt32(); 468 | MSRD.TextureInfo[i].StringOffset = brMSRD.ReadInt32(); 469 | } 470 | 471 | MSRD.TextureNames = new string[MSRD.TextureCount]; 472 | for (int i = 0; i < MSRD.TextureCount; i++) 473 | { 474 | sMSRD.Seek(MSRD.MainOffset + MSRD.TextureCountOffset + MSRD.TextureInfo[i].StringOffset, SeekOrigin.Begin); 475 | MSRD.TextureNames[i] = FormatTools.ReadNullTerminatedString(brMSRD); 476 | } 477 | } 478 | 479 | if (MSRD.TextureIdsOffset == MSRD.TextureCountOffset) 480 | { 481 | MSRD.TextureIds = new short[MSRD.TextureCount]; 482 | for (int i = 0; i < MSRD.TextureCount; i++) 483 | { 484 | MSRD.TextureIds[i] = brMSRD.ReadInt16(); 485 | } 486 | } 487 | 488 | MSRD.TOC = new Structs.MSRDTOC[MSRD.FileCount]; 489 | for (int i = 0; i < MSRD.FileCount; i++) 490 | { 491 | sMSRD.Seek(MSRD.MainOffset + MSRD.TOCOffset + (i * 12), SeekOrigin.Begin); //prevents errors I guess 492 | MSRD.TOC[i].CompSize = brMSRD.ReadInt32(); 493 | MSRD.TOC[i].FileSize = brMSRD.ReadInt32(); 494 | MSRD.TOC[i].Offset = brMSRD.ReadInt32(); 495 | 496 | App.PushLog($"Decompressing file{i} in MSRD..."); 497 | MSRD.TOC[i].Data = ReadXBC1(sMSRD, brMSRD, MSRD.TOC[i].Offset, true).Data; 498 | if (App.ExportFormat == Structs.ExportFormat.RawFiles) 499 | SaveStreamToFile(MSRD.TOC[i].Data, $"file{i}.bin", App.CurOutputPath + @"\RawFiles\"); 500 | } 501 | 502 | return MSRD; 503 | } 504 | 505 | public Structs.MXMD ReadMXMD(Stream sMXMD, BinaryReader brMXMD) 506 | { 507 | App.PushLog("Parsing MXMD..."); 508 | sMXMD.Seek(0, SeekOrigin.Begin); 509 | int MXMDMagic = brMXMD.ReadInt32(); 510 | if (MXMDMagic != 0x4D584D44) 511 | { 512 | App.PushLog("MXMD is corrupt (or wrong endianness)!"); 513 | return new Structs.MXMD { Version = Int32.MaxValue }; 514 | } 515 | 516 | Structs.MXMD MXMD = new Structs.MXMD 517 | { 518 | Version = brMXMD.ReadInt32(), 519 | 520 | ModelStructOffset = brMXMD.ReadInt32(), 521 | MaterialsOffset = brMXMD.ReadInt32(), 522 | 523 | Unknown1 = brMXMD.ReadInt32(), 524 | 525 | VertexBufferOffset = brMXMD.ReadInt32(), 526 | ShadersOffset = brMXMD.ReadInt32(), 527 | CachedTexturesTableOffset = brMXMD.ReadInt32(), 528 | Unknown2 = brMXMD.ReadInt32(), 529 | UncachedTexturesTableOffset = brMXMD.ReadInt32(), 530 | 531 | Unknown3 = brMXMD.ReadBytes(0x28) 532 | }; 533 | 534 | if (MXMD.ModelStructOffset != 0) 535 | { 536 | sMXMD.Seek(MXMD.ModelStructOffset, SeekOrigin.Begin); 537 | MXMD.ModelStruct = new Structs.MXMDModelStruct 538 | { 539 | Unknown1 = brMXMD.ReadInt32(), 540 | BoundingBoxStart = new Vector3(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()), 541 | BoundingBoxEnd = new Vector3(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()), 542 | MeshesOffset = brMXMD.ReadInt32(), 543 | MeshesCount = brMXMD.ReadInt32(), 544 | Unknown3 = brMXMD.ReadInt32(), 545 | NodesOffset = brMXMD.ReadInt32(), 546 | 547 | Unknown4 = brMXMD.ReadBytes(0x54), 548 | 549 | MorphControllersOffset = brMXMD.ReadInt32(), 550 | MorphNamesOffset = brMXMD.ReadInt32() 551 | }; 552 | 553 | if (MXMD.ModelStruct.MorphControllersOffset != 0) 554 | { 555 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.MorphControllersOffset, SeekOrigin.Begin); 556 | MXMD.ModelStruct.MorphControls = new Structs.MXMDMorphControls 557 | { 558 | TableOffset = brMXMD.ReadInt32(), 559 | Count = brMXMD.ReadInt32(), 560 | 561 | Unknown2 = brMXMD.ReadBytes(0x10) 562 | }; 563 | 564 | MXMD.ModelStruct.MorphControls.Controls = new Structs.MXMDMorphControl[MXMD.ModelStruct.MorphControls.Count]; 565 | long nextPosition = sMXMD.Position; 566 | for (int i = 0; i < MXMD.ModelStruct.MorphControls.Count; i++) 567 | { 568 | sMXMD.Seek(nextPosition, SeekOrigin.Begin); 569 | nextPosition += 0x1C; 570 | MXMD.ModelStruct.MorphControls.Controls[i] = new Structs.MXMDMorphControl 571 | { 572 | NameOffset1 = brMXMD.ReadInt32(), 573 | NameOffset2 = brMXMD.ReadInt32(), //the results of these should be identical 574 | Unknown1 = brMXMD.ReadBytes(0x14) 575 | }; 576 | 577 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.MorphControllersOffset + MXMD.ModelStruct.MorphControls.Controls[i].NameOffset1, SeekOrigin.Begin); 578 | MXMD.ModelStruct.MorphControls.Controls[i].Name = FormatTools.ReadNullTerminatedString(brMXMD); 579 | } 580 | } 581 | 582 | if (MXMD.ModelStruct.MorphNamesOffset != 0) 583 | { 584 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.MorphNamesOffset, SeekOrigin.Begin); 585 | MXMD.ModelStruct.MorphNames = new Structs.MXMDMorphNames 586 | { 587 | TableOffset = brMXMD.ReadInt32(), 588 | Count = brMXMD.ReadInt32(), 589 | 590 | Unknown2 = brMXMD.ReadBytes(0x20) 591 | }; 592 | 593 | MXMD.ModelStruct.MorphNames.Names = new Structs.MXMDMorphName[MXMD.ModelStruct.MorphNames.Count]; 594 | long nextPosition = sMXMD.Position; 595 | for (int i = 0; i < MXMD.ModelStruct.MorphNames.Count; i++) 596 | { 597 | sMXMD.Seek(nextPosition, SeekOrigin.Begin); 598 | nextPosition += 0x10; 599 | MXMD.ModelStruct.MorphNames.Names[i] = new Structs.MXMDMorphName 600 | { 601 | NameOffset = brMXMD.ReadInt32(), 602 | Unknown1 = brMXMD.ReadInt32(), 603 | Unknown2 = brMXMD.ReadInt32(), 604 | Unknown3 = brMXMD.ReadInt32(), 605 | }; 606 | 607 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.MorphNamesOffset + MXMD.ModelStruct.MorphNames.Names[i].NameOffset, SeekOrigin.Begin); 608 | MXMD.ModelStruct.MorphNames.Names[i].Name = FormatTools.ReadNullTerminatedString(brMXMD); 609 | } 610 | } 611 | 612 | if (MXMD.ModelStruct.MeshesOffset != 0) 613 | { 614 | MXMD.ModelStruct.Meshes = new Structs.MXMDMeshes[MXMD.ModelStruct.MeshesCount]; 615 | 616 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.MeshesOffset, SeekOrigin.Begin); 617 | for (int i = 0; i < MXMD.ModelStruct.MeshesCount; i++) 618 | { 619 | MXMD.ModelStruct.Meshes[i] = new Structs.MXMDMeshes 620 | { 621 | TableOffset = brMXMD.ReadInt32(), 622 | TableCount = brMXMD.ReadInt32(), 623 | Unknown1 = brMXMD.ReadInt32(), 624 | 625 | BoundingBoxStart = new Vector3(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()), 626 | BoundingBoxEnd = new Vector3(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()), 627 | BoundingRadius = brMXMD.ReadSingle() 628 | }; 629 | 630 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.Meshes[i].TableOffset, SeekOrigin.Begin); 631 | MXMD.ModelStruct.Meshes[i].Descriptors = new Structs.MXMDMeshDescriptor[MXMD.ModelStruct.Meshes[i].TableCount]; 632 | for (int j = 0; j < MXMD.ModelStruct.Meshes[i].TableCount; j++) 633 | { 634 | MXMD.ModelStruct.Meshes[i].Descriptors[j] = new Structs.MXMDMeshDescriptor 635 | { 636 | //ms says to add 1 to some of these, why? 637 | ID = brMXMD.ReadInt32(), 638 | 639 | Descriptor = brMXMD.ReadInt32(), 640 | 641 | VertTableIndex = brMXMD.ReadInt16(), 642 | FaceTableIndex = brMXMD.ReadInt16(), 643 | 644 | Unknown1 = brMXMD.ReadInt16(), 645 | MaterialID = brMXMD.ReadInt16(), 646 | Unknown2 = brMXMD.ReadBytes(0xC), 647 | Unknown3 = brMXMD.ReadInt16(), 648 | 649 | LOD = brMXMD.ReadInt16(), 650 | Unknown4 = brMXMD.ReadInt32(), 651 | 652 | Unknown5 = brMXMD.ReadBytes(0xC), 653 | }; 654 | } 655 | } 656 | } 657 | 658 | if (MXMD.ModelStruct.NodesOffset != 0) 659 | { 660 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.NodesOffset, SeekOrigin.Begin); 661 | MXMD.ModelStruct.Nodes = new Structs.MXMDNodes 662 | { 663 | BoneCount = brMXMD.ReadInt32(), 664 | BoneCount2 = brMXMD.ReadInt32(), 665 | 666 | NodeIdsOffset = brMXMD.ReadInt32(), 667 | NodeTmsOffset = brMXMD.ReadInt32() 668 | }; 669 | 670 | MXMD.ModelStruct.Nodes.Nodes = new Structs.MXMDNode[MXMD.ModelStruct.Nodes.BoneCount]; 671 | 672 | long nextPosition = MXMD.ModelStructOffset + MXMD.ModelStruct.NodesOffset + MXMD.ModelStruct.Nodes.NodeIdsOffset; 673 | for (int i = 0; i < MXMD.ModelStruct.Nodes.BoneCount; i++) 674 | { 675 | sMXMD.Seek(nextPosition, SeekOrigin.Begin); 676 | nextPosition += 0x18; 677 | MXMD.ModelStruct.Nodes.Nodes[i] = new Structs.MXMDNode 678 | { 679 | NameOffset = brMXMD.ReadInt32(), 680 | Unknown1 = brMXMD.ReadSingle(), 681 | Unknown2 = brMXMD.ReadInt32(), 682 | 683 | ID = brMXMD.ReadInt32(), 684 | Unknown3 = brMXMD.ReadInt32(), 685 | Unknown4 = brMXMD.ReadInt32() 686 | }; 687 | 688 | sMXMD.Seek(MXMD.ModelStructOffset + MXMD.ModelStruct.NodesOffset + MXMD.ModelStruct.Nodes.Nodes[i].NameOffset, SeekOrigin.Begin); 689 | MXMD.ModelStruct.Nodes.Nodes[i].Name = FormatTools.ReadNullTerminatedString(brMXMD); 690 | } 691 | 692 | nextPosition = MXMD.ModelStructOffset + MXMD.ModelStruct.NodesOffset + MXMD.ModelStruct.Nodes.NodeTmsOffset; 693 | for (int i = 0; i < MXMD.ModelStruct.Nodes.BoneCount; i++) 694 | { 695 | sMXMD.Seek(nextPosition, SeekOrigin.Begin); 696 | nextPosition += 0x10 * 4; 697 | 698 | //this is probably very incorrect 699 | MXMD.ModelStruct.Nodes.Nodes[i].Scale = new Quaternion(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()); 700 | MXMD.ModelStruct.Nodes.Nodes[i].Rotation = new Quaternion(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()); 701 | MXMD.ModelStruct.Nodes.Nodes[i].Position = new Quaternion(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()); 702 | 703 | MXMD.ModelStruct.Nodes.Nodes[i].ParentTransform = new Quaternion(brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle(), brMXMD.ReadSingle()); 704 | } 705 | } 706 | } 707 | 708 | if (MXMD.MaterialsOffset != 0) 709 | { 710 | sMXMD.Seek(MXMD.MaterialsOffset, SeekOrigin.Begin); 711 | MXMD.MaterialHeader = new Structs.MXMDMaterialHeader 712 | { 713 | Offset = brMXMD.ReadInt32(), 714 | Count = brMXMD.ReadInt32() 715 | }; 716 | 717 | MXMD.Materials = new Structs.MXMDMaterial[MXMD.MaterialHeader.Count]; 718 | for (int i = 0; i < MXMD.MaterialHeader.Count; i++) 719 | { 720 | sMXMD.Seek(MXMD.MaterialsOffset + MXMD.MaterialHeader.Offset + (i * 0x74), SeekOrigin.Begin); 721 | MXMD.Materials[i] = new Structs.MXMDMaterial 722 | { 723 | NameOffset = brMXMD.ReadInt32(), 724 | Unknown1 = brMXMD.ReadBytes(0x70) 725 | }; 726 | 727 | sMXMD.Seek(MXMD.MaterialsOffset + MXMD.Materials[i].NameOffset, SeekOrigin.Begin); 728 | MXMD.Materials[i].Name = ReadNullTerminatedString(brMXMD); 729 | } 730 | } 731 | 732 | return MXMD; 733 | } 734 | 735 | public Structs.Mesh ReadMesh(Stream sMesh, BinaryReader brMesh) 736 | { 737 | App.PushLog("Parsing mesh..."); 738 | sMesh.Seek(0, SeekOrigin.Begin); 739 | 740 | Structs.Mesh Mesh = new Structs.Mesh 741 | { 742 | VertexTableOffset = brMesh.ReadInt32(), 743 | VertexTableCount = brMesh.ReadInt32(), 744 | FaceTableOffset = brMesh.ReadInt32(), 745 | FaceTableCount = brMesh.ReadInt32(), 746 | 747 | Reserved1 = brMesh.ReadBytes(0xC), 748 | 749 | UnknownOffset1 = brMesh.ReadInt32(), 750 | UnknownOffset2 = brMesh.ReadInt32(), 751 | UnknownOffset2Count = brMesh.ReadInt32(), 752 | 753 | MorphDataOffset = brMesh.ReadInt32(), 754 | DataSize = brMesh.ReadInt32(), 755 | DataOffset = brMesh.ReadInt32(), 756 | WeightDataSize = brMesh.ReadInt32(), 757 | WeightDataOffset = brMesh.ReadInt32(), 758 | 759 | Reserved2 = brMesh.ReadBytes(0x14) 760 | }; 761 | 762 | if (Mesh.VertexTableOffset != 0) 763 | { 764 | Mesh.VertexTables = new Structs.MeshVertexTable[Mesh.VertexTableCount]; 765 | Mesh.VertexDescriptors = new List(); 766 | for (int i = 0; i < Mesh.VertexTableCount; i++) 767 | { 768 | sMesh.Seek(Mesh.VertexTableOffset + (i * 0x20), SeekOrigin.Begin); 769 | Mesh.VertexTables[i] = new Structs.MeshVertexTable 770 | { 771 | DataOffset = brMesh.ReadInt32(), 772 | DataCount = brMesh.ReadInt32(), 773 | BlockSize = brMesh.ReadInt32(), 774 | 775 | DescOffset = brMesh.ReadInt32(), 776 | DescCount = brMesh.ReadInt32(), 777 | 778 | Unknown1 = brMesh.ReadBytes(0xC) 779 | }; 780 | Mesh.VertexTables[i].Descriptors = new Structs.MeshVertexDescriptor[Mesh.VertexTables[i].DescCount]; 781 | sMesh.Seek(Mesh.VertexTables[i].DescOffset, SeekOrigin.Begin); 782 | for (int j = 0; j < Mesh.VertexTables[i].DescCount; j++) 783 | { 784 | Structs.MeshVertexDescriptor desc = new Structs.MeshVertexDescriptor 785 | { 786 | Type = brMesh.ReadInt16(), 787 | Size = brMesh.ReadInt16() 788 | }; 789 | Mesh.VertexDescriptors.Add(desc); 790 | Mesh.VertexTables[i].Descriptors[j] = desc; 791 | } 792 | } 793 | } 794 | 795 | if (Mesh.FaceTableOffset != 0) 796 | { 797 | Mesh.FaceTables = new Structs.MeshFaceTable[Mesh.FaceTableCount]; 798 | for (int i = 0; i < Mesh.FaceTableCount; i++) 799 | { 800 | sMesh.Seek(Mesh.FaceTableOffset + (i * 0x14), SeekOrigin.Begin); 801 | Mesh.FaceTables[i] = new Structs.MeshFaceTable 802 | { 803 | Offset = brMesh.ReadInt32(), 804 | VertCount = brMesh.ReadInt32(), 805 | 806 | Unknown1 = brMesh.ReadBytes(0xC) 807 | }; 808 | 809 | Mesh.FaceTables[i].Vertices = new ushort[Mesh.FaceTables[i].VertCount]; 810 | sMesh.Seek(Mesh.DataOffset + Mesh.FaceTables[i].Offset, SeekOrigin.Begin); 811 | for (int j = 0; j < Mesh.FaceTables[i].VertCount; j++) 812 | Mesh.FaceTables[i].Vertices[j] = brMesh.ReadUInt16(); 813 | } 814 | } 815 | 816 | if (Mesh.WeightDataOffset != 0) 817 | { 818 | sMesh.Seek(Mesh.WeightDataOffset, SeekOrigin.Begin); 819 | Mesh.WeightData = new Structs.MeshWeightData 820 | { 821 | WeightManagerCount = brMesh.ReadInt32(), 822 | WeightManagerOffset = brMesh.ReadInt32(), 823 | 824 | VertexTableIndex = brMesh.ReadInt16(), 825 | Unknown2 = brMesh.ReadInt16(), 826 | 827 | Offset02 = brMesh.ReadInt32() 828 | }; 829 | 830 | Mesh.WeightData.WeightManagers = new Structs.MeshWeightManager[Mesh.WeightData.WeightManagerCount]; 831 | sMesh.Seek(Mesh.WeightData.WeightManagerOffset, SeekOrigin.Begin); 832 | for (int i = 0; i < Mesh.WeightData.WeightManagerCount; i++) 833 | { 834 | Mesh.WeightData.WeightManagers[i] = new Structs.MeshWeightManager 835 | { 836 | Unknown1 = brMesh.ReadInt32(), 837 | Offset = brMesh.ReadInt32(), 838 | Count = brMesh.ReadInt32(), 839 | 840 | Unknown2 = brMesh.ReadBytes(0x11), 841 | LOD = brMesh.ReadByte(), 842 | Unknown3 = brMesh.ReadBytes(0xA) 843 | }; 844 | } 845 | } 846 | 847 | if (Mesh.MorphDataOffset > 0) //has flexes 848 | { 849 | sMesh.Seek(Mesh.MorphDataOffset, SeekOrigin.Begin); 850 | 851 | Mesh.MorphData.MorphDescriptorsCount = brMesh.ReadInt32(); 852 | Mesh.MorphData.MorphDescriptorsOffset = brMesh.ReadInt32(); 853 | Mesh.MorphData.MorphTargetsCount = brMesh.ReadInt32(); 854 | Mesh.MorphData.MorphTargetsOffset = brMesh.ReadInt32(); 855 | 856 | Mesh.MorphData.MorphDescriptors = new Structs.MeshMorphDescriptor[Mesh.MorphData.MorphDescriptorsCount]; 857 | sMesh.Seek(Mesh.MorphData.MorphDescriptorsOffset, SeekOrigin.Begin); 858 | for (int i = 0; i < Mesh.MorphData.MorphDescriptorsCount; i++) 859 | { 860 | Mesh.MorphData.MorphDescriptors[i] = new Structs.MeshMorphDescriptor 861 | { 862 | BufferID = brMesh.ReadInt32(), 863 | 864 | TargetIndex = brMesh.ReadInt32(), 865 | TargetCounts = brMesh.ReadInt32(), 866 | TargetIDOffsets = brMesh.ReadInt32(), 867 | 868 | Unknown1 = brMesh.ReadInt32() 869 | }; 870 | } 871 | 872 | Mesh.MorphData.MorphTargets = new Structs.MeshMorphTarget[Mesh.MorphData.MorphTargetsCount]; 873 | sMesh.Seek(Mesh.MorphData.MorphTargetsOffset, SeekOrigin.Begin); 874 | for (int i = 0; i < Mesh.MorphData.MorphTargetsCount; i++) 875 | { 876 | Mesh.MorphData.MorphTargets[i] = new Structs.MeshMorphTarget 877 | { 878 | BufferOffset = brMesh.ReadInt32(), 879 | VertCount = brMesh.ReadInt32(), 880 | BlockSize = brMesh.ReadInt32(), 881 | 882 | Unknown1 = brMesh.ReadInt16(), 883 | Type = brMesh.ReadInt16() 884 | }; 885 | Mesh.MorphData.MorphTargets[i].Vertices = new Vector3[Mesh.MorphData.MorphTargets.Max(x => x.VertCount)]; 886 | Mesh.MorphData.MorphTargets[i].Normals = new Quaternion[Mesh.MorphData.MorphTargets.Max(x => x.VertCount)]; 887 | } 888 | } 889 | 890 | for (int i = 0; i < Mesh.VertexTableCount; i++) 891 | { 892 | sMesh.Seek(Mesh.DataOffset + Mesh.VertexTables[i].DataOffset, SeekOrigin.Begin); 893 | 894 | Mesh.VertexTables[i].Vertices = new Vector3[Mesh.VertexTables[i].DataCount]; 895 | Mesh.VertexTables[i].Weights = new int[Mesh.VertexTables[i].DataCount]; 896 | Mesh.VertexTables[i].UVPos = new Vector2[Mesh.VertexTables[i].DataCount, 4]; 897 | Mesh.VertexTables[i].VertexColor = new Color[Mesh.VertexTables[i].DataCount]; 898 | Mesh.VertexTables[i].Normals = new Quaternion[Mesh.VertexTables[i].DataCount]; 899 | Mesh.VertexTables[i].WeightValues = new float[Mesh.VertexTables[i].DataCount, 4]; 900 | Mesh.VertexTables[i].WeightIds = new byte[Mesh.VertexTables[i].DataCount, 4]; 901 | 902 | for (int j = 0; j < Mesh.VertexTables[i].DataCount; j++) 903 | { 904 | foreach (Structs.MeshVertexDescriptor desc in Mesh.VertexTables[i].Descriptors) 905 | { 906 | switch (desc.Type) 907 | { 908 | case 0: 909 | Mesh.VertexTables[i].Vertices[j] = new Vector3(brMesh.ReadSingle(), brMesh.ReadSingle(), brMesh.ReadSingle()); 910 | break; 911 | case 3: 912 | Mesh.VertexTables[i].Weights[j] = brMesh.ReadInt32(); 913 | break; 914 | case 5: 915 | case 6: 916 | case 7: 917 | Mesh.VertexTables[i].UVPos[j, desc.Type - 5] = new Vector2(brMesh.ReadSingle(), brMesh.ReadSingle()); 918 | if (desc.Type - 4 > Mesh.VertexTables[i].UVLayerCount) 919 | Mesh.VertexTables[i].UVLayerCount = desc.Type - 4; 920 | break; 921 | case 17: 922 | Mesh.VertexTables[i].VertexColor[j] = Color.FromArgb(brMesh.ReadByte(), brMesh.ReadByte(), brMesh.ReadByte(), brMesh.ReadByte()); 923 | break; 924 | case 28: 925 | Mesh.VertexTables[i].Normals[j] = new Quaternion(brMesh.ReadSByte() / 128f, brMesh.ReadSByte() / 128f, brMesh.ReadSByte() / 128f, brMesh.ReadSByte() /*dummy*/); 926 | break; 927 | case 41: 928 | Mesh.VertexTables[i].WeightValues[j, 0] = brMesh.ReadUInt16() / 65535f; 929 | Mesh.VertexTables[i].WeightValues[j, 1] = brMesh.ReadUInt16() / 65535f; 930 | Mesh.VertexTables[i].WeightValues[j, 2] = brMesh.ReadUInt16() / 65535f; 931 | Mesh.VertexTables[i].WeightValues[j, 3] = brMesh.ReadUInt16() / 65535f; 932 | break; 933 | case 42: 934 | Mesh.VertexTables[i].WeightIds[j, 0] = brMesh.ReadByte(); 935 | Mesh.VertexTables[i].WeightIds[j, 1] = brMesh.ReadByte(); 936 | Mesh.VertexTables[i].WeightIds[j, 2] = brMesh.ReadByte(); 937 | Mesh.VertexTables[i].WeightIds[j, 3] = brMesh.ReadByte(); 938 | break; 939 | default: 940 | sMesh.Seek(desc.Size, SeekOrigin.Current); 941 | break; 942 | } 943 | } 944 | } 945 | } 946 | 947 | for (int i = 0; i < Mesh.MorphData.MorphDescriptorsCount; i++) 948 | { 949 | Mesh.MorphData.MorphDescriptors[i].TargetIDs = new short[Mesh.MorphData.MorphDescriptors[i].TargetCounts]; 950 | sMesh.Seek(Mesh.MorphData.MorphDescriptors[i].TargetIDOffsets, SeekOrigin.Begin); 951 | for (int j = 0; j < Mesh.MorphData.MorphDescriptors[i].TargetCounts; j++) 952 | Mesh.MorphData.MorphDescriptors[i].TargetIDs[j] = brMesh.ReadInt16(); 953 | 954 | Structs.MeshMorphDescriptor desc = Mesh.MorphData.MorphDescriptors[i]; 955 | 956 | sMesh.Seek(Mesh.DataOffset + Mesh.MorphData.MorphTargets[desc.TargetIndex].BufferOffset, SeekOrigin.Begin); 957 | 958 | for (int j = 0; j < Mesh.MorphData.MorphTargets[desc.TargetIndex].VertCount; j++) 959 | { 960 | Mesh.VertexTables[desc.BufferID].Vertices[j] = new Vector3(brMesh.ReadSingle(), brMesh.ReadSingle(), brMesh.ReadSingle()); 961 | Mesh.VertexTables[desc.BufferID].Normals[j] = new Quaternion(brMesh.ReadByte() / 128f, brMesh.ReadByte() / 128f, brMesh.ReadByte() / 128f, 1 /*dummy*/); 962 | sMesh.Seek(Mesh.MorphData.MorphTargets[desc.TargetIndex].BlockSize - 15, SeekOrigin.Current); 963 | } 964 | 965 | for (int j = 1; j < Mesh.MorphData.MorphDescriptors[i].TargetCounts; j++) 966 | { 967 | Mesh.MorphData.MorphTargets[desc.TargetIndex + j].Vertices = new Vector3[Mesh.MorphData.MorphTargets[desc.TargetIndex].VertCount]; 968 | Mesh.MorphData.MorphTargets[desc.TargetIndex + j].Normals = new Quaternion[Mesh.MorphData.MorphTargets[desc.TargetIndex].VertCount]; 969 | 970 | sMesh.Seek(Mesh.DataOffset + Mesh.MorphData.MorphTargets[desc.TargetIndex + j].BufferOffset, SeekOrigin.Begin); 971 | for (int k = 0; k < Mesh.MorphData.MorphTargets[desc.TargetIndex + j].VertCount; k++) 972 | { 973 | Vector3 vert = new Vector3(brMesh.ReadSingle(), brMesh.ReadSingle(), brMesh.ReadSingle()); 974 | brMesh.ReadInt32(); 975 | Quaternion norm = new Quaternion(brMesh.ReadByte() / 128f, brMesh.ReadByte() / 128f, brMesh.ReadByte() / 128f, brMesh.ReadByte() /*dummy*/); 976 | brMesh.ReadInt32(); 977 | brMesh.ReadInt32(); 978 | int index = brMesh.ReadInt32(); 979 | Mesh.MorphData.MorphTargets[desc.TargetIndex + j].Vertices[index] = vert; 980 | Mesh.MorphData.MorphTargets[desc.TargetIndex + j].Normals[index] = norm; 981 | } 982 | } 983 | } 984 | 985 | return Mesh; 986 | } 987 | 988 | public Structs.MapInfo ReadMapInfo(Stream sMap, BinaryReader brMap, bool IsProp) 989 | { 990 | Structs.MapInfo map = new Structs.MapInfo 991 | { 992 | Unknown1 = brMap.ReadInt32(), 993 | Unknown2 = brMap.ReadInt32(), 994 | Unknown3 = brMap.ReadInt32(), 995 | 996 | MeshTableOffset = brMap.ReadInt32(), 997 | MaterialTableOffset = brMap.ReadInt32(), 998 | 999 | Unknown4 = brMap.ReadInt32(), 1000 | MiscPropertiesTable = brMap.ReadInt32(), 1001 | Unknown5 = brMap.ReadInt32(), 1002 | Unknown6 = brMap.ReadInt32(), 1003 | Unknown7 = brMap.ReadInt32(), 1004 | 1005 | PropFileIndexOffset = brMap.ReadInt32(), 1006 | PropFileIndexCount = brMap.ReadInt32(), 1007 | Unknown8 = brMap.ReadInt32(), 1008 | Unknown9 = brMap.ReadInt32(), 1009 | 1010 | TableIndexOffset = brMap.ReadInt32() 1011 | }; 1012 | 1013 | if (map.MaterialTableOffset != 0) 1014 | { 1015 | sMap.Seek(map.MaterialTableOffset, SeekOrigin.Begin); 1016 | map.MaterialHeader = new Structs.MXMDMaterialHeader 1017 | { 1018 | Offset = brMap.ReadInt32(), 1019 | Count = brMap.ReadInt32() 1020 | }; 1021 | 1022 | map.Materials = new Structs.MXMDMaterial[map.MaterialHeader.Count]; 1023 | for (int i = 0; i < map.MaterialHeader.Count; i++) 1024 | { 1025 | sMap.Seek(map.MaterialTableOffset + map.MaterialHeader.Offset + (i * 0x74), SeekOrigin.Begin); 1026 | map.Materials[i] = new Structs.MXMDMaterial 1027 | { 1028 | NameOffset = brMap.ReadInt32(), 1029 | Unknown1 = brMap.ReadBytes(0x70) 1030 | }; 1031 | 1032 | sMap.Seek(map.MaterialTableOffset + map.Materials[i].NameOffset, SeekOrigin.Begin); 1033 | map.Materials[i].Name = ReadNullTerminatedString(brMap); 1034 | } 1035 | } 1036 | 1037 | if (map.MeshTableOffset != 0) 1038 | { 1039 | sMap.Seek(map.MeshTableOffset + 0x1C, SeekOrigin.Begin); 1040 | map.MeshTableDataOffset = brMap.ReadInt32(); 1041 | map.MeshTableDataCount = brMap.ReadInt32(); 1042 | if (!IsProp && map.MeshTableDataCount % 2 == 0) 1043 | map.MeshTableDataCount /= 2; 1044 | 1045 | map.MeshTables = new Structs.MapInfoMeshTable[map.MeshTableDataCount]; 1046 | for (int i = 0; i < map.MeshTableDataCount; i++) 1047 | { 1048 | sMap.Seek(map.MeshTableOffset + map.MeshTableDataOffset + (i * 0x44), SeekOrigin.Begin); 1049 | map.MeshTables[i].MeshOffset = brMap.ReadInt32(); 1050 | map.MeshTables[i].MeshCount = brMap.ReadInt32(); 1051 | 1052 | map.MeshTables[i].Descriptors = new Structs.MXMDMeshDescriptor[map.MeshTables[i].MeshCount]; 1053 | sMap.Seek(map.MeshTableOffset + map.MeshTables[i].MeshOffset, SeekOrigin.Begin); 1054 | for (int j = 0; j < map.MeshTables[i].MeshCount; j++) 1055 | { 1056 | map.MeshTables[i].Descriptors[j] = new Structs.MXMDMeshDescriptor 1057 | { 1058 | ID = brMap.ReadInt32(), 1059 | 1060 | Descriptor = brMap.ReadInt32(), 1061 | 1062 | VertTableIndex = brMap.ReadInt16(), 1063 | FaceTableIndex = brMap.ReadInt16(), 1064 | 1065 | Unknown1 = brMap.ReadInt16(), 1066 | MaterialID = brMap.ReadInt16(), 1067 | Unknown2 = brMap.ReadBytes(0xC), 1068 | Unknown3 = brMap.ReadInt16(), 1069 | 1070 | LOD = brMap.ReadInt16(), 1071 | Unknown4 = brMap.ReadInt32(), 1072 | 1073 | Unknown5 = brMap.ReadBytes(0xC) 1074 | }; 1075 | } 1076 | } 1077 | 1078 | if (map.TableIndexOffset != 0 && map.PropFileIndexOffset == 0) 1079 | { 1080 | sMap.Seek(map.TableIndexOffset + 0x8, SeekOrigin.Begin); 1081 | map.MeshFileLookupOffset = brMap.ReadInt32(); 1082 | map.MeshFileLookupCount = brMap.ReadInt32(); 1083 | 1084 | sMap.Seek(map.TableIndexOffset + map.MeshFileLookupOffset, SeekOrigin.Begin); 1085 | map.MeshFileLookup = new short[map.MeshFileLookupCount]; 1086 | for (int i = 0; i < map.MeshFileLookupCount; i++) 1087 | { 1088 | map.MeshFileLookup[i] = brMap.ReadInt16(); 1089 | } 1090 | } 1091 | } 1092 | 1093 | if (map.MiscPropertiesTable != 0 && IsProp) 1094 | { 1095 | sMap.Seek(map.MiscPropertiesTable, SeekOrigin.Begin); 1096 | int IThinkItsACount = brMap.ReadInt32(); 1097 | if (IThinkItsACount == 0) 1098 | { 1099 | map.PropLODsDataCount = brMap.ReadInt32(); 1100 | map.PropLODsDataOffset = brMap.ReadInt32(); 1101 | 1102 | map.PropLODs = new List(); 1103 | map.PropIDs = new List(); 1104 | sMap.Seek(map.MiscPropertiesTable + map.PropLODsDataOffset, SeekOrigin.Begin); 1105 | for (int i = 0; i < map.PropLODsDataCount; i++) 1106 | { 1107 | int Unknown = brMap.ReadInt32(); 1108 | int NumberOfProps = brMap.ReadInt32(); 1109 | for (int j = 0; j < NumberOfProps; j++) 1110 | { 1111 | map.PropLODs.Add(j); 1112 | map.PropIDs.Add(i); 1113 | } 1114 | } 1115 | } 1116 | 1117 | sMap.Seek(map.MiscPropertiesTable + 0x14, SeekOrigin.Begin); 1118 | map.PropPosTableCount = brMap.ReadInt32(); 1119 | map.PropPosTableOffset = brMap.ReadInt32(); 1120 | 1121 | if (map.PropPosTableOffset != 0) 1122 | { 1123 | map.PropPositions = new List(); 1124 | sMap.Seek(map.MiscPropertiesTable + map.PropPosTableOffset, SeekOrigin.Begin); 1125 | for (int i = 0; i < map.PropPosTableCount; i++) 1126 | { 1127 | Structs.MapInfoPropPosition PropPos = new Structs.MapInfoPropPosition 1128 | { 1129 | Matrix = new Matrix4x4(brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle(), brMap.ReadSingle()), 1130 | 1131 | Unknown1 = brMap.ReadBytes(0x1C), 1132 | 1133 | PropID = brMap.ReadInt32(), 1134 | 1135 | Unknown2 = brMap.ReadBytes(0x10) 1136 | }; 1137 | PropPos.Rotation = new Quaternion(PropPos.Matrix.M32, PropPos.Matrix.M13, -PropPos.Matrix.M21, 1); 1138 | PropPos.Position = new Vector3(PropPos.Matrix.M41, PropPos.Matrix.M42, -PropPos.Matrix.M43); 1139 | map.PropPositions.Add(PropPos); 1140 | } 1141 | } 1142 | } 1143 | 1144 | if (map.PropFileIndexOffset != 0) 1145 | { 1146 | map.PropFileLookup = new int[map.PropFileIndexCount]; 1147 | sMap.Seek(map.PropFileIndexOffset, SeekOrigin.Begin); 1148 | for (int i = 0; i < map.PropFileIndexCount; i++) 1149 | map.PropFileLookup[i] = brMap.ReadInt32(); 1150 | } 1151 | 1152 | return map; 1153 | } 1154 | 1155 | public Structs.SeamworkPropPosition ReadPropPositions(Stream sPos, BinaryReader brPos) 1156 | { 1157 | Structs.SeamworkPropPosition pos = new Structs.SeamworkPropPosition 1158 | { 1159 | TableCount = brPos.ReadInt32(), 1160 | TableOffset = brPos.ReadInt32(), 1161 | 1162 | Unknown1 = brPos.ReadInt32(), 1163 | Unknown2 = brPos.ReadInt32(), 1164 | 1165 | UnknownTable1Count = brPos.ReadInt32(), 1166 | UnknownTable1Offset = brPos.ReadInt32(), 1167 | 1168 | Unknown3 = brPos.ReadBytes(0x10), 1169 | 1170 | UnknownTable2Count = brPos.ReadInt32(), 1171 | UnknownTable2Offset = brPos.ReadInt32(), 1172 | UnknownTable3Count = brPos.ReadInt32(), 1173 | UnknownTable3Offset = brPos.ReadInt32(), 1174 | 1175 | Unknown7 = brPos.ReadBytes(0x28) 1176 | }; 1177 | 1178 | if (pos.TableOffset != 0) 1179 | { 1180 | pos.Positions = new List(); 1181 | sPos.Seek(pos.TableOffset, SeekOrigin.Begin); 1182 | for (int i = 0; i < pos.TableCount; i++) 1183 | { 1184 | Structs.MapInfoPropPosition PropPos = new Structs.MapInfoPropPosition 1185 | { 1186 | Matrix = new Matrix4x4(brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle(), brPos.ReadSingle()), 1187 | 1188 | Unknown1 = brPos.ReadBytes(0x1C), 1189 | 1190 | PropID = brPos.ReadInt32(), 1191 | 1192 | Unknown2 = brPos.ReadBytes(0x10) 1193 | }; 1194 | PropPos.Rotation = new Quaternion(PropPos.Matrix.M32, PropPos.Matrix.M13, -PropPos.Matrix.M21, 1); 1195 | PropPos.Position = new Vector3(PropPos.Matrix.M41, PropPos.Matrix.M42, -PropPos.Matrix.M43); 1196 | pos.Positions.Add(PropPos); 1197 | } 1198 | } 1199 | 1200 | return pos; 1201 | } 1202 | 1203 | public Structs.NVMS ReadNVMS(Stream sNVMS, BinaryReader brNVMS) 1204 | { 1205 | App.PushLog("Parsing NVMS..."); 1206 | sNVMS.Seek(0, SeekOrigin.Begin); 1207 | int MXMDMagic = brNVMS.ReadInt32(); 1208 | if (MXMDMagic != 0x534D564E) 1209 | { 1210 | App.PushLog("NVMS is corrupt (or wrong endianness)!"); 1211 | return new Structs.NVMS { Version = Int32.MaxValue }; 1212 | } 1213 | 1214 | Structs.NVMS NVMS = new Structs.NVMS 1215 | { 1216 | Version = brNVMS.ReadInt32(), 1217 | 1218 | NVDATableOffset = brNVMS.ReadInt32(), 1219 | NVDATableCount = brNVMS.ReadInt32(), 1220 | 1221 | Table2Offset = brNVMS.ReadInt32(), 1222 | Table2Count = brNVMS.ReadInt32(), 1223 | Table3Offset = brNVMS.ReadInt32(), 1224 | Table3Count = brNVMS.ReadInt32(), 1225 | 1226 | Reserved1 = brNVMS.ReadBytes(0x20) 1227 | }; 1228 | 1229 | sNVMS.Seek(NVMS.NVDATableOffset, SeekOrigin.Begin); 1230 | NVMS.NVDAPointers = new Structs.NVMSNVDAPointers[NVMS.NVDATableCount]; 1231 | for (int i = 0; i < NVMS.NVDATableCount; i++) 1232 | { 1233 | NVMS.NVDAPointers[i] = new Structs.NVMSNVDAPointers 1234 | { 1235 | Unknown1 = brNVMS.ReadInt32(), 1236 | XBC1Offset = brNVMS.ReadInt32(), 1237 | XBC1Size = brNVMS.ReadInt32(), 1238 | SecondFileOffset = brNVMS.ReadInt32() 1239 | }; 1240 | } 1241 | 1242 | return NVMS; 1243 | } 1244 | 1245 | public void ModelToASCII(Structs.Mesh[] Meshes, Structs.MXMD MXMD, Structs.SKEL SKEL, Structs.MapInfo MapInfo, string FilenameOverride = "") 1246 | { 1247 | string filename = App.CurFileNameNoExt; 1248 | if (!string.IsNullOrWhiteSpace(FilenameOverride)) 1249 | filename += FilenameOverride; 1250 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1251 | filename += $"_mesh{MapInfo.MeshFileLookup.Min()}-{MapInfo.MeshFileLookup.Max()}"; 1252 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1253 | filename += $"_props_mesh{MapInfo.PropFileLookup.Min()}-{MapInfo.PropFileLookup.Max()}"; 1254 | 1255 | //begin ascii 1256 | StreamWriter asciiWriter = new StreamWriter($@"{App.CurOutputPath}\{filename}.ascii"); 1257 | 1258 | //bone time 1259 | Dictionary NodesIdsNames = new Dictionary(); 1260 | for (int r = 0; r < MXMD.ModelStruct.Nodes.BoneCount; r++) 1261 | { 1262 | if (!MXMD.ModelStruct.Nodes.Nodes[r].Name.StartsWith("eff")) 1263 | NodesIdsNames.Add(r, MXMD.ModelStruct.Nodes.Nodes[r].Name); 1264 | else 1265 | NodesIdsNames.Add(r, SKEL.Nodes[r].Name); //stupidest hack in the world but it WORKS 1266 | } 1267 | 1268 | if (SKEL.Unknown1 != Int32.MaxValue) 1269 | { 1270 | asciiWriter.WriteLine(SKEL.TOCItems[2].Count); 1271 | for (int i = 0; i < SKEL.TOCItems[2].Count; i++) 1272 | { 1273 | asciiWriter.WriteLine(SKEL.Nodes[i].Name); 1274 | asciiWriter.WriteLine(SKEL.Parents[i]); 1275 | asciiWriter.Write(SKEL.Transforms[i].RealPosition.X.ToString("F6") + " "); 1276 | asciiWriter.Write(SKEL.Transforms[i].RealPosition.Y.ToString("F6") + " "); 1277 | asciiWriter.Write(SKEL.Transforms[i].RealPosition.Z.ToString("F6")); 1278 | asciiWriter.WriteLine(); 1279 | } 1280 | } 1281 | else 1282 | asciiWriter.WriteLine(0); 1283 | 1284 | List[] ValidMeshes = VerifyMeshes(Meshes[0], MXMD); 1285 | int ActualMeshCount = 0; 1286 | 1287 | for (int i = 0; i < MXMD.ModelStruct.MeshesCount; i++) 1288 | for (int j = 1; j < ValidMeshes[i].Count; j++) 1289 | if ((MXMD.Version == Int32.MaxValue ? default(Structs.MXMDMeshDescriptor) : MXMD.ModelStruct.Meshes[i].Descriptors[ValidMeshes[i][j]]).LOD == (MapInfo.Unknown1 != Int32.MaxValue ? App.LOD : ValidMeshes[i][0]) || App.LOD == -1) 1290 | ActualMeshCount++; 1291 | 1292 | Structs.Mesh Mesh = Meshes[0]; 1293 | asciiWriter.WriteLine(ActualMeshCount); 1294 | for (int i = 0; i < MXMD.ModelStruct.MeshesCount; i++) 1295 | { 1296 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1297 | Mesh = Meshes[MapInfo.MeshFileLookup[i]]; 1298 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1299 | Mesh = Meshes[MapInfo.PropFileLookup[MapInfo.PropPositions[i].PropID]]; 1300 | 1301 | int lastMeshIdIdenticalCount = 0; 1302 | bool lastMeshIdIdentical = false; 1303 | for (int j = 1; j < ValidMeshes[i].Count; j++) 1304 | { 1305 | lastMeshIdIdentical = j == 1 ? false : ValidMeshes[i][j - 1] == ValidMeshes[i][j]; 1306 | 1307 | if (lastMeshIdIdentical) 1308 | lastMeshIdIdenticalCount++; 1309 | else 1310 | lastMeshIdIdenticalCount = 0; 1311 | 1312 | int descId = ValidMeshes[i][j]; 1313 | Structs.MXMDMeshDescriptor desc = MXMD.Version == Int32.MaxValue ? default(Structs.MXMDMeshDescriptor) : MXMD.ModelStruct.Meshes[i].Descriptors[descId]; 1314 | if (desc.LOD == (MapInfo.Unknown1 != Int32.MaxValue ? App.LOD : ValidMeshes[i][0]) || App.LOD == -1) 1315 | { 1316 | Structs.MeshVertexTable vertTbl = Mesh.VertexTables[desc.VertTableIndex]; 1317 | Structs.MeshFaceTable faceTbl = Mesh.FaceTables[desc.FaceTableIndex]; 1318 | Structs.MeshVertexTable weightTbl = Mesh.VertexTables.Last(); 1319 | Structs.MeshMorphDescriptor morphDesc = new Structs.MeshMorphDescriptor(); 1320 | if (App.ExportFlexes && Mesh.MorphDataOffset > 0) 1321 | morphDesc = Mesh.MorphData.MorphDescriptors.Where(x => x.BufferID == desc.VertTableIndex).FirstOrDefault(); 1322 | 1323 | int highestVertId = 0; 1324 | int lowestVertId = vertTbl.DataCount; 1325 | for (int k = 0; k < faceTbl.VertCount; k++) 1326 | { 1327 | if (faceTbl.Vertices[k] > highestVertId) 1328 | highestVertId = faceTbl.Vertices[k]; 1329 | if (faceTbl.Vertices[k] < lowestVertId) 1330 | lowestVertId = faceTbl.Vertices[k]; 1331 | } 1332 | 1333 | string meshName = $"mesh{i}"; 1334 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1335 | meshName = $"mesh{MapInfo.MeshFileLookup[i]}"; 1336 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1337 | meshName = $"prop{i}mesh{MapInfo.PropFileLookup[MapInfo.PropPositions[i].PropID]}"; 1338 | meshName += $"desc{descId}_{(desc.LOD != App.LOD ? $"LOD{desc.LOD}_" : "")}"; 1339 | if (lastMeshIdIdentical) 1340 | meshName += $"flex_{MXMD.ModelStruct.MorphControls.Controls[lastMeshIdIdenticalCount - 1].Name}"; 1341 | else 1342 | { 1343 | if (MXMD.Version != Int32.MaxValue) 1344 | meshName += MXMD.Materials[desc.MaterialID].Name; 1345 | else 1346 | meshName += "NO_MATERIALS"; 1347 | } 1348 | 1349 | asciiWriter.WriteLine(meshName); //mesh name 1350 | asciiWriter.WriteLine(vertTbl.UVLayerCount); 1351 | asciiWriter.WriteLine(0); //texture count, always 0 for us, though maybe I should change that? 1352 | asciiWriter.WriteLine(highestVertId - lowestVertId + 1); //vertex count 1353 | for (int vrtIndex = lowestVertId; vrtIndex <= highestVertId; vrtIndex++) 1354 | { 1355 | //vertex position 1356 | Vector3 vertexPos = vertTbl.Vertices[vrtIndex]; 1357 | if (lastMeshIdIdentical) 1358 | vertexPos += Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + lastMeshIdIdenticalCount + 1].Vertices[vrtIndex]; 1359 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1360 | vertexPos = Vector3.Transform(vertexPos, MapInfo.PropPositions[i].Matrix); 1361 | asciiWriter.Write($"{vertexPos.X:F6} "); 1362 | asciiWriter.Write($"{vertexPos.Y:F6} "); 1363 | asciiWriter.Write($"{vertexPos.Z:F6}"); 1364 | asciiWriter.WriteLine(); 1365 | 1366 | //vertex normal 1367 | Quaternion normalPos = vertTbl.Normals[vrtIndex]; 1368 | if (lastMeshIdIdentical) 1369 | normalPos += Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + lastMeshIdIdenticalCount + 1].Normals[vrtIndex]; 1370 | asciiWriter.Write($"{normalPos.X:F6} "); 1371 | asciiWriter.Write($"{normalPos.Y:F6} "); 1372 | asciiWriter.Write($"{normalPos.Z:F6}"); 1373 | asciiWriter.WriteLine(); 1374 | 1375 | //vertex color 1376 | asciiWriter.Write(vertTbl.VertexColor[vrtIndex].R + " "); 1377 | asciiWriter.Write(vertTbl.VertexColor[vrtIndex].G + " "); 1378 | asciiWriter.Write(vertTbl.VertexColor[vrtIndex].B + " "); 1379 | asciiWriter.Write(vertTbl.VertexColor[vrtIndex].A); 1380 | asciiWriter.WriteLine(); 1381 | 1382 | //uv coords 1383 | for (int curUVLayer = 0; curUVLayer < vertTbl.UVLayerCount; curUVLayer++) 1384 | asciiWriter.WriteLine(vertTbl.UVPos[vrtIndex, curUVLayer].X.ToString("F6") + " " + vertTbl.UVPos[vrtIndex, curUVLayer].Y.ToString("F6")); 1385 | 1386 | if (SKEL.Unknown1 != Int32.MaxValue) 1387 | { 1388 | //weight ids 1389 | asciiWriter.Write(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[vrtIndex], 0]]] + " "); 1390 | asciiWriter.Write(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[vrtIndex], 1]]] + " "); 1391 | asciiWriter.Write(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[vrtIndex], 2]]] + " "); 1392 | asciiWriter.Write(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[vrtIndex], 3]]]); 1393 | asciiWriter.WriteLine(); 1394 | 1395 | //weight values 1396 | asciiWriter.Write(weightTbl.WeightValues[vertTbl.Weights[vrtIndex], 0].ToString("F6") + " "); 1397 | asciiWriter.Write(weightTbl.WeightValues[vertTbl.Weights[vrtIndex], 1].ToString("F6") + " "); 1398 | asciiWriter.Write(weightTbl.WeightValues[vertTbl.Weights[vrtIndex], 2].ToString("F6") + " "); 1399 | asciiWriter.Write(weightTbl.WeightValues[vertTbl.Weights[vrtIndex], 3].ToString("F6")); 1400 | asciiWriter.WriteLine(); 1401 | } 1402 | } 1403 | 1404 | //face count 1405 | asciiWriter.WriteLine(faceTbl.VertCount / 3); 1406 | for (int k = 0; k < faceTbl.VertCount; k += 3) 1407 | { 1408 | int faceVertex2 = faceTbl.Vertices[k] - lowestVertId; 1409 | int faceVertex1 = faceTbl.Vertices[k + 1] - lowestVertId; 1410 | int faceVertex0 = faceTbl.Vertices[k + 2] - lowestVertId; 1411 | //face vertex ids 1412 | asciiWriter.WriteLine($"{faceVertex0} {faceVertex1} {faceVertex2}"); 1413 | } 1414 | } 1415 | } 1416 | } 1417 | 1418 | App.PushLog("Writing .ascii file..."); 1419 | asciiWriter.Flush(); 1420 | asciiWriter.Dispose(); 1421 | } 1422 | 1423 | public void ModelToGLTF(Structs.Mesh[] Meshes, Structs.MXMD MXMD, Structs.SKEL SKEL, Structs.MapInfo MapInfo, string FilenameOverride = "") 1424 | { 1425 | string filename = App.CurFileNameNoExt; 1426 | if (!string.IsNullOrWhiteSpace(FilenameOverride)) 1427 | filename += $"_{FilenameOverride}"; 1428 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1429 | filename += $"_mesh{MapInfo.MeshFileLookup.Min()}-{MapInfo.MeshFileLookup.Max()}"; 1430 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1431 | filename += $"_props_mesh{MapInfo.PropFileLookup.Min()}-{MapInfo.PropFileLookup.Max()}"; 1432 | 1433 | SceneBuilder scene = new SceneBuilder(filename); 1434 | 1435 | List[] ValidMeshes = VerifyMeshes(Meshes[0], MXMD); 1436 | List glTFMaterials = new List(); 1437 | 1438 | for (int i = 0; i < MXMD.Materials.Length; i++) 1439 | { 1440 | MaterialBuilder material = new MaterialBuilder(MXMD.Materials[i].Name) 1441 | .WithMetallicRoughnessShader() 1442 | .WithChannelParam(KnownChannel.BaseColor, new Vector4(1, 1, 1, 1)); 1443 | glTFMaterials.Add(material); 1444 | } 1445 | 1446 | NodeBuilder rootNode = new NodeBuilder("PissIdiot"); 1447 | List bones = new List(); 1448 | 1449 | Dictionary NodesIdsNames = new Dictionary(); 1450 | for (int r = 0; r < MXMD.ModelStruct.Nodes.BoneCount; r++) 1451 | { 1452 | if (!MXMD.ModelStruct.Nodes.Nodes[r].Name.StartsWith("eff")) 1453 | NodesIdsNames.Add(r, MXMD.ModelStruct.Nodes.Nodes[r].Name); 1454 | else 1455 | NodesIdsNames.Add(r, SKEL.Nodes[r].Name); //stupidest hack in the world but it WORKS 1456 | } 1457 | 1458 | if (SKEL.Unknown1 != Int32.MaxValue) 1459 | { 1460 | NodeBuilder node = null; 1461 | for (int i = 0; i < SKEL.TOCItems[2].Count; i++) 1462 | { 1463 | Vector3 bonePos = new Vector3(SKEL.Transforms[i].RealPosition.X, SKEL.Transforms[i].RealPosition.Y, SKEL.Transforms[i].RealPosition.Z); 1464 | 1465 | //these are actually quaternions not eulers 1466 | //Quaternion boneRot = SKEL.Transforms[i].RealRotation; 1467 | Quaternion boneRot = SKEL.Transforms[i].Rotation; 1468 | 1469 | Vector3 boneScale = new Vector3(SKEL.Transforms[i].Scale.X, SKEL.Transforms[i].Scale.Y, SKEL.Transforms[i].Scale.Z); 1470 | 1471 | if (node != null) 1472 | bonePos -= SKEL.Transforms[SKEL.Parents[i]].RealPosition; 1473 | 1474 | if (node == null) 1475 | node = rootNode.CreateNode(SKEL.Nodes[i].Name).WithLocalTranslation(bonePos).WithLocalScale(boneScale); 1476 | else 1477 | node = bones[SKEL.Parents[i]].CreateNode(SKEL.Nodes[i].Name).WithLocalTranslation(bonePos).WithLocalScale(boneScale); 1478 | bones.Add(node); 1479 | } 1480 | } 1481 | 1482 | Structs.Mesh Mesh = Meshes[0]; 1483 | Structs.MeshVertexTable weightTbl = Mesh.VertexTables.Last(); 1484 | for (int i = 0; i < MXMD.ModelStruct.MeshesCount; i++) 1485 | { 1486 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1487 | Mesh = Meshes[MapInfo.MeshFileLookup[i]]; 1488 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1489 | Mesh = Meshes[MapInfo.PropFileLookup[MapInfo.PropPositions[MXMD.ModelStruct.Meshes[i].Unknown1].PropID]]; 1490 | 1491 | for (int j = 1; j < ValidMeshes[i].Count; j++) 1492 | { 1493 | int descId = ValidMeshes[i][j]; 1494 | Structs.MXMDMeshDescriptor desc = MXMD.Version == Int32.MaxValue ? default(Structs.MXMDMeshDescriptor) : MXMD.ModelStruct.Meshes[i].Descriptors[descId]; 1495 | 1496 | Structs.MeshVertexTable vertTbl = Mesh.VertexTables[desc.VertTableIndex]; 1497 | Structs.MeshFaceTable faceTbl = Mesh.FaceTables[desc.FaceTableIndex]; 1498 | 1499 | Structs.MeshMorphDescriptor morphDesc = new Structs.MeshMorphDescriptor(); 1500 | if (App.ExportFlexes && Mesh.MorphDataOffset > 0) 1501 | morphDesc = Mesh.MorphData.MorphDescriptors.Where(x => x.BufferID == desc.VertTableIndex).FirstOrDefault(); 1502 | 1503 | string meshName = $"mesh{i}"; 1504 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.MeshFileLookupOffset != 0) 1505 | meshName = $"mesh{MapInfo.MeshFileLookup[i]}"; 1506 | else if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1507 | meshName = $"prop{i}mesh{MapInfo.PropFileLookup[MapInfo.PropPositions[i].PropID]}"; 1508 | meshName += $"desc{descId}{(desc.LOD != App.LOD ? $"_LOD{desc.LOD}" : "")}"; 1509 | 1510 | MeshBuilder meshBuilder = new MeshBuilder(meshName); 1511 | PrimitiveBuilder meshPrim = meshBuilder.UsePrimitive((MXMD.Version == Int32.MaxValue ? new MaterialBuilder("NO_MATERIALS") : glTFMaterials[desc.MaterialID])); 1512 | 1513 | for (int k = 0; k < faceTbl.VertCount; k += 3) 1514 | { 1515 | Vector3 vert0 = vertTbl.Vertices[faceTbl.Vertices[k ]]; 1516 | Vector3 vert1 = vertTbl.Vertices[faceTbl.Vertices[k + 1]]; 1517 | Vector3 vert2 = vertTbl.Vertices[faceTbl.Vertices[k + 2]]; 1518 | Vector3 norm0 = new Vector3(vertTbl.Normals[faceTbl.Vertices[k ]].X, vertTbl.Normals[faceTbl.Vertices[k ]].Y, vertTbl.Normals[faceTbl.Vertices[k ]].Z); 1519 | Vector3 norm1 = new Vector3(vertTbl.Normals[faceTbl.Vertices[k + 1]].X, vertTbl.Normals[faceTbl.Vertices[k + 1]].Y, vertTbl.Normals[faceTbl.Vertices[k + 1]].Z); 1520 | Vector3 norm2 = new Vector3(vertTbl.Normals[faceTbl.Vertices[k + 2]].X, vertTbl.Normals[faceTbl.Vertices[k + 2]].Y, vertTbl.Normals[faceTbl.Vertices[k + 2]].Z); 1521 | 1522 | VertexPositionNormal tri0 = new VertexPositionNormal(vert0, norm0); 1523 | VertexPositionNormal tri1 = new VertexPositionNormal(vert1, norm1); 1524 | VertexPositionNormal tri2 = new VertexPositionNormal(vert2, norm2); 1525 | 1526 | int UVs = vertTbl.UVLayerCount; 1527 | VertexColor1Texture2 uv0 = new VertexColor1Texture2(ColorToVector4(vertTbl.VertexColor[faceTbl.Vertices[k ]]), UVs >= 1 ? vertTbl.UVPos[faceTbl.Vertices[k ], 0] : Vector2.Zero, UVs >= 2 ? vertTbl.UVPos[faceTbl.Vertices[k ], 1] : Vector2.Zero); 1528 | VertexColor1Texture2 uv1 = new VertexColor1Texture2(ColorToVector4(vertTbl.VertexColor[faceTbl.Vertices[k + 1]]), UVs >= 1 ? vertTbl.UVPos[faceTbl.Vertices[k + 1], 0] : Vector2.Zero, UVs >= 2 ? vertTbl.UVPos[faceTbl.Vertices[k + 1], 1] : Vector2.Zero); 1529 | VertexColor1Texture2 uv2 = new VertexColor1Texture2(ColorToVector4(vertTbl.VertexColor[faceTbl.Vertices[k + 2]]), UVs >= 1 ? vertTbl.UVPos[faceTbl.Vertices[k + 2], 0] : Vector2.Zero, UVs >= 2 ? vertTbl.UVPos[faceTbl.Vertices[k + 2], 1] : Vector2.Zero); 1530 | 1531 | Vector4 idx0 = new Vector4(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k ]], 0]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k ]], 1]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k ]], 2]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k ]], 3]]]); 1532 | Vector4 idx1 = new Vector4(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 1]], 0]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 1]], 1]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 1]], 2]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 1]], 3]]]); 1533 | Vector4 idx2 = new Vector4(SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 2]], 0]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 2]], 1]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 2]], 2]]], SKEL.NodeNames[NodesIdsNames[weightTbl.WeightIds[vertTbl.Weights[faceTbl.Vertices[k + 2]], 3]]]); 1534 | 1535 | Vector4 wgt0 = new Vector4(weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k ]], 0], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k ]], 1], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k ]], 2], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k ]], 3]); 1536 | Vector4 wgt1 = new Vector4(weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 1]], 0], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 1]], 1], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 1]], 2], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 1]], 3]); 1537 | Vector4 wgt2 = new Vector4(weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 2]], 0], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 2]], 1], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 2]], 2], weightTbl.WeightValues[vertTbl.Weights[faceTbl.Vertices[k + 2]], 3]); 1538 | 1539 | if (wgt0 == Vector4.Zero) 1540 | wgt0 = new Vector4(1f, 0f, 0f, 0f); 1541 | if (wgt1 == Vector4.Zero) 1542 | wgt1 = new Vector4(1f, 0f, 0f, 0f); 1543 | if (wgt2 == Vector4.Zero) 1544 | wgt2 = new Vector4(1f, 0f, 0f, 0f); 1545 | 1546 | VertexJoints4 bone0 = new VertexJoints4(new SparseWeight8(idx0, wgt0)); 1547 | VertexJoints4 bone1 = new VertexJoints4(new SparseWeight8(idx1, wgt1)); 1548 | VertexJoints4 bone2 = new VertexJoints4(new SparseWeight8(idx2, wgt2)); 1549 | 1550 | if (SKEL.Unknown1 != Int32.MaxValue) 1551 | meshPrim.AddTriangle(new GLTFVert(tri0, uv0, bone0), new GLTFVert(tri1, uv1, bone1), new GLTFVert(tri2, uv2, bone2)); 1552 | else 1553 | meshPrim.AddTriangle(new GLTFVert(tri0, uv0, new VertexJoints4((0, .25f))), new GLTFVert(tri1, uv1, new VertexJoints4((0, .25f))), new GLTFVert(tri2, uv2, new VertexJoints4((0, .25f)))); 1554 | 1555 | if (App.ExportFlexes && morphDesc.TargetCounts > 0) 1556 | { 1557 | for (int l = 0; l < morphDesc.TargetCounts; l++) 1558 | { 1559 | int morphId = morphDesc.TargetIDs[l]; 1560 | 1561 | var morphBuilder = meshBuilder.UseMorphTarget(morphId); 1562 | 1563 | Vector3 vertMorph0 = Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Vertices[faceTbl.Vertices[k]]; 1564 | Vector3 vertMorph1 = Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Vertices[faceTbl.Vertices[k + 1]]; 1565 | Vector3 vertMorph2 = Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Vertices[faceTbl.Vertices[k + 2]]; 1566 | 1567 | Vector3 normMorph0 = new Vector3(Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k]].X, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k]].Y, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k]].Z); 1568 | Vector3 normMorph1 = new Vector3(Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 1]].X, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 1]].Y, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 1]].Z); 1569 | Vector3 normMorph2 = new Vector3(Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 2]].X, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 2]].Y, Mesh.MorphData.MorphTargets[morphDesc.TargetIndex + morphId].Normals[faceTbl.Vertices[k + 2]].Z); 1570 | 1571 | morphBuilder.SetVertexDelta(vert0, new VertexGeometryDelta(vertMorph0, normMorph0, Vector3.Zero)); 1572 | morphBuilder.SetVertexDelta(vert1, new VertexGeometryDelta(vertMorph1, normMorph1, Vector3.Zero)); 1573 | morphBuilder.SetVertexDelta(vert2, new VertexGeometryDelta(vertMorph2, normMorph2, Vector3.Zero)); 1574 | } 1575 | } 1576 | } 1577 | 1578 | if (SKEL.Unknown1 != Int32.MaxValue) 1579 | { 1580 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1581 | scene.AddSkinnedMesh(meshBuilder, MapInfo.PropPositions[MXMD.ModelStruct.Meshes[i].Unknown1].Matrix, bones.ToArray()); 1582 | else 1583 | scene.AddSkinnedMesh(meshBuilder, rootNode.WorldMatrix, bones.ToArray()); 1584 | } 1585 | else 1586 | { 1587 | if (MapInfo.Unknown1 != Int32.MaxValue && MapInfo.PropFileIndexOffset != 0) 1588 | scene.AddMesh(meshBuilder, MapInfo.PropPositions[MXMD.ModelStruct.Meshes[i].Unknown1].Matrix); 1589 | else 1590 | scene.AddMesh(meshBuilder, Matrix4x4.Identity); 1591 | } 1592 | } 1593 | } 1594 | 1595 | App.PushLog($"Writing {filename}.glb..."); 1596 | scene.ToSchema2().SaveGLB($@"{App.CurOutputPath}\{filename}.glb"); 1597 | } 1598 | 1599 | public void ReadTextures(Structs.MSRD MSRD, string texturesFolderPath, List LBIMs = null) 1600 | { 1601 | App.PushLog("Reading textures..."); 1602 | 1603 | if (!Directory.Exists(texturesFolderPath)) 1604 | Directory.CreateDirectory(texturesFolderPath); 1605 | 1606 | if (LBIMs == null) 1607 | LBIMs = new List(); 1608 | 1609 | for (int i = 0; i < MSRD.DataItemsCount; i++) 1610 | { 1611 | Structs.MSRDDataItem dataItem = MSRD.DataItems[i]; 1612 | int Offset = dataItem.Offset; 1613 | int Size = dataItem.Size; 1614 | 1615 | switch (dataItem.Type) 1616 | { 1617 | case Structs.MSRDDataItemTypes.Texture: 1618 | Stream sStream = MSRD.TOC[1].Data; 1619 | BinaryReader brTexture = new BinaryReader(sStream); 1620 | Structs.LBIM TextureLBIM = ReadLBIM(sStream, brTexture, Offset, Size, dataItem); 1621 | LBIMs.Add(TextureLBIM); 1622 | break; 1623 | case Structs.MSRDDataItemTypes.CachedTextures: 1624 | BinaryReader meshData = new BinaryReader(MSRD.TOC[0].Data); 1625 | for (int j = 0; j < MSRD.TextureInfo.Length; j++) 1626 | { 1627 | Structs.LBIM CacheLBIM = ReadLBIM(MSRD.TOC[0].Data, meshData, Offset + MSRD.TextureInfo[j].Offset, MSRD.TextureInfo[j].Size); 1628 | LBIMs.Add(CacheLBIM); 1629 | } 1630 | break; 1631 | } 1632 | } 1633 | 1634 | for (int i = 0; i < LBIMs.Count; i++) 1635 | { 1636 | Structs.LBIM LBIM = LBIMs[i]; 1637 | MemoryStream TextureData = LBIM.Data; 1638 | 1639 | if (LBIM.DataItem.TOCIndex != 0 && LBIM.DataItem.Type == Structs.MSRDDataItemTypes.Texture) 1640 | TextureData = MSRD.TOC[LBIM.DataItem.TOCIndex - 1].Data; 1641 | 1642 | TextureData.Seek(0x0, SeekOrigin.Begin); 1643 | 1644 | int TextureType = 0; 1645 | switch (LBIM.Type) 1646 | { 1647 | case 37: 1648 | TextureType = 28; 1649 | break; 1650 | case 66: 1651 | TextureType = 71; 1652 | break; 1653 | case 68: 1654 | TextureType = 77; 1655 | break; 1656 | case 73: 1657 | TextureType = 80; 1658 | break; 1659 | case 75: 1660 | TextureType = 83; 1661 | break; 1662 | case 0: 1663 | default: 1664 | App.PushLog($"Unknown texture type {LBIMs[i].Type}! Skipping texture..."); 1665 | continue; 1666 | } 1667 | 1668 | int ImageWidth = LBIM.DataItem.Size == 0 ? LBIM.Width : LBIM.Width * 2; 1669 | int ImageHeight = LBIM.DataItem.Size == 0 ? LBIM.Height : LBIM.Height * 2; 1670 | int TextureUnswizzleBufferSize = BitsPerPixel[TextureType] * 2; 1671 | int SwizzleSize = 4; 1672 | 1673 | int DDSFourCC = 0x30315844; //DX10 1674 | switch (TextureType) 1675 | { 1676 | case 71: 1677 | DDSFourCC = 0x31545844; //DXT1 1678 | break; 1679 | case 74: 1680 | DDSFourCC = 0x33545844; //DXT3 1681 | break; 1682 | case 77: 1683 | DDSFourCC = 0x35545844; //DXT5 1684 | break; 1685 | case 80: 1686 | DDSFourCC = 0x31495441; //ATI1 1687 | break; 1688 | case 83: 1689 | DDSFourCC = 0x32495441; //ATI2 1690 | break; 1691 | } 1692 | 1693 | //this will rewrite the small versions of the textures, but that's not a big deal considering they're so tiny 1694 | string filename = $@"{texturesFolderPath}\"; 1695 | if (MSRD.DataItemsCount > 0) 1696 | { 1697 | int NameIndex = i < MSRD.TextureInfo.Length ? i : MSRD.TextureIds[i % MSRD.TextureInfo.Length]; 1698 | filename += $"{NameIndex:d2}.{MSRD.TextureNames[NameIndex]}"; 1699 | } 1700 | else if (!string.IsNullOrWhiteSpace(LBIMs[i].Filename)) 1701 | filename += $"{i:d2}.{LBIMs[i].Filename}"; 1702 | else 1703 | filename += $"{i:d2}.UnknownFilename"; 1704 | 1705 | FileStream fsTexture; 1706 | if (LBIMs[i].Type == 37) 1707 | { 1708 | fsTexture = new FileStream(filename + ".tga", FileMode.Create); 1709 | BinaryWriter bwTexture = new BinaryWriter(fsTexture); 1710 | bwTexture.Write(0x20000); //type stuff 1711 | bwTexture.Write(0x0); //color map info 1712 | bwTexture.Write(0x0); //origin position 1713 | bwTexture.Write((short)ImageWidth); 1714 | bwTexture.Write((short)ImageHeight); 1715 | bwTexture.Write(0x820); //pixel size and descriptor 1716 | bwTexture.Seek(0x12, SeekOrigin.Begin); 1717 | TextureUnswizzleBufferSize = BitsPerPixel[TextureType] / 8; 1718 | SwizzleSize = 1; 1719 | } 1720 | else 1721 | { 1722 | fsTexture = new FileStream(filename + ".dds", FileMode.Create); 1723 | BinaryWriter bwTexture = new BinaryWriter(fsTexture); 1724 | bwTexture.Write(0x7C20534444); //magic 1725 | bwTexture.Write(0x1007); //flags 1726 | bwTexture.Write(ImageHeight); 1727 | bwTexture.Write(ImageWidth); 1728 | bwTexture.Write(TextureData.Length); 1729 | bwTexture.Write(0x1); 1730 | fsTexture.Seek(0x2C, SeekOrigin.Current); 1731 | bwTexture.Write(0x20); 1732 | bwTexture.Write(0x4); 1733 | bwTexture.Write(DDSFourCC); 1734 | fsTexture.Seek(0x28, SeekOrigin.Current); 1735 | if (DDSFourCC == 0x30315844) //DXT10 header 1736 | { 1737 | bwTexture.Write(TextureType); 1738 | bwTexture.Write(3); //resourceDimension 1739 | bwTexture.Write(0); //miscFlag 1740 | bwTexture.Write(1); //arraySize 1741 | bwTexture.Write(0); //miscFlags2 1742 | } 1743 | } 1744 | 1745 | byte[] TextureUnswizzleBuffer = new byte[16]; 1746 | byte[] TextureUnswizzled = new byte[TextureData.Length]; 1747 | 1748 | int ImageHeightInTiles = ImageHeight / SwizzleSize; 1749 | int ImageWidthInTiles = ImageWidth / SwizzleSize; 1750 | 1751 | int ImageRowCount = ImageHeightInTiles / 8; 1752 | if (ImageRowCount > 16) 1753 | ImageRowCount = 16; 1754 | else if (ImageRowCount == 0) 1755 | ImageRowCount = 1; 1756 | 1757 | int ImageColumnCount = 1; 1758 | switch (TextureUnswizzleBufferSize) 1759 | { 1760 | case 16: 1761 | ImageColumnCount = 1; 1762 | break; 1763 | case 8: 1764 | ImageColumnCount = 2; 1765 | break; 1766 | case 4: 1767 | ImageColumnCount = 4; 1768 | break; 1769 | } 1770 | 1771 | for (int HeightSection = 0; HeightSection < (ImageHeightInTiles / 8) / ImageRowCount; HeightSection++) 1772 | { 1773 | for (int WidthSection = 0; WidthSection < (ImageWidthInTiles / 4) / ImageColumnCount; WidthSection++) 1774 | { 1775 | for (int CurRow = 0; CurRow < ImageRowCount; CurRow++) 1776 | { 1777 | for (int SwizzleIndex = 0; SwizzleIndex < 32; SwizzleIndex++) 1778 | { 1779 | for (int CurColumn = 0; CurColumn < ImageColumnCount; CurColumn++) 1780 | { 1781 | int CurSwizzle = SwizzleLookup[SwizzleIndex]; 1782 | int somethingHeight = (HeightSection * ImageRowCount + CurRow) * 8 + (CurSwizzle / 4); 1783 | int somethingWidth = (WidthSection * 4 + (CurSwizzle % 4)) * ImageColumnCount + CurColumn; 1784 | 1785 | if (SwizzleSize == 1 || ImageWidth == SwizzleSize) 1786 | { 1787 | TextureUnswizzleBuffer[2] = (byte)TextureData.ReadByte(); 1788 | TextureUnswizzleBuffer[1] = (byte)TextureData.ReadByte(); 1789 | TextureUnswizzleBuffer[0] = (byte)TextureData.ReadByte(); 1790 | TextureUnswizzleBuffer[3] = (byte)TextureData.ReadByte(); 1791 | somethingHeight = ImageHeight - somethingHeight - 1; 1792 | } 1793 | else 1794 | { 1795 | TextureData.Read(TextureUnswizzleBuffer, 0, TextureUnswizzleBufferSize); 1796 | } 1797 | 1798 | int destinationIndex = TextureUnswizzleBufferSize * (somethingHeight * ImageWidthInTiles + somethingWidth); 1799 | Array.Copy(TextureUnswizzleBuffer, 0, TextureUnswizzled, destinationIndex, TextureUnswizzleBufferSize); 1800 | } 1801 | } 1802 | } 1803 | } 1804 | } 1805 | 1806 | fsTexture.Write(TextureUnswizzled, 0, (int)TextureData.Length); 1807 | fsTexture.Dispose(); 1808 | } 1809 | } 1810 | 1811 | public static int[] BitsPerPixel = new int[] { 1812 | 0, 128, 128, 128, 128, 96, 96, 96, 96, 64, 64, 64, 64, 64, 64, 64, 1813 | 64, 64, 64, 64, 64, 64, 64, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1814 | 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 1815 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 1816 | 8, 8, 1, 32, 32, 32, 4, 4, 4, 8, 8, 8, 8, 8, 8, 4, 1817 | 4, 4, 8, 8, 8, 16, 16, 32, 32, 32, 32, 32, 32, 32, 8, 8, 1818 | 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1819 | 0, 0, 0, 16 1820 | }; 1821 | 1822 | public static int[] SwizzleLookup = new int[] 1823 | { 1824 | 0, 4, 1, 5, 8, 12, 9, 13, 1825 | 16, 20, 17, 21, 24, 28, 25, 29, 1826 | 2, 6, 3, 7, 10, 14, 11, 15, 1827 | 18, 22, 19, 23, 26, 30, 27, 31 1828 | }; 1829 | } 1830 | } 1831 | -------------------------------------------------------------------------------- /XBC2ModelDecomp/MainFormTest.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 |