public void Read(byte[] file) { FileData f = new FileData(file); f.Endian = Endianness.Big; f.seek(0); f.seek(4); // magic check SwitchCheck = f.readInt(); //Switch version only has padded magic verNumD = f.readByte(); verNumC = f.readByte(); verNumB = f.readByte(); verNumA = f.readByte(); if (SwitchCheck == 0x20202020) { //Console.WriteLine("Version = " + verNumA + "." + verNumB + "." + verNumC + "." + verNumD); if (f.readShort() == 0xFEFF) { f.Endian = Endianness.Big; } else { f.Endian = Endianness.Little; } f.skip(2); //Size Headeer f.skip(4); //File Name Direct int fileAlignment = f.readInt(); int RelocationTableOffset = f.readInt(); int BfresSize = f.readInt(); string name = f.readString(readOffset(f) + 2, -1); f.skip(4); // Padding long FMDLOffset = f.readInt64(); long FMDLDict = f.readInt64(); long FSKAOffset = f.readInt64(); long FSKADict = f.readInt64(); long FMAAOffset = f.readInt64(); long FMAADict = f.readInt64(); long FVISOffset = f.readInt64(); long FVISDict = f.readInt64(); long FSHUOffset = f.readInt64(); long FSHUDict = f.readInt64(); long FSCNOffset = f.readInt64(); long FSCNDict = f.readInt64(); long BuffMemPool = f.readInt64(); long BuffMemPoolInfo = f.readInt64(); long EMBOffset = f.readInt64(); long EMBDict = f.readInt64(); f.skip(8); // Padding long StringTableOffset = f.readInt64(); int unk11 = f.readInt(); int FMDLCount = f.readShort(); /*FSKACount =*/ f.readShort(); int FMAACount = f.readShort(); int FVISCount = f.readShort(); int FSHUCount = f.readShort(); int FSCNCount = f.readShort(); int EMBCount = f.readShort(); f.skip(12); // Padding // //Console.WriteLine($"FMDLOffset {FMDLOffset} FMDLCount {FMDLCount} FMDLDict {FMDLDict} FSKAOffset {FSKAOffset} FSKADict {FSKADict}"); // //Console.WriteLine($"FMAAOffset {FMAAOffset} FMAADict {FMAADict} FVISOffset {FVISOffset} FSHUOffset {FSKAOffset} FSKADict {FSHUDict}"); //FMDLs -Models- for (int i = 0; i < EMBCount; i++) { f.seek((int)EMBOffset + (i * 16)); int DataOffset = f.readInt(); f.seek(DataOffset); string EmMagic = f.readString(f.pos(), 4); if (EmMagic.Equals("BNTX")) //Textures { f.skip(24); int size = f.readInt(); f.seek(DataOffset); BinaryTexture t = new BinaryTexture(f.GetStream(size)); textures.Add(t.Name, t); } } f.seek((int)FMDLOffset); for (int i = 0; i < FMDLCount; i++) { // //Console.WriteLine("Reading FMDL...."); FMDL_Model model = new FMDL_Model(); //FMDL modelTest = new FMDL(); //modelTest.Read(f); f.skip(16); FMDLheader fmdl_info = new FMDLheader { name = f.readString(f.readInt() + 2, -1), padding = f.readInt(), eofString = f.readInt64(), fsklOff = f.readInt64(), fvtxArrOff = f.readInt64(), fshpOffset = f.readInt64(), fshpIndx = f.readInt64(), fmatOffset = f.readInt64(), fmatIndx = f.readInt64(), UserDataOffset = f.readInt64(), padding1 = f.readInt64(), padding2 = f.readInt64(), fvtxCount = f.readShort(), fshpCount = f.readShort(), fmatCount = f.readShort(), paramCount = f.readShort(), VertCount = f.readInt(), unk2 = f.readInt(), }; int NextFMDL = f.pos(); model.name = fmdl_info.name; //Models.Nodes.Add(fmdl_info.name); // //Console.WriteLine($" Name {fmdl_info.name} eofString {fmdl_info.eofString} fsklOff {fmdl_info.fsklOff}"); // //Console.WriteLine(fmdl_info.fvtxCount); List <FVTXH> FVTXArr = new List <FVTXH>(); f.seek((int)fmdl_info.fvtxArrOff); for (int vtx = 0; vtx < fmdl_info.fvtxCount; vtx++) { // //Console.WriteLine("Reading FVTX...."); f.skip(16); FVTXArr.Add(new FVTXH { attArrOff = f.readInt64(), attIndxOff = f.readInt64(), unk1 = f.readInt64(), unk2 = f.readInt64(), unk3 = f.readInt64(), buffSizeOff = f.readInt64(), buffStrideSizeOff = f.readInt64(), buffArrOff = f.readInt64(), buffOff = f.readInt(), attCount = f.readByte(), buffCount = f.readByte(), sectIndx = f.readShort(), vertCount = f.readInt(), SkinWeightInfluence = f.readInt() }); // //Console.WriteLine($"attCount {FVTXArr[vtx].attCount}"); } f.seek((int)fmdl_info.fmatOffset); List <FMATH> FMATheaders = new List <FMATH>(); for (int mat = 0; mat < fmdl_info.fmatCount; mat++) { // //Console.WriteLine("Reading FMAT...."); f.skip(16); FMATH fmat_info = new FMATH { name = f.readString((int)f.readInt64() + 2, -1), renderInfoOff = f.readInt64(), renderInfoIndx = f.readInt64(), shaderAssignOff = f.readInt64(), u1 = f.readInt64(), texSelOff = f.readInt64(), u2 = f.readInt64(), texAttSelOff = f.readInt64(), texAttIndxOff = f.readInt64(), matParamArrOff = f.readInt64(), matParamIndxOff = f.readInt64(), matParamOff = f.readInt64(), userDataOff = f.readInt64(), userDataIndxOff = f.readInt64(), volatileFlagOffset = f.readInt64(), u3 = f.readInt64(), samplerSlotOff = f.readInt64(), textureSlotOff = f.readInt64(), flags = f.readInt(), //This toggles material visabilty sectIndx = f.readShort(), rendParamCount = f.readShort(), texSelCount = f.readByte(), texAttSelCount = f.readByte(), matParamCount = f.readShort(), u4 = f.readShort(), matParamSize = f.readShort(), rawParamDataSize = f.readShort(), userDataCount = f.readShort(), padding = f.readInt(), }; string FMATNameOffset = fmat_info.name; // //Console.WriteLine($"{FMATNameOffset} {fmat_info.texSelCount} "); FMATheaders.Add(fmat_info); } f.seek((int)fmdl_info.fsklOff + 16); // //Console.WriteLine("Reading FSKL...."); FSKLH fskl_info = new FSKLH { boneIndxOff = f.readInt64(), boneArrOff = f.readInt64(), invIndxArrOff = f.readInt64(), invMatrArrOff = f.readInt64(), padding1 = f.readInt64(), fsklType = f.readInt(), //flags boneArrCount = f.readShort(), invIndxArrCount = f.readShort(), exIndxCount = f.readShort(), u1 = f.readInt(), }; f.seek((int)fmdl_info.fsklOff + 16); FSKLH fskl_infov8 = new FSKLH { boneIndxOff = f.readInt64(), boneArrOff = f.readInt64(), invIndxArrOff = f.readInt64(), invMatrArrOff = f.readInt64(), padding1 = f.readInt64(), padding2 = f.readInt64(), padding3 = f.readInt64(), fsklType = f.readInt(), //flags boneArrCount = f.readShort(), invIndxArrCount = f.readShort(), exIndxCount = f.readShort(), u1 = f.readInt(), }; // //Console.WriteLine($"Bone Count {fskl_info.boneArrCount}"); //FSKL and many other sections will be revised and cleaner later if (verNumB == 8) { model.Node_Array = new int[fskl_infov8.invIndxArrCount + fskl_infov8.exIndxCount]; f.seek((int)fskl_infov8.invIndxArrOff); for (int nodes = 0; nodes < fskl_infov8.invIndxArrCount + fskl_infov8.exIndxCount; nodes++) { model.Node_Array[nodes] = (f.readShort()); } } else { model.Node_Array = new int[fskl_info.invIndxArrCount + fskl_info.exIndxCount]; f.seek((int)fskl_info.invIndxArrOff); for (int nodes = 0; nodes < fskl_info.invIndxArrCount + fskl_info.exIndxCount; nodes++) { model.Node_Array[nodes] = (f.readShort()); } } List <FSHPH> FSHPArr = new List <FSHPH>(); // //Console.WriteLine("Reading FSHP...."); f.seek((int)fmdl_info.fshpOffset); for (int shp = 0; shp < fmdl_info.fshpCount; shp++) { f.skip(16); FSHPArr.Add(new FSHPH { polyNameOff = f.readInt(), u1 = f.readInt(), fvtxOff = f.readInt64(), lodMdlOff = f.readInt64(), fsklIndxArrOff = f.readInt64(), u3 = f.readInt64(), u4 = f.readInt64(), boundingBoxOff = f.readInt64(), radiusOff = f.readInt64(), padding = f.readInt64(), flags = f.readInt(), sectIndx = f.readShort(), fmatIndx = f.readShort(), fsklIndx = f.readShort(), fvtxIndx = f.readShort(), fsklIndxArrCount = f.readShort(), matrFlag = f.readByte(), lodMdlCount = f.readByte(), visGrpCount = f.readInt(), visGrpIndxOff = f.readShort(), visGrpNodeOff = f.readShort(), }); } // //Console.WriteLine("Reading Bones...."); // //Console.WriteLine("Reading FSHP Array...."); //MeshTime!! for (int m = 0; m < FSHPArr.Count; m++) { Mesh poly = new Mesh(); poly.name = f.readString(FSHPArr[m].polyNameOff + 2, -1); // //Console.WriteLine("Polygon = " + poly.name); List <attdata> AttrArr = new List <attdata>(); f.seek((int)FVTXArr[FSHPArr[m].fvtxIndx].attArrOff); for (int att = 0; att < FVTXArr[FSHPArr[m].fvtxIndx].attCount; att++) { string AttType = f.readString(f.readInt() + 2, -1); f.skip(4); //padding f.Endian = Endianness.Big; int vertType = f.readShort(); f.skip(2); f.Endian = Endianness.Little; int buffOff = f.readShort(); int buffIndx = f.readShort(); // //Console.WriteLine($"{AttType} Type = {vertType} Offset = {buffOff} Index = {buffIndx} "); AttrArr.Add(new attdata { attName = AttType, buffIndx = buffIndx, buffOff = buffOff, vertType = vertType }); } //Get RLT real quick for buffer offset f.seek(0x18); int RTLOffset = f.readInt(); f.seek(RTLOffset); f.skip(0x030); int DataStart = f.readInt(); // //Console.WriteLine($"RLT {DataStart}"); List <buffData> BuffArr = new List <buffData>(); f.seek((int)FVTXArr[FSHPArr[m].fvtxIndx].buffArrOff); for (int buff = 0; buff < FVTXArr[FSHPArr[m].fvtxIndx].buffCount; buff++) { buffData data = new buffData(); f.seek((int)FVTXArr[FSHPArr[m].fvtxIndx].buffSizeOff + ((buff) * 0x10)); data.buffSize = f.readInt(); f.seek((int)FVTXArr[FSHPArr[m].fvtxIndx].buffStrideSizeOff + ((buff) * 0x10)); data.strideSize = f.readInt(); //So these work by grabbing the RLT offset first and then adding the buffer offset. Then they keep adding each other by their buffer sizes if (buff == 0) { data.DataOffset = (DataStart + FVTXArr[FSHPArr[m].fvtxIndx].buffOff); } if (buff > 0) { data.DataOffset = BuffArr[buff - 1].DataOffset + BuffArr[buff - 1].buffSize; } if (data.DataOffset % 8 != 0) { data.DataOffset = data.DataOffset + (8 - (data.DataOffset % 8)); } BuffArr.Add(data); // //Console.WriteLine("Data Offset = " + data.DataOffset + " Vertex Buffer Size =" + data.buffSize + " Index = " + buff + " vertexStrideSize size =" + data.strideSize); } for (int v = 0; v < FVTXArr[FSHPArr[m].fvtxIndx].vertCount; v++) { Vertex vert = new Vertex(); for (int attr = 0; attr < AttrArr.Count; attr++) { f.seek(((BuffArr[AttrArr[attr].buffIndx].DataOffset) + (AttrArr[attr].buffOff) + (BuffArr[AttrArr[attr].buffIndx].strideSize * v))); switch (AttrArr[attr].attName) { case "_p0": if (AttrArr[attr].vertType == 1301) { vert.pos = new Vector3 { X = f.readHalfFloat(), Y = f.readHalfFloat(), Z = f.readHalfFloat() } } ; if (AttrArr[attr].vertType == 1304) { vert.pos = new Vector3 { X = f.readFloat(), Y = f.readFloat(), Z = f.readFloat() } } ; break; case "_c0": if (AttrArr[attr].vertType == 1301) { vert.col = new Vector4(f.readHalfFloat(), f.readHalfFloat(), f.readHalfFloat(), f.readHalfFloat()); } if (AttrArr[attr].vertType == 2067) { vert.col = new Vector4 { X = f.readFloat(), Y = f.readFloat(), Z = f.readFloat(), W = f.readFloat() } } ; if (AttrArr[attr].vertType == 267) { vert.col = new Vector4(f.readByte() / 255f, f.readByte() / 255f, f.readByte() / 255f, f.readByte() / 255f); } break; case "_n0": if (AttrArr[attr].vertType == 526) { int normVal = (int)f.readInt(); //Thanks RayKoopa! vert.nrm = new Vector3 { X = sign10Bit((normVal) & 0x3FF) / (float)511, Y = sign10Bit((normVal >> 10) & 0x3FF) / (float)511, Z = sign10Bit((normVal >> 20) & 0x3FF) / (float)511 }; } break; case "_u0": case "color": case "_t0": case "_b0": case "_u1": case "_u2": case "_u3": if (AttrArr[attr].vertType == 265 || AttrArr[attr].vertType == 521) { vert.tx.Add(new Vector2 { X = ((float)f.readByte()) / 255, Y = ((float)f.readByte()) / 255 }); } if (AttrArr[attr].vertType == 274) { vert.tx.Add(new Vector2 { X = ((float)f.readShort()) / 65535, Y = ((float)f.readShort()) / 65535 }); } if (AttrArr[attr].vertType == 530) { vert.tx.Add(new Vector2 { X = ((float)f.readShort()) / 32767, Y = ((float)f.readShort()) / 32767 }); } if (AttrArr[attr].vertType == 1298) { vert.tx.Add(new Vector2 { X = f.readHalfFloat(), Y = f.readHalfFloat() }); } if (AttrArr[attr].vertType == 1303) { vert.tx.Add(new Vector2 { X = f.readFloat(), Y = f.readFloat() }); } break; case "_i0": if (AttrArr[attr].vertType == 770) { vert.node.Add(f.readByte()); vert.weight.Add((float)1.0); } if (AttrArr[attr].vertType == 777) { vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); } if (AttrArr[attr].vertType == 779) { vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); } if (AttrArr[attr].vertType == 523) { vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); vert.node.Add(f.readByte()); } break; case "_w0": if (AttrArr[attr].vertType == 258) { vert.weight.Add((f.readByte()) / (float)255); } if (AttrArr[attr].vertType == 265) { vert.weight.Add((f.readByte()) / (float)255); vert.weight.Add((f.readByte()) / (float)255); } if (AttrArr[attr].vertType == 267) { vert.weight.Add((f.readByte()) / (float)255); vert.weight.Add((f.readByte()) / (float)255); vert.weight.Add((f.readByte()) / (float)255); vert.weight.Add((f.readByte()) / (float)255); } if (AttrArr[attr].vertType == 274) { vert.weight.Add((f.readShort()) / (float)255); vert.weight.Add((f.readShort()) / (float)255); } break; default: // //Console.WriteLine(AttrArr[attr].attName + " Unknown type " + AttrArr[attr].vertType.ToString("x") + " 0x"); break; } } poly.vertices.Add(vert); } int LoadLOD = FSHPArr[m].lodMdlCount - 1; f.seek((int)FSHPArr[m].lodMdlOff); for (int lod = 0; lod < FSHPArr[m].lodMdlCount; lod++) { long SubMeshOff = f.readInt64(); long unk1 = f.readInt64(); long unk2 = f.readInt64(); long indxBuffOff = f.readInt64(); int FaceBuffer = f.readInt(); int PrimativefaceType = f.readInt(); int faceType = f.readInt(); int FaceCount = f.readInt(); int elmSkip = f.readInt(); int subMeshCount = f.readInt(); int temp = f.pos(); f.seek(FaceBuffer + DataStart); if (faceType == 1) { FaceCount = FaceCount / 3; } if (faceType == 2) { FaceCount = FaceCount / 6; } if (lod == LoadLOD) { for (int face = 0; face < FaceCount; face++) { if (faceType == 1) { poly.faces.Add(new List <int> { elmSkip + f.readShort(), elmSkip + f.readShort(), elmSkip + f.readShort() }); } else if (faceType == 2) { poly.faces.Add(new List <int> { elmSkip + f.readInt(), elmSkip + f.readInt(), elmSkip + f.readInt() }); } else { Console.Write("UnkFaceFormat"); } } } f.seek(temp); } f.seek((int)FMATheaders[FSHPArr[m].fmatIndx].texSelOff); List <string> MatTexList = new List <string>(); for (int tex = 0; FMATheaders[FSHPArr[m].fmatIndx].texAttSelCount > tex; tex++) { string TextureName = f.readString((int)f.readInt64() + 2, -1).ToLower(); MatTexList.Add(TextureName); } if (MatTexList.Count > 0) { poly.texNames.Add(MatTexList[0]); } //Console.WriteLine(String.Join(",",MatTexList)); model.poly.Add(poly); } models.Add(model); f.seek(NextFMDL); } } }