├── .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 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/MainFormTest.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Forms;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Navigation;
16 | using System.Threading;
17 | using System.Reflection;
18 | using Microsoft.WindowsAPICodePack.Dialogs;
19 | using System.Globalization;
20 |
21 | namespace XBC2ModelDecomp
22 | {
23 | ///
24 | /// Interaction logic for MainFormTest.xaml
25 | ///
26 | public partial class MainFormTest : Window
27 | {
28 | public static FormatTools FormatTools = new FormatTools();
29 | public static ModelTools ModelTools = new ModelTools();
30 | public static MapTools MapTools = new MapTools();
31 | public static TextureTools TextureTools = new TextureTools();
32 |
33 | public string[] Quotes =
34 | {
35 | "Find me on GitHub!",
36 | "\"Humongous hungolomghnonolougongus.\"",
37 | "\"Do you wish to change it? The future?\"",
38 | "\"Oops! That wasn't supposed to happen...\"",
39 | "\"I like your attitude!\"",
40 | "Very Funny Quote™ Goes Here",
41 | "\"i'm very bad at this\" - Block, 2019"
42 | };
43 |
44 | public MainFormTest()
45 | {
46 | InitializeComponent();
47 | App.LogEvent += LogEvent;
48 |
49 | CultureInfo culture = CultureInfo.CreateSpecificCulture("en-US");
50 | Thread.CurrentThread.CurrentCulture = culture;
51 | Thread.CurrentThread.CurrentUICulture = culture;
52 |
53 | txtConsole.Text = Quotes[new Random().Next(0, Quotes.Length)];
54 |
55 | if (FormatTools == null)
56 | FormatTools = new FormatTools();
57 | if (ModelTools == null)
58 | ModelTools = new ModelTools();
59 | if (MapTools == null)
60 | MapTools = new MapTools();
61 | if (TextureTools == null)
62 | TextureTools = new TextureTools();
63 |
64 | this.Title = $"XBC2ModelDecomp v{Assembly.GetEntryAssembly().GetName().Version.ToString(2)}-{ThisAssembly.Git.Commit}";
65 | }
66 |
67 | private void LogEvent(object message)
68 | {
69 | Dispatcher.Invoke(() =>
70 | {
71 | txtConsole.AppendText(string.IsNullOrWhiteSpace(txtConsole.Text) ? message.ToString() : '\n' + message.ToString());
72 | txtConsole.ScrollToEnd();
73 | });
74 | }
75 |
76 | private void TabControlChanged(object sender, SelectionChangedEventArgs e)
77 | {
78 | if (tabConsole != null && tabConsole.IsSelected && txtConsole.LineCount <= 1)
79 | txtConsole.Text = Quotes[new Random().Next(0, Quotes.Length)];
80 | }
81 |
82 | private void SelectFile(object sender, RoutedEventArgs e)
83 | {
84 | OpenFileDialog ofd = new OpenFileDialog { Filter = "Model Files (*.wismt)|*.wismt|Map Files (*.wismda)|*.wismda|Font Files (*.wifnt)|*.wifnt|All files (*.*)|*.*", Multiselect = true };
85 | if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
86 | {
87 | App.FilePaths = ofd.FileNames;
88 | App.OutputPaths = new string[App.FilePaths.Length];
89 | for (int i = 0; i < App.FilePaths.Length; i++)
90 | App.OutputPaths[i] = App.FilePaths[i].Remove(App.FilePaths[i].LastIndexOf('.'));
91 |
92 | EXbtnOutput.IsEnabled = true;
93 | EXbtnExtract.IsEnabled = true;
94 |
95 | EXtxtOutput.Text = string.Join(", ", App.OutputPaths);
96 | EXtxtInput.Text = string.Join(", ", App.FilePaths);
97 |
98 | EXtxtOutput.CaretIndex = EXtxtOutput.Text.Length;
99 | EXtxtOutput.ScrollToHorizontalOffset(EXtxtOutput.GetRectFromCharacterIndex(EXtxtOutput.CaretIndex).Right);
100 | EXtxtInput.CaretIndex = EXtxtInput.Text.Length;
101 | EXtxtInput.ScrollToHorizontalOffset(EXtxtInput.GetRectFromCharacterIndex(EXtxtInput.CaretIndex).Right);
102 | }
103 | }
104 |
105 | private void SelectOutputDir(object sender, RoutedEventArgs e)
106 | {
107 | CommonOpenFileDialog fbd = new CommonOpenFileDialog { IsFolderPicker = true };
108 | if (App.OutputPaths != null)
109 | fbd.InitialDirectory = App.OutputPaths[0].Remove(App.OutputPaths[0].LastIndexOf('\\'));
110 | if (fbd.ShowDialog() == CommonFileDialogResult.Ok)
111 | {
112 | if (App.OutputPaths != null)
113 | for (int i = 0; i < App.OutputPaths.Length; i++)
114 | App.OutputPaths[i] = fbd.FileName + $@"\{Path.GetFileNameWithoutExtension(App.FilePaths[i])}";
115 | EXtxtOutput.Text = fbd.FileName;
116 | }
117 | }
118 |
119 | private void ExtractFile(object sender, RoutedEventArgs e)
120 | {
121 | if (App.FilePaths == null || App.FilePaths.Length == 0)
122 | return;
123 | App.ExportTextures = EXcbxTextures.IsChecked.Value;
124 | App.ExportFlexes = EXcbxFlexes.IsChecked.Value;
125 | App.ExportAnims = EXcbxAnims.IsChecked.Value;
126 | App.ExportOutlines = EXcbxOutlines.IsChecked.Value;
127 | App.ExportMapMesh = EXcbxMapMesh.IsChecked.Value;
128 | App.ExportMapProps = EXcbxMapProps.IsChecked.Value;
129 | App.ShowInfo = EXcbxShowInfo.IsChecked.Value;
130 | App.LOD = (int)EXsldLOD.Value;
131 | App.PropSplitCount = (int)EXsldPropSplit.Value;
132 | App.ExportFormat = (Structs.ExportFormat)EXdropFormat.SelectedIndex;
133 |
134 | tabConsole.Focus();
135 | txtConsole.Text = "";
136 | App.PushLog($"Extracting {App.FilePaths.Length} file(s)...");
137 |
138 | Thread taskThread = new Thread(() =>
139 | {
140 | for (int i = 0; i < App.FilePaths.Length; i++)
141 | {
142 | App.FileIndex = i;
143 | switch (Path.GetExtension(App.FilePaths[i]))
144 | {
145 | case ".wimdo":
146 | case ".wismt":
147 | case ".wiefp":
148 | case ".arc":
149 | case ".mot":
150 | ModelTools.ExtractModels();
151 | break;
152 | case ".wismda":
153 | MapTools.ExtractMaps();
154 | break;
155 | case ".wifnt":
156 | TextureTools.ExtractTextures();
157 | break;
158 | }
159 | }
160 | });
161 | taskThread.Start();
162 | }
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/MapTools.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using zlib;
8 |
9 | namespace XBC2ModelDecomp
10 | {
11 | public class MapTools
12 | {
13 | private FormatTools ft = MainFormTest.FormatTools;
14 |
15 | public void ExtractMaps()
16 | {
17 | App.PushLog("Extracting large maps can take a decent amount of memory as the program decompresses/handles data.");
18 | if (App.ExportTextures)
19 | App.PushLog("In addition, exporting textures can sometimes take even more memory (1-5GB), especially for larger maps.");
20 | if (App.ExportFormat == Structs.ExportFormat.XNALara)
21 | App.PushLog("While XNALara *works*, glTF retains object position, rotation, and scale; it is reccomended to use it for those reasons.");
22 |
23 | if (File.Exists(App.CurFilePathAndName + ".winvhe"))
24 | {
25 | FileStream fsWINVHE = new FileStream(App.CurFilePathAndName + ".winvhe", FileMode.Open, FileAccess.Read);
26 | BinaryReader brWINVHE = new BinaryReader(fsWINVHE);
27 |
28 | Structs.NVMS NVMS = ft.ReadNVMS(fsWINVHE, brWINVHE);
29 |
30 | FileStream fsWINVDA = new FileStream(App.CurFilePathAndName + ".winvda", FileMode.Open, FileAccess.Read);
31 | BinaryReader brWINVDA = new BinaryReader(fsWINVDA);
32 |
33 | Structs.NVDA NVDA = new Structs.NVDA { Version = brWINVHE.ReadInt32() };
34 |
35 | NVDA.XBC1s = new Structs.XBC1[NVMS.NVDATableCount];
36 | for (int i = 0; i < NVMS.NVDATableCount; i++)
37 | NVDA.XBC1s[i] = ft.ReadXBC1(fsWINVDA, brWINVDA, NVMS.NVDAPointers[i].XBC1Offset);
38 | if (App.ExportFormat == Structs.ExportFormat.RawFiles)
39 | DumpXBC1s(fsWINVDA, NVDA.XBC1s);
40 | }
41 |
42 | List magicOccurences = new List();
43 |
44 | FileStream fsWISMDA = new FileStream(App.CurFilePathAndName + ".wismda", FileMode.Open, FileAccess.Read);
45 | BinaryReader brWISMDA = new BinaryReader(fsWISMDA);
46 |
47 | //this thing can be replaced with data in the wismhd, but I can't figure out a consistent way to get the data
48 | //for ma01a it's at 0x340
49 | //the table looks like [Int32 offset, Int32 FileSize]
50 | //it doesn't seem to have every xbc1 though?
51 | byte[] ByteBuffer = File.ReadAllBytes(App.CurFilePathAndName + ".wismda");
52 | byte[] SearchBytes = Encoding.ASCII.GetBytes("xbc1");
53 | for (int i = 0; i <= (ByteBuffer.Length - SearchBytes.Length); i++)
54 | {
55 | if (ByteBuffer[i] == SearchBytes[0])
56 | {
57 | for (int j = 1; j < SearchBytes.Length && ByteBuffer[i + j] == SearchBytes[j]; j++)
58 | {
59 | if (j == SearchBytes.Length - 1)
60 | {
61 | magicOccurences.Add(i);
62 | i += BitConverter.ToInt32(ByteBuffer, i + 12);
63 | }
64 | }
65 | }
66 | }
67 | ByteBuffer = new byte[0];
68 |
69 | if (magicOccurences.Count > 0)
70 | if (!Directory.Exists(App.CurOutputPath))
71 | Directory.CreateDirectory(App.CurOutputPath);
72 |
73 | Structs.WISMDA WISMDA = new Structs.WISMDA
74 | {
75 | Data = fsWISMDA,
76 | Files = new Structs.XBC1[magicOccurences.Count]
77 | };
78 |
79 | for (int i = 0; i < magicOccurences.Count; i++)
80 | WISMDA.Files[i] = ft.ReadXBC1(fsWISMDA, brWISMDA, magicOccurences[i]);
81 | if (App.ExportFormat == Structs.ExportFormat.RawFiles)
82 | DumpXBC1s(WISMDA.Data, WISMDA.Files);
83 |
84 | App.PushLog("Finished reading .wismda...");
85 |
86 | if (App.ExportFormat != Structs.ExportFormat.RawFiles)
87 | {
88 | if (App.ExportTextures)
89 | SaveMapTextures(WISMDA, $@"{App.CurOutputPath}\Textures");
90 |
91 | if (App.ExportMapMesh)
92 | SaveMapMeshes(WISMDA);
93 | if (App.ExportMapProps)
94 | SaveMapProps(WISMDA);
95 | }
96 |
97 | App.PushLog("Done!");
98 | }
99 |
100 | public void SaveMapMeshes(Structs.WISMDA WISMDA)
101 | {
102 | Structs.XBC1[] MapInfoDatas = WISMDA.FilesBySearch("bina_basefix.temp_wi");
103 | Structs.MXMD[] MapMXMDs = new Structs.MXMD[MapInfoDatas.Length];
104 | Structs.MapInfo[] MapInfos = new Structs.MapInfo[MapInfoDatas.Length];
105 | for (int i = 0; i < MapInfoDatas.Length; i++)
106 | {
107 | MapInfos[i] = ft.ReadMapInfo(MapInfoDatas[i].Data, new BinaryReader(MapInfoDatas[i].Data), false);
108 | for (int j = 0; j < MapInfos[i].MeshFileLookup.Length; j++)
109 | if (i != 0)
110 | MapInfos[i].MeshFileLookup[j] += (short)(MapInfos[i - 1].MeshFileLookup.Max() + 1);
111 |
112 | MapMXMDs[i] = new Structs.MXMD { Version = 0xFF };
113 | MapMXMDs[i].Materials = MapInfos[i].Materials;
114 | MapMXMDs[i].ModelStruct.MeshesCount = MapInfos[i].MeshTableDataCount;
115 | MapMXMDs[i].ModelStruct.Meshes = new Structs.MXMDMeshes[MapInfos[i].MeshTables.Length];
116 | for (int j = 0; j < MapInfos[i].MeshTables.Length; j++)
117 | {
118 | MapMXMDs[i].ModelStruct.Meshes[j].TableCount = MapInfos[i].MeshTables[j].MeshCount;
119 | MapMXMDs[i].ModelStruct.Meshes[j].Descriptors = MapInfos[i].MeshTables[j].Descriptors;
120 | }
121 | }
122 |
123 | if (App.ShowInfo)
124 | foreach (Structs.MapInfo map in MapInfos)
125 | App.PushLog("MapInfo:" + Structs.ReflectToString(map, 1, 180));
126 |
127 | Structs.XBC1[] MapMeshDatas = WISMDA.FilesBySearch("basemap/poli//");
128 | Structs.Mesh[] MapMeshes = new Structs.Mesh[MapMeshDatas.Length];
129 | for (int i = 0; i < MapMeshes.Length; i++)
130 | {
131 | MemoryStream model = MapMeshDatas[i].Data;
132 | MapMeshes[i] = ft.ReadMesh(model, new BinaryReader(model));
133 | }
134 |
135 | for (int i = 0; i < MapInfos.Length; i++)
136 | {
137 | switch (App.ExportFormat)
138 | {
139 | case Structs.ExportFormat.XNALara:
140 | ft.ModelToASCII(MapMeshes, MapMXMDs[i], new Structs.SKEL { Unknown1 = Int32.MaxValue }, MapInfos[i]);
141 | break;
142 | case Structs.ExportFormat.glTF:
143 | ft.ModelToGLTF(MapMeshes, MapMXMDs[i], new Structs.SKEL { Unknown1 = Int32.MaxValue }, MapInfos[i]);
144 | break;
145 | }
146 | }
147 | }
148 |
149 | public void SaveMapProps(Structs.WISMDA WISMDA)
150 | {
151 | Structs.XBC1[] MapInfoDatas = WISMDA.FilesBySearch("seamwork/inst/out");
152 | Structs.XBC1[] MapMeshDatas = WISMDA.FilesBySearch("seamwork/inst/mdl");
153 | Structs.XBC1[] MapPosDatas = WISMDA.FilesBySearch("seamwork/inst/pos");
154 | Structs.Mesh[] MapMeshes = new Structs.Mesh[MapMeshDatas.Length];
155 | List MapMXMDs = new List();
156 | Structs.SeamworkPropPosition[] MapPositions = new Structs.SeamworkPropPosition[MapPosDatas.Length];
157 | Structs.MapInfo[] MapInfos = new Structs.MapInfo[MapInfoDatas.Length];
158 | Dictionary MXMDToMapInfo = new Dictionary();
159 |
160 | int MapPositionsIndex = 0;
161 | for (int i = 0; i < MapPosDatas.Length; i++)
162 | MapPositions[i] = ft.ReadPropPositions(MapPosDatas[i].Data, new BinaryReader(MapPosDatas[i].Data));
163 |
164 | for (int i = 0; i < MapInfoDatas.Length; i++)
165 | {
166 | MapInfos[i] = ft.ReadMapInfo(MapInfoDatas[i].Data, new BinaryReader(MapInfoDatas[i].Data), true);
167 |
168 | for (int j = 0; j < MapMeshDatas.Length; j++)
169 | if (MapMeshes[j].VertexTableOffset == 0)
170 | MapMeshes[j] = ft.ReadMesh(MapMeshDatas[j].Data, new BinaryReader(MapMeshDatas[j].Data));
171 |
172 | if (MapInfos[i].PropPosTableCount == 1 && MapInfos[i].PropPositions[0].PropID != 0)
173 | {
174 | MapInfos[i].PropPositions.AddRange(MapPositions[MapPositionsIndex].Positions);
175 | MapInfos[i].PropPosTableCount = MapInfos[i].PropPositions.Count;
176 | MapPositionsIndex++;
177 | }
178 |
179 | //base things off prop position table
180 | //the table has the prop ids I need
181 | //duplicate ids mean duplicate meshes
182 | //get the highest LOD available
183 | //scrub through the propid table and only take unique values, dictionary this by index
184 | //take the dictionary and get the same indexes out of meshtables
185 | //loop through each prop position and build the MXMD based off those + artificial prop table
186 | //keep in mind structs are just memory values so I can duplicate things easily
187 |
188 | Dictionary UniqueIDIndex = new Dictionary();
189 |
190 | for (int j = 0; j < MapInfos[i].PropIDs.Count; j++)
191 | if (!UniqueIDIndex.ContainsKey(MapInfos[i].PropIDs[j]))
192 | UniqueIDIndex.Add(MapInfos[i].PropIDs[j], j);
193 |
194 | Structs.MapInfoMeshTable[] MeshTables = new Structs.MapInfoMeshTable[UniqueIDIndex.Count];
195 | int[] MeshLookup = new int[UniqueIDIndex.Count];
196 | for (int j = 0; j < UniqueIDIndex.Count; j++)
197 | {
198 | MeshTables[j] = MapInfos[i].MeshTables[UniqueIDIndex.Values.ElementAt(j)];
199 | MeshLookup[j] = MapInfos[i].PropFileLookup[UniqueIDIndex.Values.ElementAt(j)];
200 | }
201 | MapInfos[i].PropFileLookup = MeshLookup;
202 |
203 | for (int j = 0; j < (MapInfos[i].PropPosTableCount / App.PropSplitCount) + 1; j++)
204 | {
205 | int MeshCount = j + 1 == (MapInfos[i].PropPosTableCount / App.PropSplitCount) + 1 ? MapInfos[i].PropPosTableCount % App.PropSplitCount : App.PropSplitCount;
206 | Structs.MXMD FakeMXMD = new Structs.MXMD { Version = 0xFF };
207 | FakeMXMD.Materials = MapInfos[i].Materials;
208 | FakeMXMD.ModelStruct.MeshesCount = MeshCount;
209 | FakeMXMD.ModelStruct.Meshes = new Structs.MXMDMeshes[MeshCount];
210 |
211 | for (int k = 0; k < MeshCount; k++)
212 | {
213 | Structs.MapInfoPropPosition PropPosition = MapInfos[i].PropPositions[k + (j * App.PropSplitCount)];
214 |
215 | FakeMXMD.ModelStruct.Meshes[k].Unknown1 = k + (j * App.PropSplitCount);
216 | FakeMXMD.ModelStruct.Meshes[k].TableCount = MeshTables[PropPosition.PropID].MeshCount;
217 | FakeMXMD.ModelStruct.Meshes[k].Descriptors = MeshTables[PropPosition.PropID].Descriptors;
218 | }
219 |
220 | MapMXMDs.Add(FakeMXMD);
221 | MXMDToMapInfo.Add(FakeMXMD, MapInfos[i]);
222 | }
223 | }
224 |
225 | if (App.ShowInfo)
226 | foreach (Structs.MapInfo map in MapInfos)
227 | App.PushLog("PropInfo:" + Structs.ReflectToString(map, 1, 180));
228 |
229 | for (int i = 0; i < MXMDToMapInfo.Count; i++)
230 | {
231 | switch (App.ExportFormat)
232 | {
233 | case Structs.ExportFormat.XNALara:
234 | ft.ModelToASCII(MapMeshes, MXMDToMapInfo.Keys.ElementAt(i), new Structs.SKEL { Unknown1 = Int32.MaxValue }, MXMDToMapInfo.Values.ElementAt(i), $"props{i}x{MXMDToMapInfo.Keys.ElementAt(i).ModelStruct.MeshesCount}");
235 | break;
236 | case Structs.ExportFormat.glTF:
237 | ft.ModelToGLTF(MapMeshes, MXMDToMapInfo.Keys.ElementAt(i), new Structs.SKEL { Unknown1 = Int32.MaxValue }, MXMDToMapInfo.Values.ElementAt(i), $"props{i}x{MXMDToMapInfo.Keys.ElementAt(i).ModelStruct.MeshesCount}");
238 | break;
239 | }
240 | }
241 | }
242 |
243 | public void SaveMapTextures(Structs.WISMDA WISMDA, string texturesFolderPath)
244 | {
245 | //"cache/" contains all base colors and normals
246 | //"seamwork/tecpac//" contains PBR materials but in severely disjointed fashion
247 | //"seamwork/texture//" contains PBR materials and some base color things, seems to be for props exclusively?
248 |
249 | List TextureLBIMs = new List();
250 |
251 | List MapCache = WISMDA.FilesBySearch($"cache/cache_{App.CurFileNameNoExt}").ToList();
252 | List DoubleSize = new List();
253 | for (int i = 0; i < MapCache.Count; i++)
254 | {
255 | BinaryReader brTexture = new BinaryReader(MapCache[i].Data);
256 | MapCache[i].Data.Seek(-0x4, SeekOrigin.End);
257 | if (brTexture.ReadInt32() == 0x4D49424C)
258 | {
259 | Structs.LBIM lbim = ft.ReadLBIM(MapCache[i].Data, brTexture, 0, (int)MapCache[i].Data.Length);
260 | lbim.Filename = MapCache[i].Name.Split('/').LastOrDefault();
261 | if (lbim.Type == 66)
262 | DoubleSize.Add(i);
263 | else if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies
264 | TextureLBIMs.Add(lbim);
265 | }
266 | else
267 | {
268 | Structs.LBIM lbim = ft.ReadLBIM(MapCache[DoubleSize.First()].Data, new BinaryReader(MapCache[DoubleSize.First()].Data), 0, (int)MapCache[DoubleSize.First()].Data.Length);
269 | //lbim.Filename = MapCache[i].Name.Split('/').LastOrDefault();
270 | lbim.Filename = $"{MapCache[i].Name.Split('/').LastOrDefault()}-yoda-{i}-{DoubleSize.First()}";
271 | lbim.Data = MapCache[i].Data;
272 | lbim.Width *= 2;
273 | lbim.Height *= 2;
274 | TextureLBIMs.Add(lbim);
275 | DoubleSize.RemoveAt(0);
276 | }
277 | }
278 |
279 | /*for (int i = 0; i < DoubleSize.Count; i++)
280 | {
281 | Structs.LBIM lbim = DoubleSize[i];
282 | lbim.Data = MapCache[i + (MapCache.Count - DoubleSize.Count)].Data;
283 | lbim.Width *= 2;
284 | lbim.Height *= 2;
285 | TextureLBIMs.Add(lbim);
286 | }*/
287 |
288 | /*List TextureCache = WISMDA.FilesBySearch("cache//texture").ToList();
289 | TextureCache.AddRange(WISMDA.FilesBySearch("seamwork/tecpac"));
290 | foreach (Structs.XBC1 xbc1 in TextureCache)
291 | {
292 | BinaryReader brTexture = new BinaryReader(xbc1.Data);
293 | xbc1.Data.Seek(-0x4, SeekOrigin.End);
294 | if (brTexture.ReadInt32() == 0x4D49424C)
295 | {
296 | Structs.LBIM lbim = ft.ReadLBIM(xbc1.Data, brTexture, 0, (int)xbc1.Data.Length);
297 | lbim.Filename = xbc1.Name.Split('/').LastOrDefault();
298 | if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies
299 | TextureLBIMs.Add(lbim);
300 | }
301 | }*/
302 |
303 | ft.ReadTextures(new Structs.MSRD { Version = Int32.MaxValue }, texturesFolderPath + @"\CacheAndTecPac", TextureLBIMs);
304 | foreach (Structs.LBIM lbim in TextureLBIMs)
305 | lbim.Data.Dispose();
306 |
307 | TextureLBIMs.Clear();
308 |
309 | foreach (Structs.XBC1 xbc1 in WISMDA.FilesBySearch("seamwork/texture"))
310 | {
311 | BinaryReader brTexture = new BinaryReader(xbc1.Data);
312 | Structs.SeamworkTexture smwrkTexture = new Structs.SeamworkTexture
313 | {
314 | TableCount = brTexture.ReadInt32(),
315 | TableOffset = brTexture.ReadInt32()
316 | };
317 |
318 | smwrkTexture.Table = new Structs.SeamworkTextureTable[smwrkTexture.TableCount];
319 | xbc1.Data.Seek(smwrkTexture.TableOffset, SeekOrigin.Begin);
320 | for (int i = 0; i < smwrkTexture.TableCount; i++)
321 | {
322 | smwrkTexture.Table[i] = new Structs.SeamworkTextureTable
323 | {
324 | Unknown1 = brTexture.ReadInt32(),
325 | Size = brTexture.ReadInt32(),
326 | Offset = brTexture.ReadInt32(),
327 | Unknown2 = brTexture.ReadInt32()
328 | };
329 | }
330 | foreach (Structs.SeamworkTextureTable table in smwrkTexture.Table)
331 | {
332 | Structs.LBIM lbim = ft.ReadLBIM(xbc1.Data, brTexture, table.Offset, table.Size);
333 |
334 | if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies
335 | TextureLBIMs.Add(lbim);
336 | }
337 | }
338 |
339 | ft.ReadTextures(new Structs.MSRD { Version = Int32.MaxValue }, texturesFolderPath + @"\SeamworkTexture", TextureLBIMs);
340 | foreach (Structs.LBIM lbim in TextureLBIMs)
341 | lbim.Data.Dispose();
342 |
343 | TextureLBIMs.Clear();
344 | }
345 |
346 | public void DumpXBC1s(Stream sData, Structs.XBC1[] XBC1s)
347 | {
348 | List filenames = new List();
349 |
350 | App.PushLog($"Saving {XBC1s.Length} file(s) to disk...");
351 | for (int i = 0; i < XBC1s.Length; i++)
352 | {
353 | using (MemoryStream XBC1Stream = FormatTools.ReadZlib(sData, XBC1s[i].OffsetInFile + 0x30, XBC1s[i].FileSize, XBC1s[i].CompressedSize))
354 | {
355 | string fileName = XBC1s[i].Name.Split('/').Last();
356 | int dupeCount = filenames.Where(x => x == XBC1s[i].Name).Count();
357 | string saveName = $"{XBC1s[i].Name}{(string.IsNullOrWhiteSpace(fileName) ? "NOFILENAME" : "")}{(dupeCount > 0 ? $"-{dupeCount}" : "")}";
358 |
359 | ft.SaveStreamToFile(XBC1Stream, saveName, App.CurOutputPath + @"\RawFiles\");
360 | if (App.ShowInfo)
361 | App.PushLog($"Saved {saveName} to disk...");
362 | filenames.Add(XBC1s[i].Name);
363 | }
364 | }
365 | }
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/ModelTools.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 zlib;
9 |
10 | namespace XBC2ModelDecomp
11 | {
12 | public class ModelTools
13 | {
14 | private FormatTools ft = MainFormTest.FormatTools;
15 |
16 | public void ExtractModels()
17 | {
18 | App.PushLog($"Reading {App.CurFileNameNoExt}...");
19 |
20 | //wismt
21 | FileStream fsWISMT = new FileStream(App.CurFilePathAndName + ".wismt", FileMode.Open, FileAccess.Read);
22 | BinaryReader brWISMT = new BinaryReader(fsWISMT);
23 |
24 | Structs.MSRD MSRD = ft.ReadMSRD(fsWISMT, brWISMT);
25 |
26 | if (App.ExportAnims)
27 | {
28 | if (File.Exists(App.CurFilePathAndName + ".mot"))
29 | {
30 | foreach (string file in Directory.GetFiles(App.CurFilePath, $"{App.CurFileNameNoExt}*.mot"))
31 | {
32 | FileStream fsMOT = new FileStream(file, FileMode.Open, FileAccess.Read);
33 | BinaryReader brMOT = new BinaryReader(fsMOT);
34 |
35 | Structs.SAR1 SAR1 = ft.ReadSAR1(fsMOT, brMOT, @"\Animations\", App.ExportAnims);
36 |
37 | brMOT.Dispose();
38 | fsMOT.Dispose();
39 | }
40 | }
41 | else
42 | App.PushLog("No .mot file exists, continuing...");
43 | }
44 |
45 | //start mesh file
46 | if (MSRD.TOC.Length > 0)
47 | {
48 | if (App.ExportFormat != Structs.ExportFormat.None && !Directory.Exists(App.CurOutputPath))
49 | Directory.CreateDirectory(App.CurOutputPath);
50 |
51 | if (App.ExportTextures)
52 | ft.ReadTextures(MSRD, $@"{App.CurOutputPath}\Textures");
53 |
54 | BinaryReader brCurFile = new BinaryReader(MSRD.TOC[0].Data); //start new file
55 |
56 | Structs.Mesh Mesh = ft.ReadMesh(MSRD.TOC[0].Data, brCurFile);
57 |
58 | Structs.MXMD MXMD = new Structs.MXMD { Version = Int32.MaxValue };
59 | if (File.Exists(App.CurFilePathAndName + ".wimdo"))
60 | {
61 | FileStream fsWIMDO = new FileStream(App.CurFilePathAndName + ".wimdo", FileMode.Open, FileAccess.Read);
62 | BinaryReader brWIMDO = new BinaryReader(fsWIMDO);
63 |
64 | MXMD = ft.ReadMXMD(fsWIMDO, brWIMDO);
65 | }
66 |
67 | Structs.SAR1 SAR1 = new Structs.SAR1 { Version = Int32.MaxValue };
68 | Structs.SKEL SKEL = new Structs.SKEL { Unknown1 = Int32.MaxValue };
69 | if (File.Exists(App.CurFilePathAndName + ".arc"))
70 | {
71 | FileStream fsARC = new FileStream(App.CurFilePathAndName + ".arc", FileMode.Open, FileAccess.Read);
72 | BinaryReader brARC = new BinaryReader(fsARC);
73 |
74 | SAR1 = ft.ReadSAR1(fsARC, brARC, @"\RawFiles\", App.ExportFormat == Structs.ExportFormat.RawFiles);
75 | BinaryReader brSKEL = new BinaryReader(SAR1.ItemBySearch(".skl").Data);
76 | SKEL = ft.ReadSKEL(brSKEL.BaseStream, brSKEL);
77 | }
78 |
79 | if (App.ShowInfo)
80 | {
81 | App.PushLog(MSRD.ToString());
82 | App.PushLog(Mesh.ToString());
83 | App.PushLog(MXMD.ToString());
84 | }
85 |
86 | switch (App.ExportFormat)
87 | {
88 | case Structs.ExportFormat.XNALara:
89 | ft.ModelToASCII(new Structs.Mesh[] { Mesh }, MXMD, SKEL, new Structs.MapInfo { Unknown1 = Int32.MaxValue });
90 | break;
91 | case Structs.ExportFormat.glTF:
92 | ft.ModelToGLTF(new Structs.Mesh[] { Mesh }, MXMD, SKEL, new Structs.MapInfo { Unknown1 = Int32.MaxValue });
93 | break;
94 | }
95 |
96 | App.PushLog($"Finished {App.CurFileNameNoExt}!");
97 | }
98 | else
99 | {
100 | App.PushLog($"No files found in {App.CurFilePathAndName}.wismt?");
101 | }
102 |
103 | brWISMT.Dispose();
104 | fsWISMT.Dispose();
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("XBC2ModelDecomp")]
9 | [assembly: AssemblyDescription("Manipulates model assets inside the files of Xenoblade 2.")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Block57")]
12 | [assembly: AssemblyProduct("XBC2ModelDecomp")]
13 | [assembly: AssemblyCopyright("")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("63858b8d-24a0-4e76-83f6-f792c2117318")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("2.1.0.0")]
36 | [assembly: AssemblyFileVersion("2.1.0.0")]
37 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace XBC2ModelDecomp.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("XBC2ModelDecomp.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace XBC2ModelDecomp.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.9.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/Structs.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Numerics;
6 | using System.Reflection;
7 | using System.Threading.Tasks;
8 | using System.Windows.Media;
9 |
10 | namespace XBC2ModelDecomp
11 | {
12 | public class Structs
13 | {
14 | public enum ExportFormat
15 | {
16 | None,
17 | RawFiles,
18 | XNALara,
19 | glTF
20 | }
21 |
22 | public static string ReflectToString(object parent, int tabCount = 1, int arrayLimit = 120)
23 | {
24 | string output = "";
25 |
26 | Type parentType = parent.GetType();
27 |
28 | foreach (FieldInfo field in parentType.GetFields())
29 | {
30 | object value = field.GetValue(parent);
31 | if (value == null)
32 | continue;
33 |
34 | if (value is int || value is uint ||
35 | value is short || value is ushort ||
36 | value is byte || value is sbyte)
37 | {
38 | output += "\n";
39 | for (int i = 0; i < tabCount; i++)
40 | output += "\t";
41 |
42 | output += $"{field.Name}: 0x{value:X} ({value})";
43 | }
44 | else if (value is string)
45 | {
46 | output += "\n";
47 | for (int i = 0; i < tabCount; i++)
48 | output += "\t";
49 |
50 | output += $"{field.Name}: {value}";
51 | }
52 | else if (field.FieldType.IsEnum)
53 | {
54 | output += "\n";
55 | for (int i = 0; i < tabCount; i++)
56 | output += "\t";
57 |
58 | object enumValue = Enum.Parse(field.FieldType, value.ToString());
59 |
60 | output += $"{field.Name}: {enumValue} ({Convert.ToInt32(enumValue)})";
61 | }
62 | else if (field.FieldType.IsArray)
63 | {
64 | Array array = field.GetValue(parent) as Array;
65 |
66 | output += "\n";
67 | for (int i = 0; i < tabCount; i++)
68 | output += "\t";
69 |
70 | output += $"{field.Name}[{array.Length}]: ";
71 |
72 | if (array.Rank > 1 || array.Length > arrayLimit || array is Vector3[] || array is Quaternion[] || array is Color[])
73 | continue;
74 |
75 | if (array is byte[])
76 | {
77 | output += $"0x{BitConverter.ToString(array as byte[]).Replace("-", "")}";
78 | }
79 | else
80 | {
81 | for (int i = 0; i < array.Length; i++)
82 | {
83 | output += "\n";
84 | for (int j = 0; j < tabCount + 1; j++)
85 | output += "\t";
86 |
87 | if (field.FieldType.Namespace == "XBC2ModelDecomp")
88 | {
89 | output += $"Item {i}:";
90 | output += ReflectToString(array.GetValue(i), tabCount + 2);
91 | }
92 | else
93 | {
94 | switch (array.GetValue(i).GetType().Name)
95 | {
96 | case nameof(Int32):
97 | case nameof(UInt32):
98 | case nameof(Int16):
99 | case nameof(UInt16):
100 | output += $"Item {i}: 0x{array.GetValue(i):X} ({array.GetValue(i)})";
101 | break;
102 | case nameof(String):
103 | output += $"Item {i}: {array.GetValue(i)}";
104 | break;
105 | default:
106 | //output += $"({field.FieldType.Name}) {field.Name}: {field.GetValue(parent)}";
107 | break;
108 | }
109 |
110 | }
111 | }
112 | }
113 | }
114 | else if (field.FieldType.Namespace == "XBC2ModelDecomp")
115 | {
116 | output += "\n";
117 | for (int i = 0; i < tabCount; i++)
118 | output += "\t";
119 |
120 | output += $"{field.Name}:";
121 | output += ReflectToString(value, tabCount + 1);
122 | }
123 | }
124 |
125 | return output;
126 | }
127 |
128 | public struct XBC1
129 | {
130 | public int Version;
131 | public int FileSize;
132 | public int CompressedSize;
133 | public int Unknown1;
134 |
135 | public string Name;
136 |
137 | public MemoryStream Data;
138 |
139 | public int OffsetInFile; //not in struct
140 |
141 | public override string ToString()
142 | {
143 | string output = "xbc1:";
144 | output += ReflectToString(this);
145 | return output;
146 | }
147 | }
148 |
149 | public struct LBIM //its reverse?
150 | {
151 | public MSRDDataItem DataItem; //not in struct
152 | public string Filename; //not in struct
153 |
154 | public MemoryStream Data;
155 |
156 | public int Unknown5;
157 | public int Unknown4;
158 |
159 | public int Width;
160 | public int Height;
161 |
162 | public int Unknown3;
163 | public int Unknown2;
164 |
165 | public int Type;
166 | public int Unknown1;
167 | public int Version;
168 | }
169 |
170 |
171 | //wismt
172 | public struct MSRD
173 | {
174 | public int Version;
175 | public int HeaderSize;
176 | public int MainOffset;
177 |
178 | public int Tag;
179 | public int Revision;
180 |
181 | public int DataItemsCount;
182 | public int DataItemsOffset;
183 | public int FileCount;
184 | public int TOCOffset;
185 |
186 | public byte[] Unknown1; //0x1C long
187 |
188 | public int TextureIdsCount;
189 | public int TextureIdsOffset;
190 | public int TextureCountOffset;
191 |
192 | public MSRDDataItem[] DataItems;
193 | public MSRDTOC[] TOC;
194 |
195 | public short[] TextureIds;
196 | public int TextureCount;
197 | public int TextureChunkSize;
198 | public int Unknown2; //texture related
199 | public int TextureStringBufferOffset;
200 | public MSRDTextureInfo[] TextureInfo;
201 | public string[] TextureNames;
202 |
203 | public override string ToString()
204 | {
205 | string output = "MSRD:";
206 | output += ReflectToString(this);
207 | return output;
208 | }
209 | }
210 |
211 | public enum MSRDDataItemTypes : ushort
212 | {
213 | Model = 0,
214 | ShaderBundle,
215 | CachedTextures,
216 | Texture
217 | }
218 |
219 | public struct MSRDDataItem
220 | {
221 | public int Offset;
222 | public int Size;
223 | public short TOCIndex;
224 | public MSRDDataItemTypes Type;
225 | }
226 |
227 | public struct MSRDTOC
228 | {
229 | public int CompSize;
230 | public int FileSize;
231 | public int Offset;
232 |
233 | public MemoryStream Data;
234 | }
235 |
236 | public struct MSRDTexture
237 | {
238 | public int Size;
239 | public int Offset;
240 | public string Name;
241 | }
242 |
243 | public struct MSRDTextureInfo
244 | {
245 | public int Unknown1;
246 | public int Size;
247 | public int Offset;
248 | public int StringOffset;
249 | }
250 |
251 |
252 | //wismt file0
253 | public struct Mesh
254 | {
255 | public int VertexTableOffset;
256 | public int VertexTableCount;
257 | public int FaceTableOffset;
258 | public int FaceTableCount;
259 |
260 | public byte[] Reserved1; //0xC long
261 |
262 | public int UnknownOffset1;
263 | public int UnknownOffset2;
264 | public int UnknownOffset2Count;
265 |
266 | public int MorphDataOffset;
267 | public int DataSize;
268 | public int DataOffset;
269 | public int WeightDataSize;
270 | public int WeightDataOffset;
271 |
272 | public byte[] Reserved2; //0x14 long
273 |
274 | public MeshVertexTable[] VertexTables;
275 | public MeshFaceTable[] FaceTables;
276 |
277 | //public byte[] Reserved3; //0x30 long
278 |
279 | public List VertexDescriptors;
280 |
281 | public MeshWeightData WeightData;
282 | public MeshMorphData MorphData;
283 |
284 | public override string ToString()
285 | {
286 | string output = "Mesh:";
287 | output += ReflectToString(this);
288 | return output;
289 | }
290 | }
291 |
292 | public struct MeshVertexTable
293 | {
294 | public int DataOffset;
295 | public int DataCount;
296 | public int BlockSize;
297 |
298 | public int DescOffset;
299 | public int DescCount;
300 |
301 | public byte[] Unknown1; //0xC long
302 |
303 | public MeshVertexDescriptor[] Descriptors; //not in struct
304 |
305 | //these are right after the actual vertex struct, but not in this order
306 | public Vector3[] Vertices;
307 | public int[] Weights;
308 | public Vector2[,] UVPos;
309 | public int UVLayerCount;
310 | public Color[] VertexColor;
311 | public Quaternion[] Normals;
312 | public float[,] WeightValues;
313 | public byte[,] WeightIds;
314 | }
315 |
316 | public struct MeshFaceTable
317 | {
318 | public int Offset;
319 | public int VertCount;
320 |
321 | public byte[] Unknown1; //0xC long
322 |
323 | public ushort[] Vertices; //not in struct
324 | }
325 |
326 | public struct MeshVertexDescriptor
327 | {
328 | public short Type;
329 | public short Size;
330 | }
331 |
332 | public struct MeshWeightData
333 | {
334 | public int WeightManagerCount;
335 | public int WeightManagerOffset;
336 |
337 | public short VertexTableIndex;
338 | public short Unknown2;
339 |
340 | public int Offset02;
341 |
342 | public MeshWeightManager[] WeightManagers;
343 | }
344 |
345 | public struct MeshWeightManager
346 | {
347 | public int Unknown1;
348 | public int Offset;
349 | public int Count;
350 |
351 | public byte[] Unknown2; //0x11 long
352 | public byte LOD;
353 | public byte[] Unknown3; //0xA long
354 | }
355 |
356 | public struct MeshMorphData
357 | {
358 | public int MorphDescriptorsCount;
359 | public int MorphDescriptorsOffset;
360 |
361 | public MeshMorphDescriptor[] MorphDescriptors;
362 |
363 | public int MorphTargetsCount;
364 | public int MorphTargetsOffset;
365 |
366 | public MeshMorphTarget[] MorphTargets;
367 | }
368 |
369 | public struct MeshMorphDescriptor
370 | {
371 | public int BufferID;
372 |
373 | public int TargetIndex;
374 | public int TargetCounts;
375 | public int TargetIDOffsets;
376 |
377 | public int Unknown1;
378 |
379 | public short[] TargetIDs; //not in struct
380 | }
381 |
382 | public struct MeshMorphTarget
383 | {
384 | public int BufferOffset;
385 | public int VertCount;
386 | public int BlockSize;
387 |
388 | public short Unknown1;
389 | public short Type;
390 |
391 | public Vector3[] Vertices; //not in struct
392 | public Quaternion[] Normals; //not in struct
393 | }
394 |
395 |
396 | //wimdo
397 | public struct MXMD
398 | {
399 | public int Version;
400 |
401 | public int ModelStructOffset;
402 | public int MaterialsOffset;
403 |
404 | public int Unknown1;
405 |
406 | public int VertexBufferOffset;
407 | public int ShadersOffset;
408 | public int CachedTexturesTableOffset;
409 | public int Unknown2;
410 | public int UncachedTexturesTableOffset;
411 |
412 | public byte[] Unknown3; //0x28 long
413 |
414 | public MXMDModelStruct ModelStruct;
415 |
416 | public MXMDMaterialHeader MaterialHeader;
417 | public MXMDMaterial[] Materials;
418 |
419 | public override string ToString()
420 | {
421 | string output = "MXMD:";
422 | output += ReflectToString(this);
423 | return output;
424 | }
425 | }
426 |
427 | public struct MXMDModelStruct
428 | {
429 | public int Unknown1;
430 | public Vector3 BoundingBoxStart;
431 | public Vector3 BoundingBoxEnd;
432 | public int MeshesOffset;
433 | public int MeshesCount;
434 | public int Unknown3;
435 | public int NodesOffset;
436 |
437 | public byte[] Unknown4; //0x54 long
438 |
439 | public int MorphControllersOffset;
440 | public int MorphNamesOffset;
441 |
442 | public MXMDMorphControls MorphControls;
443 |
444 | public MXMDMorphNames MorphNames;
445 |
446 | public MXMDMeshes[] Meshes;
447 |
448 | public MXMDNodes Nodes;
449 | }
450 |
451 | public struct MXMDMorphControls
452 | {
453 | public int TableOffset;
454 | public int Count;
455 |
456 | public byte[] Unknown2; //0x10 long
457 |
458 | public MXMDMorphControl[] Controls;
459 | }
460 |
461 | public struct MXMDMorphControl
462 | {
463 | public int NameOffset1;
464 | public int NameOffset2;
465 |
466 | public byte[] Unknown1; //0x14 long
467 |
468 | public string Name; //not in real struct
469 | }
470 |
471 | public struct MXMDMorphNames
472 | {
473 | public int TableOffset;
474 | public int Count;
475 |
476 | public byte[] Unknown2; //0x20 long
477 |
478 | public MXMDMorphName[] Names;
479 | }
480 |
481 | public struct MXMDMorphName
482 | {
483 | public int NameOffset;
484 | public int Unknown1;
485 | public int Unknown2;
486 | public int Unknown3;
487 |
488 | public string Name; //not in real struct
489 | }
490 |
491 | public struct MXMDMeshes
492 | {
493 | public int TableOffset;
494 | public int TableCount;
495 | public int Unknown1;
496 |
497 | public Vector3 BoundingBoxStart;
498 | public Vector3 BoundingBoxEnd;
499 | public float BoundingRadius;
500 |
501 | public MXMDMeshDescriptor[] Descriptors;
502 | }
503 |
504 | public struct MXMDMeshDescriptor
505 | {
506 | public int ID;
507 |
508 | public int Descriptor;
509 | public int WeightBind; //not in struct
510 |
511 | public short VertTableIndex;
512 | public short FaceTableIndex;
513 |
514 | public short Unknown1;
515 | public short MaterialID;
516 | public byte[] Unknown2; //0xC long
517 | public short Unknown3;
518 |
519 | public short LOD;
520 | public int Unknown4;
521 |
522 | public byte[] Unknown5; //0xC long
523 | }
524 |
525 | public struct MXMDNodes
526 | {
527 | public int BoneCount;
528 | public int BoneCount2;
529 |
530 | public int NodeIdsOffset;
531 | public int NodeTmsOffset;
532 |
533 | public MXMDNode[] Nodes;
534 | }
535 |
536 | public struct MXMDNode
537 | {
538 | public int NameOffset;
539 | public float Unknown1;
540 | public int Unknown2;
541 |
542 | public int ID;
543 | public int Unknown3;
544 | public int Unknown4;
545 |
546 | public string Name; //not in struct
547 |
548 | public Quaternion Scale;
549 | public Quaternion Rotation;
550 | public Quaternion Position;
551 |
552 | public Quaternion ParentTransform;
553 | }
554 |
555 | public struct MXMDMaterialHeader
556 | {
557 | public int Offset;
558 | public int Count;
559 | }
560 |
561 | public struct MXMDMaterial
562 | {
563 | public int NameOffset;
564 |
565 | public byte[] Unknown1; //0x70 long oh no
566 |
567 | public string Name; //not in struct
568 | }
569 |
570 |
571 | //arc, mot
572 | public struct SAR1
573 | {
574 | public int FileSize;
575 | public int Version;
576 | public int NumFiles;
577 | public int TOCOffset;
578 | public int DataOffset;
579 | public int Unknown1;
580 | public int Unknown2;
581 | public string Path; //0x80 chars
582 |
583 | public SARTOC[] TOCItems;
584 | public SARBC[] BCItems;
585 |
586 | public SARBC ItemBySearch(string search)
587 | {
588 | return BCItems[Array.FindIndex(TOCItems, x => x.Filename.Contains(search))];
589 | }
590 |
591 | public override string ToString()
592 | {
593 | string output = "SAR1:";
594 | output += ReflectToString(this);
595 | return output;
596 | }
597 | }
598 |
599 | public struct SARTOC
600 | {
601 | public int Offset;
602 | public int Size;
603 | public int Unknown1;
604 | public string Filename; //0x34 chars
605 | }
606 |
607 | public struct SARBC
608 | {
609 | public int BlockCount;
610 | public int FileSize;
611 | public int PointerCount;
612 | public int OffsetToData; //starts from blockcount, not magic
613 |
614 | public MemoryStream Data;
615 | }
616 |
617 |
618 | //arc
619 | public struct SKEL
620 | {
621 | public int Unknown1;
622 | public int Unknown2;
623 |
624 | public SKELTOC[] TOCItems;
625 | public short[] Parents;
626 | public SKELNodes[] Nodes;
627 | public SKELTransforms[] Transforms;
628 |
629 | public Dictionary NodeNames; //not in struct
630 |
631 | public override string ToString()
632 | {
633 | string output = "SAR1:";
634 | output += ReflectToString(this);
635 | return output;
636 | }
637 | }
638 |
639 | public struct SKELTOC
640 | {
641 | public int Offset;
642 | public int Unknown1;
643 | public int Count;
644 | public int Unknown2;
645 | }
646 |
647 | public struct SKELNodes
648 | {
649 | public int Offset;
650 | public byte[] Unknown1; //0xC long
651 | public string Name; //not in struct
652 | }
653 |
654 | public struct SKELTransforms
655 | {
656 | public Quaternion Position;
657 | public Quaternion Rotation;
658 | public Quaternion Scale;
659 |
660 | public Vector3 RealPosition; //not in struct
661 | public Quaternion RealRotation; //not in struct
662 | }
663 |
664 |
665 | //wismda
666 | public struct WISMDA
667 | {
668 | public Stream Data;
669 | public XBC1[] Files;
670 |
671 | public XBC1 FileBySearch(string search)
672 | {
673 | XBC1 xbc1 = Files[Array.FindIndex(Files, x => x.Name.Contains(search))];
674 | xbc1.Data = FormatTools.ReadZlib(Data, xbc1.OffsetInFile + 0x30, xbc1.FileSize, xbc1.CompressedSize);
675 | return xbc1;
676 | }
677 |
678 | public XBC1[] FilesBySearch(string search, bool unique = false)
679 | {
680 | XBC1[] xbc1s = Files.Where(x => x.Name.Contains(search)).ToArray();
681 | for (int i = 0; i < xbc1s.Length; i++)
682 | xbc1s[i].Data = FormatTools.ReadZlib(Data, xbc1s[i].OffsetInFile + 0x30, xbc1s[i].FileSize, xbc1s[i].CompressedSize);
683 | return xbc1s;
684 | }
685 | }
686 |
687 | //winvhe
688 | public struct NVMS
689 | {
690 | public int Version;
691 |
692 | public int NVDATableOffset;
693 | public int NVDATableCount;
694 |
695 | public int Table2Offset;
696 | public int Table2Count;
697 | public int Table3Offset;
698 | public int Table3Count;
699 |
700 | public byte[] Reserved1; //0x20 long
701 |
702 | public NVMSNVDAPointers[] NVDAPointers;
703 | }
704 |
705 | //winvda
706 | public struct NVMSNVDAPointers
707 | {
708 | public int Unknown1;
709 | public int XBC1Offset;
710 | public int XBC1Size;
711 | public int SecondFileOffset;
712 | }
713 |
714 | public struct NVDA
715 | {
716 | public int Version;
717 |
718 | public XBC1[] XBC1s;
719 | }
720 |
721 | public struct MapInfo
722 | {
723 | public int Unknown1;
724 | public int Unknown2;
725 | public int Unknown3;
726 |
727 | public int MeshTableOffset;
728 | public int MaterialTableOffset;
729 |
730 | public int Unknown4;
731 | public int MiscPropertiesTable;
732 | public int Unknown5;
733 | public int Unknown6;
734 | public int Unknown7;
735 |
736 | public int PropFileIndexOffset;
737 | public int PropFileIndexCount;
738 | public int Unknown8;
739 | public int Unknown9;
740 |
741 | public int TableIndexOffset;
742 |
743 | //the rest of these will be all over the place
744 |
745 | public MXMDMaterialHeader MaterialHeader;
746 | public MXMDMaterial[] Materials;
747 |
748 | public int MeshTableDataOffset;
749 | public int MeshTableDataCount;
750 |
751 | public int MeshFileLookupOffset;
752 | public int MeshFileLookupCount;
753 |
754 | public short[] MeshFileLookup;
755 |
756 | public MapInfoMeshTable[] MeshTables;
757 |
758 | public int PropLODsDataCount;
759 | public int PropLODsDataOffset;
760 |
761 | public List PropLODs;
762 | public List PropIDs;
763 | public int[] PropFileLookup;
764 |
765 | public int PropPosTableCount;
766 | public int PropPosTableOffset;
767 |
768 | public List PropPositions;
769 |
770 | public override string ToString()
771 | {
772 | string output = "MapInfo:";
773 | output += ReflectToString(this);
774 | return output;
775 | }
776 | }
777 |
778 | public struct MapInfoMeshTable
779 | {
780 | public int MeshOffset;
781 | public int MeshCount;
782 |
783 | public MXMDMeshDescriptor[] Descriptors;
784 | }
785 |
786 | public struct SeamworkTexture
787 | {
788 | public int TableCount;
789 | public int TableOffset;
790 |
791 | public SeamworkTextureTable[] Table;
792 | }
793 |
794 | public struct SeamworkTextureTable
795 | {
796 | public int Unknown1;
797 | public int Size;
798 | public int Offset;
799 | public int Unknown2;
800 | }
801 |
802 | public struct SeamworkPropPosition
803 | {
804 | public int TableCount;
805 | public int TableOffset;
806 |
807 | public int Unknown1;
808 | public int Unknown2;
809 |
810 | public int UnknownTable1Count;
811 | public int UnknownTable1Offset;
812 |
813 | public byte[] Unknown3; //0x10 long
814 |
815 | public int UnknownTable2Count;
816 | public int UnknownTable2Offset;
817 | public int UnknownTable3Count;
818 | public int UnknownTable3Offset;
819 |
820 | public byte[] Unknown7; //0x28 long
821 |
822 | public List Positions;
823 | }
824 |
825 | public struct MapInfoPropPosition
826 | {
827 | public Matrix4x4 Matrix;
828 |
829 | public Vector3 Position; //not in struct
830 | public Quaternion Rotation; //not in struct
831 |
832 | public byte[] Unknown1; //0x1C long
833 |
834 | public int PropID;
835 |
836 | public byte[] Unknown2; //0x10 long
837 | }
838 | }
839 | }
840 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/TextureTools.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace XBC2ModelDecomp
9 | {
10 | public class TextureTools
11 | {
12 | private FormatTools ft = MainFormTest.FormatTools;
13 |
14 | public void ExtractTextures()
15 | {
16 | App.PushLog($"Reading {App.CurFileNameNoExt}...");
17 |
18 | //wismt
19 | FileStream fsWIFNT = new FileStream(App.CurFilePathAndName + ".wifnt", FileMode.Open, FileAccess.Read);
20 | BinaryReader brWIFNT = new BinaryReader(fsWIFNT);
21 |
22 | List TextureLBIMs = new List();
23 |
24 | fsWIFNT.Seek(-0x4, SeekOrigin.End);
25 | if (brWIFNT.ReadInt32() == 0x4D49424C)
26 | {
27 | Structs.LBIM lbim = ft.ReadLBIM(fsWIFNT, brWIFNT, 0x1000, (int)fsWIFNT.Length - 0x1000);
28 | lbim.Filename = "thistestcool";
29 | lbim.Type = 75;
30 | if (lbim.Data != null)
31 | TextureLBIMs.Add(lbim);
32 | }
33 |
34 | ft.ReadTextures(new Structs.MSRD { Version = Int32.MaxValue }, App.CurFilePath, TextureLBIMs);
35 |
36 | App.PushLog("Done!");
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/XBC2ModelDecomp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {63858B8D-24A0-4E76-83F6-F792C2117318}
8 | WinExe
9 | XBC2ModelDecomp
10 | XBC2ModelDecomp
11 | v4.6.1
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 |
20 | publish\
21 | true
22 | Disk
23 | false
24 | Foreground
25 | 7
26 | Days
27 | false
28 | false
29 | true
30 | 0
31 | 1.0.0.0
32 | false
33 | false
34 | true
35 |
36 |
37 | AnyCPU
38 | true
39 | full
40 | false
41 | bin\Debug\
42 | DEBUG;TRACE
43 | prompt
44 | 4
45 |
46 |
47 | AnyCPU
48 | none
49 | true
50 | bin\Release\
51 | TRACE
52 | prompt
53 | 4
54 |
55 |
56 | XBC2ModelDecomp.App
57 |
58 |
59 | x64
60 | bin\x64\Debug\
61 |
62 |
63 | x64
64 | bin\x64\Release\
65 |
66 |
67 |
68 | ..\packages\Microsoft-WindowsAPICodePack-Core.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.dll
69 |
70 |
71 | ..\packages\Microsoft-WindowsAPICodePack-Shell.1.1.3.3\lib\net452\Microsoft.WindowsAPICodePack.Shell.dll
72 |
73 |
74 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll
75 |
76 |
77 |
78 |
79 | ..\packages\SharpGLTF.Core.1.0.0-alpha0014\lib\netstandard2.0\SharpGLTF.Core.dll
80 |
81 |
82 | ..\packages\SharpGLTF.Toolkit.1.0.0-alpha0014\lib\netstandard2.0\SharpGLTF.Toolkit.dll
83 |
84 |
85 |
86 | ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll
87 |
88 |
89 |
90 | ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll
91 |
92 |
93 |
94 | ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll
95 |
96 |
97 | ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | ..\packages\zlib.net.1.0.4.0\lib\zlib.net.dll
112 |
113 |
114 |
115 |
116 |
117 | Designer
118 | MSBuild:Compile
119 |
120 |
121 | Designer
122 | MSBuild:Compile
123 |
124 |
125 | App.xaml
126 |
127 |
128 | MainFormTest.xaml
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | ResXFileCodeGenerator
137 | Resources.Designer.cs
138 | Designer
139 |
140 |
141 | True
142 | Resources.resx
143 | True
144 |
145 |
146 |
147 | SettingsSingleFileGenerator
148 | Settings.Designer.cs
149 |
150 |
151 | True
152 | Settings.settings
153 | True
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | False
162 | Microsoft .NET Framework 4.6.1 %28x86 and x64%29
163 | true
164 |
165 |
166 | False
167 | .NET Framework 3.5 SP1
168 | false
169 |
170 |
171 |
172 |
173 |
174 |
175 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/XBC2ModelDecomp/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------