public void SaveMapTextures(Structs.WISMDA WISMDA, string texturesFolderPath) { //"cache/" contains all base colors and normals //"seamwork/tecpac//" contains PBR materials but in severely disjointed fashion //"seamwork/texture//" contains PBR materials and some base color things, seems to be for props exclusively? List <Structs.LBIM> TextureLBIMs = new List <Structs.LBIM>(); List <Structs.XBC1> MapCache = WISMDA.FilesBySearch($"cache/cache_{App.CurFileNameNoExt}").ToList(); List <int> DoubleSize = new List <int>(); for (int i = 0; i < MapCache.Count; i++) { BinaryReader brTexture = new BinaryReader(MapCache[i].Data); MapCache[i].Data.Seek(-0x4, SeekOrigin.End); if (brTexture.ReadInt32() == 0x4D49424C) { Structs.LBIM lbim = ft.ReadLBIM(MapCache[i].Data, brTexture, 0, (int)MapCache[i].Data.Length); lbim.Filename = MapCache[i].Name.Split('/').LastOrDefault(); if (lbim.Type == 66) { DoubleSize.Add(i); } else if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies { TextureLBIMs.Add(lbim); } } else { Structs.LBIM lbim = ft.ReadLBIM(MapCache[DoubleSize.First()].Data, new BinaryReader(MapCache[DoubleSize.First()].Data), 0, (int)MapCache[DoubleSize.First()].Data.Length); //lbim.Filename = MapCache[i].Name.Split('/').LastOrDefault(); lbim.Filename = $"{MapCache[i].Name.Split('/').LastOrDefault()}-yoda-{i}-{DoubleSize.First()}"; lbim.Data = MapCache[i].Data; lbim.Width *= 2; lbim.Height *= 2; TextureLBIMs.Add(lbim); DoubleSize.RemoveAt(0); } } /*for (int i = 0; i < DoubleSize.Count; i++) * { * Structs.LBIM lbim = DoubleSize[i]; * lbim.Data = MapCache[i + (MapCache.Count - DoubleSize.Count)].Data; * lbim.Width *= 2; * lbim.Height *= 2; * TextureLBIMs.Add(lbim); * }*/ /*List<Structs.XBC1> TextureCache = WISMDA.FilesBySearch("cache//texture").ToList(); * TextureCache.AddRange(WISMDA.FilesBySearch("seamwork/tecpac")); * foreach (Structs.XBC1 xbc1 in TextureCache) * { * BinaryReader brTexture = new BinaryReader(xbc1.Data); * xbc1.Data.Seek(-0x4, SeekOrigin.End); * if (brTexture.ReadInt32() == 0x4D49424C) * { * Structs.LBIM lbim = ft.ReadLBIM(xbc1.Data, brTexture, 0, (int)xbc1.Data.Length); * lbim.Filename = xbc1.Name.Split('/').LastOrDefault(); * if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies * TextureLBIMs.Add(lbim); * } * }*/ ft.ReadTextures(new Structs.MSRD { Version = Int32.MaxValue }, texturesFolderPath + @"\CacheAndTecPac", TextureLBIMs); foreach (Structs.LBIM lbim in TextureLBIMs) { lbim.Data.Dispose(); } TextureLBIMs.Clear(); foreach (Structs.XBC1 xbc1 in WISMDA.FilesBySearch("seamwork/texture")) { BinaryReader brTexture = new BinaryReader(xbc1.Data); Structs.SeamworkTexture smwrkTexture = new Structs.SeamworkTexture { TableCount = brTexture.ReadInt32(), TableOffset = brTexture.ReadInt32() }; smwrkTexture.Table = new Structs.SeamworkTextureTable[smwrkTexture.TableCount]; xbc1.Data.Seek(smwrkTexture.TableOffset, SeekOrigin.Begin); for (int i = 0; i < smwrkTexture.TableCount; i++) { smwrkTexture.Table[i] = new Structs.SeamworkTextureTable { Unknown1 = brTexture.ReadInt32(), Size = brTexture.ReadInt32(), Offset = brTexture.ReadInt32(), Unknown2 = brTexture.ReadInt32() }; } foreach (Structs.SeamworkTextureTable table in smwrkTexture.Table) { Structs.LBIM lbim = ft.ReadLBIM(xbc1.Data, brTexture, table.Offset, table.Size); if (lbim.Data != null && lbim.Width > 15 && lbim.Height > 15) //get rid of the tinies { TextureLBIMs.Add(lbim); } } } ft.ReadTextures(new Structs.MSRD { Version = Int32.MaxValue }, texturesFolderPath + @"\SeamworkTexture", TextureLBIMs); foreach (Structs.LBIM lbim in TextureLBIMs) { lbim.Data.Dispose(); } TextureLBIMs.Clear(); }
public void ExtractMaps() { App.PushLog("Extracting large maps can take a decent amount of memory as the program decompresses/handles data."); if (App.ExportTextures) { App.PushLog("In addition, exporting textures can sometimes take even more memory (1-5GB), especially for larger maps."); } if (App.ExportFormat == Structs.ExportFormat.XNALara) { App.PushLog("While XNALara *works*, glTF retains object position, rotation, and scale; it is reccomended to use it for those reasons."); } if (File.Exists(App.CurFilePathAndName + ".winvhe")) { FileStream fsWINVHE = new FileStream(App.CurFilePathAndName + ".winvhe", FileMode.Open, FileAccess.Read); BinaryReader brWINVHE = new BinaryReader(fsWINVHE); Structs.NVMS NVMS = ft.ReadNVMS(fsWINVHE, brWINVHE); FileStream fsWINVDA = new FileStream(App.CurFilePathAndName + ".winvda", FileMode.Open, FileAccess.Read); BinaryReader brWINVDA = new BinaryReader(fsWINVDA); Structs.NVDA NVDA = new Structs.NVDA { Version = brWINVHE.ReadInt32() }; NVDA.XBC1s = new Structs.XBC1[NVMS.NVDATableCount]; for (int i = 0; i < NVMS.NVDATableCount; i++) { NVDA.XBC1s[i] = ft.ReadXBC1(fsWINVDA, brWINVDA, NVMS.NVDAPointers[i].XBC1Offset); } if (App.ExportFormat == Structs.ExportFormat.RawFiles) { DumpXBC1s(fsWINVDA, NVDA.XBC1s); } } List <int> magicOccurences = new List <int>(); FileStream fsWISMDA = new FileStream(App.CurFilePathAndName + ".wismda", FileMode.Open, FileAccess.Read); BinaryReader brWISMDA = new BinaryReader(fsWISMDA); //this thing can be replaced with data in the wismhd, but I can't figure out a consistent way to get the data //for ma01a it's at 0x340 //the table looks like [Int32 offset, Int32 FileSize] //it doesn't seem to have every xbc1 though? byte[] ByteBuffer = File.ReadAllBytes(App.CurFilePathAndName + ".wismda"); byte[] SearchBytes = Encoding.ASCII.GetBytes("xbc1"); for (int i = 0; i <= (ByteBuffer.Length - SearchBytes.Length); i++) { if (ByteBuffer[i] == SearchBytes[0]) { for (int j = 1; j < SearchBytes.Length && ByteBuffer[i + j] == SearchBytes[j]; j++) { if (j == SearchBytes.Length - 1) { magicOccurences.Add(i); i += BitConverter.ToInt32(ByteBuffer, i + 12); } } } } ByteBuffer = new byte[0]; if (magicOccurences.Count > 0) { if (!Directory.Exists(App.CurOutputPath)) { Directory.CreateDirectory(App.CurOutputPath); } } Structs.WISMDA WISMDA = new Structs.WISMDA { Data = fsWISMDA, Files = new Structs.XBC1[magicOccurences.Count] }; for (int i = 0; i < magicOccurences.Count; i++) { WISMDA.Files[i] = ft.ReadXBC1(fsWISMDA, brWISMDA, magicOccurences[i]); } if (App.ExportFormat == Structs.ExportFormat.RawFiles) { DumpXBC1s(WISMDA.Data, WISMDA.Files); } App.PushLog("Finished reading .wismda..."); if (App.ExportFormat != Structs.ExportFormat.RawFiles) { if (App.ExportTextures) { SaveMapTextures(WISMDA, $@"{App.CurOutputPath}\Textures"); } if (App.ExportMapMesh) { SaveMapMeshes(WISMDA); } if (App.ExportMapProps) { SaveMapProps(WISMDA); } } App.PushLog("Done!"); }
public void SaveMapProps(Structs.WISMDA WISMDA) { Structs.XBC1[] MapInfoDatas = WISMDA.FilesBySearch("seamwork/inst/out"); Structs.XBC1[] MapMeshDatas = WISMDA.FilesBySearch("seamwork/inst/mdl"); Structs.XBC1[] MapPosDatas = WISMDA.FilesBySearch("seamwork/inst/pos"); Structs.Mesh[] MapMeshes = new Structs.Mesh[MapMeshDatas.Length]; List <Structs.MXMD> MapMXMDs = new List <Structs.MXMD>(); Structs.SeamworkPropPosition[] MapPositions = new Structs.SeamworkPropPosition[MapPosDatas.Length]; Structs.MapInfo[] MapInfos = new Structs.MapInfo[MapInfoDatas.Length]; Dictionary <Structs.MXMD, Structs.MapInfo> MXMDToMapInfo = new Dictionary <Structs.MXMD, Structs.MapInfo>(); int MapPositionsIndex = 0; for (int i = 0; i < MapPosDatas.Length; i++) { MapPositions[i] = ft.ReadPropPositions(MapPosDatas[i].Data, new BinaryReader(MapPosDatas[i].Data)); } for (int i = 0; i < MapInfoDatas.Length; i++) { MapInfos[i] = ft.ReadMapInfo(MapInfoDatas[i].Data, new BinaryReader(MapInfoDatas[i].Data), true); for (int j = 0; j < MapMeshDatas.Length; j++) { if (MapMeshes[j].VertexTableOffset == 0) { MapMeshes[j] = ft.ReadMesh(MapMeshDatas[j].Data, new BinaryReader(MapMeshDatas[j].Data)); } } if (MapInfos[i].PropPosTableCount == 1 && MapInfos[i].PropPositions[0].PropID != 0) { MapInfos[i].PropPositions.AddRange(MapPositions[MapPositionsIndex].Positions); MapInfos[i].PropPosTableCount = MapInfos[i].PropPositions.Count; MapPositionsIndex++; } //base things off prop position table //the table has the prop ids I need //duplicate ids mean duplicate meshes //get the highest LOD available //scrub through the propid table and only take unique values, dictionary this by index //take the dictionary and get the same indexes out of meshtables //loop through each prop position and build the MXMD based off those + artificial prop table //keep in mind structs are just memory values so I can duplicate things easily Dictionary <int, int> UniqueIDIndex = new Dictionary <int, int>(); for (int j = 0; j < MapInfos[i].PropIDs.Count; j++) { if (!UniqueIDIndex.ContainsKey(MapInfos[i].PropIDs[j])) { UniqueIDIndex.Add(MapInfos[i].PropIDs[j], j); } } Structs.MapInfoMeshTable[] MeshTables = new Structs.MapInfoMeshTable[UniqueIDIndex.Count]; int[] MeshLookup = new int[UniqueIDIndex.Count]; for (int j = 0; j < UniqueIDIndex.Count; j++) { MeshTables[j] = MapInfos[i].MeshTables[UniqueIDIndex.Values.ElementAt(j)]; MeshLookup[j] = MapInfos[i].PropFileLookup[UniqueIDIndex.Values.ElementAt(j)]; } MapInfos[i].PropFileLookup = MeshLookup; for (int j = 0; j < (MapInfos[i].PropPosTableCount / App.PropSplitCount) + 1; j++) { int MeshCount = j + 1 == (MapInfos[i].PropPosTableCount / App.PropSplitCount) + 1 ? MapInfos[i].PropPosTableCount % App.PropSplitCount : App.PropSplitCount; Structs.MXMD FakeMXMD = new Structs.MXMD { Version = 0xFF }; FakeMXMD.Materials = MapInfos[i].Materials; FakeMXMD.ModelStruct.MeshesCount = MeshCount; FakeMXMD.ModelStruct.Meshes = new Structs.MXMDMeshes[MeshCount]; for (int k = 0; k < MeshCount; k++) { Structs.MapInfoPropPosition PropPosition = MapInfos[i].PropPositions[k + (j * App.PropSplitCount)]; FakeMXMD.ModelStruct.Meshes[k].Unknown1 = k + (j * App.PropSplitCount); FakeMXMD.ModelStruct.Meshes[k].TableCount = MeshTables[PropPosition.PropID].MeshCount; FakeMXMD.ModelStruct.Meshes[k].Descriptors = MeshTables[PropPosition.PropID].Descriptors; } MapMXMDs.Add(FakeMXMD); MXMDToMapInfo.Add(FakeMXMD, MapInfos[i]); } } if (App.ShowInfo) { foreach (Structs.MapInfo map in MapInfos) { App.PushLog("PropInfo:" + Structs.ReflectToString(map, 1, 180)); } } for (int i = 0; i < MXMDToMapInfo.Count; i++) { switch (App.ExportFormat) { case Structs.ExportFormat.XNALara: 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}"); break; case Structs.ExportFormat.glTF: 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}"); break; } } }
public void SaveMapMeshes(Structs.WISMDA WISMDA) { Structs.XBC1[] MapInfoDatas = WISMDA.FilesBySearch("bina_basefix.temp_wi"); Structs.MXMD[] MapMXMDs = new Structs.MXMD[MapInfoDatas.Length]; Structs.MapInfo[] MapInfos = new Structs.MapInfo[MapInfoDatas.Length]; for (int i = 0; i < MapInfoDatas.Length; i++) { MapInfos[i] = ft.ReadMapInfo(MapInfoDatas[i].Data, new BinaryReader(MapInfoDatas[i].Data), false); for (int j = 0; j < MapInfos[i].MeshFileLookup.Length; j++) { if (i != 0) { MapInfos[i].MeshFileLookup[j] += (short)(MapInfos[i - 1].MeshFileLookup.Max() + 1); } } MapMXMDs[i] = new Structs.MXMD { Version = 0xFF }; MapMXMDs[i].Materials = MapInfos[i].Materials; MapMXMDs[i].ModelStruct.MeshesCount = MapInfos[i].MeshTableDataCount; MapMXMDs[i].ModelStruct.Meshes = new Structs.MXMDMeshes[MapInfos[i].MeshTables.Length]; for (int j = 0; j < MapInfos[i].MeshTables.Length; j++) { MapMXMDs[i].ModelStruct.Meshes[j].TableCount = MapInfos[i].MeshTables[j].MeshCount; MapMXMDs[i].ModelStruct.Meshes[j].Descriptors = MapInfos[i].MeshTables[j].Descriptors; } } if (App.ShowInfo) { foreach (Structs.MapInfo map in MapInfos) { App.PushLog("MapInfo:" + Structs.ReflectToString(map, 1, 180)); } } Structs.XBC1[] MapMeshDatas = WISMDA.FilesBySearch("basemap/poli//"); Structs.Mesh[] MapMeshes = new Structs.Mesh[MapMeshDatas.Length]; for (int i = 0; i < MapMeshes.Length; i++) { MemoryStream model = MapMeshDatas[i].Data; MapMeshes[i] = ft.ReadMesh(model, new BinaryReader(model)); } for (int i = 0; i < MapInfos.Length; i++) { switch (App.ExportFormat) { case Structs.ExportFormat.XNALara: ft.ModelToASCII(MapMeshes, MapMXMDs[i], new Structs.SKEL { Unknown1 = Int32.MaxValue }, MapInfos[i]); break; case Structs.ExportFormat.glTF: ft.ModelToGLTF(MapMeshes, MapMXMDs[i], new Structs.SKEL { Unknown1 = Int32.MaxValue }, MapInfos[i]); break; } } }