private static List <int> BeginReadMGX(BufferedStreamReader streamReader) { string type = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>())); int offset = 0x20; //Base offset due to NIFL header //Deal with deicer's extra header nonsense if (type.Equals("mgx\0")) { streamReader.Seek(0xC, SeekOrigin.Begin); //Basically always 0x60, but some deicer files from the Alpha have 0x50... int headJunkSize = streamReader.Read <int>(); streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current); type = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>())); offset += headJunkSize; } //Proceed based on file variant if (type.Equals("NIFL")) { //There shouldn't be a nifl variant of this for now. MessageBox.Show("Error, NIFL .mgx found"); return(null); } else if (type.Equals("VTBF")) { return(ReadVTBFMGX(streamReader)); } else { MessageBox.Show("Improper File Format!"); return(null); } }
private static LobbyActionCommon BeginReadRebootLAC(BufferedStreamReader streamReader) { string type = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>())); int offset = 0x20; //Base offset due to NIFL header //Deal with deicer's extra header nonsense if (type.Equals("lac\0")) { streamReader.Seek(0xC, SeekOrigin.Begin); //Basically always 0x60, but some deicer files from the Alpha have 0x50... int headJunkSize = streamReader.Read <int>(); streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current); type = Encoding.UTF8.GetString(BitConverter.GetBytes(streamReader.Peek <int>())); offset += headJunkSize; } //Proceed based on file variant if (type.Equals("NIFL")) { //NIFL return(ReadNIFLRebootLAC(streamReader, offset)); } else if (type.Equals("VTBF")) { //Lacs should really never be VTBF... } else { MessageBox.Show("Improper File Format!"); } return(null); }
public static List <stamData> ReadLM(this BufferedStreamReader streamReader) { var stamList = new List <stamData>(); var lmStart = streamReader.Position(); streamReader.Read <int>(); var lmEnd = streamReader.Read <int>() + lmStart; streamReader.Seek(0x8, SeekOrigin.Current); while (streamReader.Position() < lmEnd) { var tag = streamReader.Peek <int>(); switch (tag) { case stam: stamList.Add(streamReader.ReadStam()); break; default: streamReader.SkipBasicAXSStruct(); break; } //Make sure to stop the loop if needed if (stamList[stamList.Count - 1].lastStam == true) { break; } } streamReader.Seek(lmEnd, SeekOrigin.Begin); return(stamList); }
private void FromStream(Stream stream) { // Read in the DLL or EXE and get the timestamp. Stream = new BufferedStreamReader(stream, 4096); StreamStart = stream.Position; Stream.Read <IMAGE_DOS_HEADER>(out _dosHeader); Stream.Seek(_dosHeader.e_lfanew, SeekOrigin.Begin); Stream.Seek(sizeof(uint), SeekOrigin.Current); // NT Header Signature Stream.Read <IMAGE_FILE_HEADER>(out _fileHeader); bool isPe32 = Stream.Peek <ushort>() == 0x10B; if (isPe32) { Stream.Read <IMAGE_OPTIONAL_HEADER32>(out _optionalHeader32); PopulateDataDirectories(Stream, _optionalHeader32.NumberOfRvaAndSizes); } else { Stream.Read <IMAGE_OPTIONAL_HEADER64>(out _optionalHeader64); PopulateDataDirectories(Stream, _optionalHeader64.NumberOfRvaAndSizes); } _imageSectionHeaders = new IMAGE_SECTION_HEADER[FileHeader.NumberOfSections]; for (int x = 0; x < ImageSectionHeaders.Length; ++x) { Stream.Read <IMAGE_SECTION_HEADER>(out ImageSectionHeaders[x], true); } PopulateImportDescriptors(Stream); }
public static bool TryGuess(Stream stream, int streamLength) { var data = new BufferedStreamReader(stream, 4096); var pos = stream.Position; try { var initialPos = data.Position(); // Total Groups data.Read(out int binCount); // Safeguard against unlikely big files. if (binCount is > short.MaxValue or < 1) { return(false); } // Total Items Span <byte> groups = stackalloc byte[binCount]; for (int x = 0; x < binCount; x++) { groups[x] = data.Read <byte>(); } // Alignment data.Seek(Utilities.RoundUp((int)data.Position(), 4) - data.Position(), SeekOrigin.Current); // Now compare against total running file count. int currentCount = 0; int expectedCount = 0; for (int x = 0; x < binCount; x++) { expectedCount = data.Read <short>(); if (currentCount != expectedCount) { return(false); } currentCount += groups[x]; } // Skip group ids. data.Seek(sizeof(short) * binCount, SeekOrigin.Current); // Check offsets. var firstFileOffset = data.Peek <int>(); if (streamLength != -1 && firstFileOffset > streamLength) { return(false); } // Seek to expected first file position. data.Seek(sizeof(int) * currentCount, SeekOrigin.Current); data.Seek(Utilities.RoundUp((int)data.Position(), 16), SeekOrigin.Begin); // Alignment // Try checking if first file is past expected header size, or empty 0. var currentOffset = data.Position() - initialPos; return(firstFileOffset == 0 || firstFileOffset >= (currentOffset)); } finally { stream.Position = pos; } }
private void ReadPartsCMX() { parts = new PartsCMX(); string filePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\cmx\" + cmxFilename + @"_ext\parts.cmx"; using (Stream stream = (Stream) new FileStream(filePath, FileMode.Open)) using (var streamReader = new BufferedStreamReader(stream, 8192)) { int type = streamReader.Peek <int>(); //Deal with deicer's extra header nonsense if (type.Equals(0x786D63)) { streamReader.Seek(0xC, SeekOrigin.Begin); //Basically always 0x60, but some deicer files from the Alpha have 0x50... int headJunkSize = streamReader.Read <int>(); streamReader.Seek(headJunkSize - 0x10, SeekOrigin.Current); type = streamReader.Peek <int>(); } //Seek to and read the DOC tag's streamReader.Seek(0x1C, SeekOrigin.Current); int cmxCount = streamReader.Read <ushort>(); streamReader.Seek(0x2, SeekOrigin.Current); //Read tags, get id from subtag 0xFF, and assign to dictionary with that. for (int i = 0; i < cmxCount; i++) { List <Dictionary <int, object> > data = ReadVTBFTag(streamReader, out string tagType, out int entryCount); switch (tagType) { case "BODY": parts.bodyTags.Add((int)data[0][0xFF], data); break; case "CARM": parts.carmTags.Add((int)data[0][0xFF], data); break; case "CLEG": parts.clegTags.Add((int)data[0][0xFF], data); break; case "BDP1": parts.bdp1Tags.Add((int)data[0][0xFF], data); break; case "BDP2": parts.bdp2Tags.Add((int)data[0][0xFF], data); break; case "FACE": parts.faceTags.Add((int)data[0][0xFF], data); break; case "FCMN": parts.fcmnTags.Add((int)data[0][0xFF], data); break; case "FCP1": parts.fcp1Tags.Add((int)data[0][0xFF], data); break; case "FCP2": parts.fcp2Tags.Add((int)data[0][0xFF], data); break; case "EYE ": parts.eyeTags.Add((int)data[0][0xFF], data); break; case "EYEB": parts.eyeBTags.Add((int)data[0][0xFF], data); break; case "EYEL": parts.eyeLTags.Add((int)data[0][0xFF], data); break; case "HAIR": parts.hairTags.Add((int)data[0][0xFF], data); break; case "COL ": parts.colTags.Add((int)data[0][0xFF], data); break; case "BBLY": parts.bblyTags.Add((int)data[0][0xFF], data); break; case "BCLN": parts.bclnTags.Add((int)data[0][0xFF], data); break; case "LCLN": parts.lclnTags.Add((int)data[0][0xFF], data); break; case "ACLN": parts.aclnTags.Add((int)data[0][0xFF], data); break; case "ICLN": parts.iclnTags.Add((int)data[0][0xFF], data); break; default: throw new Exception($"Unexpected tag type {tagType}"); break; } } } }
public static void ReadBM(this BufferedStreamReader streamReader, List <MeshDefinitions> defs, ipnbStruct tempLpnbList, List <stamData> stamList, long last__oaPos) { int counter = 0; MeshDefinitions mesh = null; var bmStart = streamReader.Position(); streamReader.Read <int>(); var bmEnd = streamReader.Read <int>() + bmStart; streamReader.Seek(0x8, SeekOrigin.Current); while (streamReader.Position() < bmEnd) { var tag = streamReader.Peek <int>(); switch (tag) { case ydbm: if (mesh != null) { Debug.WriteLine(defs.Count); defs.Add(mesh); } mesh = new MeshDefinitions(); mesh.oaPos = last__oaPos; mesh.lpnbStr = tempLpnbList; mesh.ydbmStr = streamReader.ReadYdbm(); if (stamList.Count > counter) { mesh.stam = stamList[counter]; } else if (stamList.Count > 0) { mesh.stam = stamList[stamList.Count - 1]; } else { mesh.stam = null; } counter++; break; case lxdi: mesh.lxdiStr = streamReader.ReadLxdi(); break; case salv: mesh.salvStr = streamReader.ReadSalv(); mesh.vtxe = GenerateGenericPSO2VTXE(mesh.salvStr.vertDef0, mesh.salvStr.vertDef1, mesh.salvStr.vertDef2, mesh.salvStr.vertDef3, mesh.salvStr.vertDef4, mesh.salvStr.vertLen); break; case ipnb: mesh.ipnbStr = streamReader.ReadIpnb(); break; default: streamReader.SkipBasicAXSStruct(); break; } } if (mesh != null) { Debug.WriteLine(defs.Count); defs.Add(mesh); } }
//We're not realistically going to fully convert everything, but we can get vertex data and bones if nothing else //Returns an aqp ready for the ConvertToNGSPSO2Mesh method public static AquaObject ReadAXS(string filePath, out AquaNode aqn) { AquaObject aqp = new NGSAquaObject(); aqn = new AquaNode(); using (Stream stream = (Stream) new FileStream(filePath, FileMode.Open)) using (var streamReader = new BufferedStreamReader(stream, 8192)) { Debug.WriteLine(Path.GetFileName(filePath)); long last__oaPos = 0; eertStruct eertNodes = null; ipnbStruct tempLpnbList = null; List <ffubStruct> ffubList = new List <ffubStruct>(); List <XgmiStruct> xgmiList = new List <XgmiStruct>(); List <string> texNames = new List <string>(); List <MeshDefinitions> meshDefList = new List <MeshDefinitions>(); List <stamData> stamList = new List <stamData>(); Dictionary <string, rddaStruct> rddaList = new Dictionary <string, rddaStruct>(); Dictionary <string, rddaStruct> imgRddaList = new Dictionary <string, rddaStruct>(); Dictionary <string, rddaStruct> vertRddaList = new Dictionary <string, rddaStruct>(); Dictionary <string, rddaStruct> faceRddaList = new Dictionary <string, rddaStruct>(); Dictionary <string, int> xgmiIdByCombined = new Dictionary <string, int>(); Dictionary <string, int> xgmiIdByUnique = new Dictionary <string, int>(); ffubStruct imgFfub = new ffubStruct(); ffubStruct vertFfub = new ffubStruct(); ffubStruct faceFfub = new ffubStruct(); var fType = streamReader.Read <int>(); var fsaLen = streamReader.Read <int>(); streamReader.Seek(0x8, SeekOrigin.Current); //Go to Vert definition, node, material, and misc data while (streamReader.Position() < fsaLen) { var tag = streamReader.Peek <int>(); var test = Encoding.UTF8.GetString(BitConverter.GetBytes(tag)); Debug.WriteLine(streamReader.Position().ToString("X")); Debug.WriteLine(test); switch (tag) { case __oa: last__oaPos = streamReader.Position(); streamReader.Seek(0xD0, SeekOrigin.Current); break; case FIA: streamReader.Seek(0x10, SeekOrigin.Current); break; case __lm: var stam = streamReader.ReadLM(); if (stam != null && stam.Count > 0) { stamList = stam; } break; case __bm: streamReader.ReadBM(meshDefList, tempLpnbList, stamList, last__oaPos); break; case lpnb: tempLpnbList = streamReader.ReadIpnb(); break; case eert: eertNodes = streamReader.ReadEert(); break; //case ssem: // streamReader.SkipBasicAXSStruct(); //Maybe use for material data later. Remember to store ordered id for _bm mesh entries for this // break; case Xgmi: var xgmiData = streamReader.ReadXgmi(); if (!xgmiIdByCombined.ContainsKey(xgmiData.stamCombinedId)) { xgmiIdByCombined.Add(xgmiData.stamCombinedId, xgmiList.Count); } xgmiIdByUnique.Add(xgmiData.stamUniqueId, xgmiList.Count); xgmiList.Add(xgmiData); break; default: streamReader.SkipBasicAXSStruct(); break; } } //Assemble aqn from eert if (eertNodes != null) { for (int i = 0; i < eertNodes.boneCount; i++) { var rttaNode = eertNodes.rttaList[i]; Matrix4x4 mat = Matrix4x4.Identity; mat *= Matrix4x4.CreateScale(rttaNode.scale); var rotation = Matrix4x4.CreateFromQuaternion(rttaNode.quatRot); mat *= rotation; mat *= Matrix4x4.CreateTranslation(rttaNode.pos); var parentId = rttaNode.parentNodeId; //If there's a parent, multiply by it if (i != 0) { if (rttaNode.parentNodeId == -1) { parentId = 0; } var pn = aqn.nodeList[parentId]; var parentInvTfm = new Matrix4x4(pn.m1.X, pn.m1.Y, pn.m1.Z, pn.m1.W, pn.m2.X, pn.m2.Y, pn.m2.Z, pn.m2.W, pn.m3.X, pn.m3.Y, pn.m3.Z, pn.m3.W, pn.m4.X, pn.m4.Y, pn.m4.Z, pn.m4.W); Matrix4x4.Invert(parentInvTfm, out var invParentInvTfm); mat = mat * invParentInvTfm; } else { parentId = -1; } rttaNode.nodeMatrix = mat; //Create AQN node NODE aqNode = new NODE(); aqNode.animatedFlag = 1; aqNode.parentId = parentId; aqNode.unkNode = -1; aqNode.pos = rttaNode.pos; aqNode.eulRot = QuaternionToEuler(rttaNode.quatRot); if (Math.Abs(aqNode.eulRot.Y) > 120) { aqNode.scale = new Vector3(-1, -1, -1); } else { aqNode.scale = new Vector3(1, 1, 1); } Matrix4x4.Invert(mat, out var invMat); aqNode.m1 = new Vector4(invMat.M11, invMat.M12, invMat.M13, invMat.M14); aqNode.m2 = new Vector4(invMat.M21, invMat.M22, invMat.M23, invMat.M24); aqNode.m3 = new Vector4(invMat.M31, invMat.M32, invMat.M33, invMat.M34); aqNode.m4 = new Vector4(invMat.M41, invMat.M42, invMat.M43, invMat.M44); aqNode.boneName = rttaNode.nodeName; Debug.WriteLine($"{i} " + aqNode.boneName.GetString()); aqn.nodeList.Add(aqNode); } } //Go to mesh buffers streamReader.Seek(fsaLen, SeekOrigin.Begin); if (streamReader.Position() >= stream.Length) { return(null); } var fType2 = streamReader.Read <int>(); //Read mesh data if (fType2 != FMA) { Debug.WriteLine("Unexpected struct in location of FMA!"); return(null); } streamReader.Seek(0xC, SeekOrigin.Current); //Skip daeh int meshSettingLen = streamReader.ReadDAEH(); //Read ffub and rdda //Count mesh count here for now and store starts and ends of data long meshSettingStart = streamReader.Position(); while (streamReader.Position() < meshSettingStart + meshSettingLen) { streamReader.ReadFFUBorRDDA(ffubList, rddaList, imgRddaList, vertRddaList, faceRddaList, ref imgFfub, ref vertFfub, ref faceFfub); } int meshCount = meshDefList.Count; //Read image data var ext = Path.GetExtension(filePath); for (int i = 0; i < xgmiList.Count; i++) { var xgmiData = xgmiList[i]; var imgBufferInfo = imgRddaList[$"{xgmiData.md5_1.ToString("X")}{xgmiData.md5_2.ToString("X")}"]; Debug.WriteLine($"Image set {i}: " + imgBufferInfo.md5_1.ToString("X") + " " + imgBufferInfo.dataStartOffset.ToString("X") + " " + imgBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset).ToString("X")); var position = meshSettingStart + imgFfub.dataStartOffset + imgBufferInfo.dataStartOffset; var buffer = streamReader.ReadBytes(position, imgBufferInfo.dataSize); var outImagePath = filePath.Replace(ext, $"_tex_{i}" + ".dds"); texNames.Add(Path.GetFileName(outImagePath)); try { string name = Path.GetFileName(filePath); Debug.WriteLine($"{name}_xgmi_{ i}"); var image = AIFMethods.GetImage(xgmiData, buffer); File.WriteAllBytes(filePath.Replace(ext, $"_tex_{i}" + ".dds"), image); } catch (Exception exc) { #if DEBUG string name = Path.GetFileName(filePath); Debug.WriteLine($"Extract tex {i} failed."); File.WriteAllBytes($"C:\\{name}_xgmiHeader_{i}.bin", xgmiData.GetBytes()); File.WriteAllBytes($"C:\\{name}_xgmiBuffer_{i}.bin", buffer); Debug.WriteLine(exc.Message); #endif } buffer = null; } //Read model data - Since ffubs are initialized, they default to 0. int vertFfubPadding = imgFfub.structSize; int faceFfubPadding = imgFfub.structSize + vertFfub.structSize; for (int i = 0; i < meshCount; i++) { var mesh = meshDefList[i]; var nodeMatrix = Matrix4x4.Identity; for (int bn = 0; bn < eertNodes.boneCount; bn++) { var node = eertNodes.rttaList[bn]; if (node.meshNodePtr == mesh.oaPos) { nodeMatrix = node.nodeMatrix; break; } } var vertBufferInfo = vertRddaList[$"{mesh.salvStr.md5_1.ToString("X")}{mesh.salvStr.md5_2.ToString("X")}"]; var faceBufferInfo = faceRddaList[$"{mesh.lxdiStr.md5_1.ToString("X")}{mesh.lxdiStr.md5_2.ToString("X")}"]; Debug.WriteLine($"Vert set {i}: " + vertBufferInfo.md5_1.ToString("X") + " " + vertBufferInfo.dataStartOffset.ToString("X") + " " + vertBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset).ToString("X")); Debug.WriteLine($"Face set {i}: " + faceBufferInfo.md5_1.ToString("X") + " " + faceBufferInfo.dataStartOffset.ToString("X") + " " + faceBufferInfo.toTagStruct.ToString("X") + " " + (meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset).ToString("X")); //Vert data var vertCount = vertBufferInfo.dataSize / mesh.salvStr.vertLen; AquaObject.VTXL vtxl = new AquaObject.VTXL(); streamReader.Seek((meshSettingStart + vertFfubPadding + vertFfub.dataStartOffset + vertBufferInfo.dataStartOffset), SeekOrigin.Begin); AquaObjectMethods.ReadVTXL(streamReader, mesh.vtxe, vtxl, vertCount, mesh.vtxe.vertDataTypes.Count); vtxl.convertToLegacyTypes(); //Fix vert transforms for (int p = 0; p < vtxl.vertPositions.Count; p++) { vtxl.vertPositions[p] = Vector3.Transform(vtxl.vertPositions[p], nodeMatrix); if (vtxl.vertNormals.Count > 0) { vtxl.vertNormals[p] = Vector3.TransformNormal(vtxl.vertNormals[p], nodeMatrix); } } //Handle bone indices if (mesh.ipnbStr != null && mesh.ipnbStr.shortList.Count > 0) { vtxl.bonePalette = (mesh.ipnbStr.shortList.ConvertAll(delegate(short num) { return((ushort)num); })); //Convert the indices based on the global bone list as pso2 will expect for (int bn = 0; bn < vtxl.bonePalette.Count; bn++) { vtxl.bonePalette[bn] = (ushort)mesh.lpnbStr.shortList[vtxl.bonePalette[bn]]; } } aqp.vtxlList.Add(vtxl); //Face data AquaObject.GenericTriangles genMesh = new AquaObject.GenericTriangles(); int faceIndexCount = faceBufferInfo.dataSize / 2; List <int> faceList = new List <int>(); streamReader.Seek((meshSettingStart + faceFfubPadding + faceFfub.dataStartOffset + faceBufferInfo.dataStartOffset), SeekOrigin.Begin); int maxStep = streamReader.Read <ushort>(); for (int fId = 0; fId < faceIndexCount - 1; fId++) { faceList.Add(streamReader.Read <ushort>()); } //Convert the data to something usable with this algorithm and then destripify it. List <ushort> triList = unpackInds(inverseWatermarkTransform(faceList, maxStep)).ConvertAll(delegate(int num) { return((ushort)num); }); var tempFaceData = new AquaObject.stripData() { triStrips = triList, format0xC33 = true, triIdCount = triList.Count }; genMesh.triList = tempFaceData.GetTriangles(); //Extra genMesh.vertCount = vertCount; genMesh.matIdList = new List <int>(new int[genMesh.triList.Count]); for (int j = 0; j < genMesh.matIdList.Count; j++) { genMesh.matIdList[j] = aqp.tempMats.Count; } aqp.tempTris.Add(genMesh); //Material var mat = new AquaObject.GenericMaterial(); mat.texNames = GetTexNames(mesh, xgmiIdByCombined, xgmiIdByUnique, texNames); aqp.tempMats.Add(mat); } return(aqp); } }
//Takes in bytes of a *n.rel file from PSO //To convert to PSO2's units, we set the scale to 1/10th scale public PSONRelConvert(byte[] file, string fileName = null, float scale = 0.1f, string outFolder = null) { fileSize = file.Length; rootScale = scale; List <dSection> dSections = new List <dSection>(); streamReader = new BufferedStreamReader(new MemoryStream(file), 8192); //Get header offset streamReader.Seek(file.Length - 0x10, SeekOrigin.Begin); //Check Endianness. No offset should ever come close to half of the int max value. be = streamReader.PeekBigEndianPrimitiveUInt32() < streamReader.Peek <uint>(); if (be) { MessageBox.Show("Sorry, Gamecube n.rel files are not supported at this time."); } uint tableOfs = streamReader.ReadBE <uint>(be); //Read header streamReader.Seek(tableOfs, SeekOrigin.Begin); var header = ReadRelHeader(streamReader, be); //Read draw Sections streamReader.Seek(header.drawOffset, SeekOrigin.Begin); for (int i = 0; i < header.drawCount; i++) { dSection section = new dSection(); section.id = streamReader.ReadBE <int>(be); section.pos = streamReader.ReadBEV3(be); var rotX = streamReader.ReadBE <int>(be); var rotY = streamReader.ReadBE <int>(be); var rotZ = streamReader.ReadBE <int>(be); section.rot = new Vector3((float)(rotX * BAMSvalue), (float)(rotY * BAMSvalue), (float)(rotZ * BAMSvalue)); section.radius = streamReader.ReadBE <float>(be); section.staticOffset = streamReader.ReadBE <uint>(be); section.animatedOffset = streamReader.ReadBE <uint>(be); section.staticCount = streamReader.ReadBE <uint>(be); section.animatedCount = streamReader.ReadBE <uint>(be); section.end = streamReader.ReadBE <uint>(be); dSections.Add(section); } //Get texture names streamReader.Seek(header.nameInfoOffset, SeekOrigin.Begin); var nameOffset = streamReader.ReadBE <uint>(be); var nameCount = streamReader.ReadBE <uint>(be); streamReader.Seek(nameOffset, SeekOrigin.Begin); List <uint> nameOffsets = new List <uint>(); for (int i = 0; i < nameCount; i++) { nameOffsets.Add(streamReader.ReadBE <uint>(be)); var unk0 = streamReader.ReadBE <uint>(be); var unk1 = streamReader.ReadBE <uint>(be); if (unk0 != 0) { Console.WriteLine($"Iteration {i} unk0 == {unk0}"); } if (unk1 != 0) { Console.WriteLine($"Iteration {i} unk1 == {unk1}"); } } foreach (uint offset in nameOffsets) { streamReader.Seek(offset, SeekOrigin.Begin); texNames.Add(AquaObjectMethods.ReadCString(streamReader)); } //If there's an .xvm, dump that too with texture names from the .rel if (fileName != null) { //Naming patterns for *n.rel files are *_12n.rel for example or *n.rel vs *.xvm. We can determine which we have, edit, and proceed var basename = fileName.Substring(0, fileName.Length - 5); string xvmName = null; if (basename.ElementAt(basename.Length - 3) == '_') { xvmName = basename.Substring(0, basename.Length - 3) + ".xvm"; } else { xvmName = basename + ".xvm"; } ExtractXVM(xvmName, texNames, outFolder); } //Create root AQN node NODE aqNode = new NODE(); aqNode.animatedFlag = 1; aqNode.parentId = -1; aqNode.unkNode = -1; aqNode.pos = new Vector3(); aqNode.eulRot = new Vector3(); aqNode.scale = new Vector3(1, 1, 1); aqNode.m1 = new Vector4(1, 0, 0, 0); aqNode.m2 = new Vector4(0, 1, 0, 0); aqNode.m3 = new Vector4(0, 0, 1, 0); aqNode.m4 = new Vector4(0, 0, 0, 1); aqNode.boneName.SetString("RootNode"); nodes.Add(aqNode); //Loop through nodes and parse geometry for (int i = 0; i < dSections.Count; i++) { var matrix = Matrix4x4.Identity; matrix *= Matrix4x4.CreateScale(1, 1, 1); var rotation = Matrix4x4.CreateRotationX(dSections[i].rot.X) * Matrix4x4.CreateRotationY(dSections[i].rot.Y) * Matrix4x4.CreateRotationZ(dSections[i].rot.Z); matrix *= rotation; matrix *= Matrix4x4.CreateTranslation(dSections[i].pos * rootScale); //Read static meshes List <staticMeshOffset> staticMeshOffsets = new List <staticMeshOffset>(); streamReader.Seek(dSections[i].staticOffset, SeekOrigin.Begin); for (int st = 0; st < dSections[i].staticCount; st++) { staticMeshOffsets.Add(ReadStaticMeshOffset(streamReader, be)); } for (int ofs = 0; ofs < staticMeshOffsets.Count; ofs++) { streamReader.Seek(staticMeshOffsets[ofs].offset, SeekOrigin.Begin); readNode(matrix, 0); } //Read animated meshes List <animMeshOffset> animatedMeshOffsets = new List <animMeshOffset>(); streamReader.Seek(dSections[i].animatedOffset, SeekOrigin.Begin); for (int st = 0; st < dSections[i].animatedCount; st++) { animatedMeshOffsets.Add(ReadAnimMeshOffset(streamReader, be)); } for (int ofs = 0; ofs < animatedMeshOffsets.Count; ofs++) { streamReader.Seek(animatedMeshOffsets[ofs].offset, SeekOrigin.Begin); readNode(matrix, 0); } } //Set material names for (int i = 0; i < aqObj.tempMats.Count; i++) { aqObj.tempMats[i].matName = $"PSOMat {i}"; } }