public static LTBFile Read(FileInfo info) { var bFlip = info.Name.StartsWith("PV"); var ltbFile = new LTBFile(); var br = new ExtendedBinaryReader(info.OpenRead()); #region Header var header = br.ReadUInt16(); if (header > 20) { br.Close(); var lzmaStream = new LzmaDecodeStream(info.OpenRead()); var ms = new MemoryStream(); lzmaStream.CopyTo(ms); if (ms.Length == 0) { Console.WriteLine($"{info.Name} is not a vaild LTB file."); return(null); } br = new ExtendedBinaryReader(ms); br.Skip(0, true); } // Skip header br.Skip(0x14, true); uint version = br.ReadUInt32(); uint nKeyFrame = br.ReadUInt32(); uint nAnim = br.ReadUInt32(); uint numBones = br.ReadUInt32(); uint nPieces = br.ReadUInt32(); uint nChildModels = br.ReadUInt32(); uint nTris = br.ReadUInt32(); uint nVerts = br.ReadUInt32(); uint nVertexWeights = br.ReadUInt32(); uint nLODs = br.ReadUInt32(); uint nSockets = br.ReadUInt32(); uint nWeightSets = br.ReadUInt32(); uint nStrings = br.ReadUInt32(); uint StringLengths = br.ReadUInt32(); uint VertAnimDataSize = br.ReadUInt32(); uint nAnimData = br.ReadUInt32(); string cmdString = br.ReadStringWithUInt16Length(); float globalRadius = br.ReadSingle(); uint iNumEnabledOBBs = br.ReadUInt32(); if (iNumEnabledOBBs != 0) { throw new Exception("LTB with OBB infomations are not supported"); } uint numMesh = br.ReadUInt32(); #endregion var romanisation = new McCuneReischauerRomanisation { PreserveNonKoreanText = true }; #region Mesh nodes for (int i = 0; i < numMesh; i++) { string meshName = br.ReadStringWithUInt16Length(ltbEncode); meshName = romanisation.RomaniseText(meshName); foreach (var kvp in replaceDictionary) { meshName = meshName.Replace(kvp.Key, kvp.Value); } meshName = meshName.ToLower(); uint numLod = br.ReadUInt32(); Console.WriteLine($"{meshName} - {numLod} Lods"); meshName = meshName.ToLower(); br.Skip((int)numLod * 4 + 8); int materialIndex = -1; if (!ltbFile.Materials.Any(material => material.Name == meshName)) { ltbFile.Materials.Add(new SEModelMaterial { Name = "mtl_" + meshName, MaterialData = new SEModelSimpleMaterial { DiffuseMap = meshName + ".png" } }); materialIndex = ltbFile.Materials.Count - 1; } else { materialIndex = ltbFile.Materials.FindIndex(mtl => mtl.Name == meshName); } for (int iLod = 0; iLod < numLod; iLod++) { var mesh = new SEModelMesh(); mesh.AddMaterialIndex(materialIndex); var nNumTex = br.ReadUInt32(); const int MAX_PIECE_TEXTURES = 4; for (int iTex = 0; iTex < MAX_PIECE_TEXTURES; iTex++) { // Texture index br.ReadUInt32(); } var renderStyle = br.ReadUInt32(); var nRenderPriority = br.ReadByte(); var lodType = (PieceType)br.ReadUInt32(); var lodSize = br.ReadUInt32(); if (lodSize != 0) { uint numVerts = br.ReadUInt32(); uint numTris = br.ReadUInt32(); uint iMaxBonesPerTri = br.ReadUInt32(); uint iMaxBonesPerVert = br.ReadUInt32(); Console.WriteLine($" Lod {iLod}: \n Vertex count: {numVerts}\n Triangle count: {numTris}"); bool bReIndexBones = false, bUseMatrixPalettes = false; if (lodType == PieceType.SkelMesh) { bReIndexBones = br.ReadBoolean(); } DataType[] streamData = { (DataType)br.ReadUInt32(), (DataType)br.ReadUInt32(), (DataType)br.ReadUInt32(), (DataType)br.ReadUInt32() }; uint rigidBone = uint.MaxValue; if (lodType == PieceType.RigidMesh) { rigidBone = br.ReadUInt32(); } else if (lodType == PieceType.SkelMesh) { bUseMatrixPalettes = br.ReadBoolean(); } else { throw new Exception("Unsupported lod type"); } if (bUseMatrixPalettes) { uint iMinBone = br.ReadUInt32(); uint iMaxBone = br.ReadUInt32(); } var boneMap = new List <uint>(); if (bReIndexBones) { uint reindexBoneMapSize = br.ReadUInt32(); for (int iMap = 0; iMap < reindexBoneMapSize; iMap++) { boneMap.Add(br.ReadUInt32()); } } for (int iStream = 0; iStream < 4; ++iStream) { if (!streamData[iStream].HasFlag(DataType.Position)) { continue; } for (int iVert = 0; iVert < numVerts; iVert++) { var v = new SEModelVertex(); if (streamData[iStream].HasFlag(DataType.Position)) { v.Position = new Vector3 { X = br.ReadSingle(), Y = br.ReadSingle(), Z = br.ReadSingle(), }; if (bFlip) { v.Position.X *= -1; } if (rigidBone == uint.MaxValue) { var weightSum = 0.0f; var maxWeight = bUseMatrixPalettes ? iMaxBonesPerVert : iMaxBonesPerTri; for (int iWeight = 0; iWeight < maxWeight - 1; iWeight++) { var weight = br.ReadSingle(); if (weight > 1) { throw new Exception("wtf"); } weightSum += weight; v.Weights.Add(new SEModelWeight { BoneIndex = uint.MaxValue, BoneWeight = weight }); } if (1.0f - weightSum > float.Epsilon) { v.Weights.Add(new SEModelWeight { BoneIndex = uint.MaxValue, BoneWeight = 1.0f - weightSum }); } if (bUseMatrixPalettes) { for (int iWeight = 0; iWeight < 4; iWeight++) { var boneIndex = br.ReadByte(); if (bReIndexBones) { boneIndex = (byte)boneMap[boneIndex]; } if (v.Weights.Count > iWeight) { v.Weights[iWeight].BoneIndex = boneIndex; } } } } else { if (rigidBone >= numBones || rigidBone < 0) { throw new Exception("wtf"); } v.Weights.Add(new SEModelWeight { BoneIndex = rigidBone, BoneWeight = 1.0f }); } } if (streamData[iStream].HasFlag(DataType.Normal)) { v.VertexNormal = new Vector3 { X = br.ReadSingle(), Y = br.ReadSingle(), Z = br.ReadSingle(), }; if (bFlip) { v.VertexNormal.X *= -1; } } if (streamData[iStream].HasFlag(DataType.Color)) { br.Skip(4); } if (streamData[iStream].HasFlag(DataType.UVSets1)) { v.UVSets.Add(new Vector2 { X = br.ReadSingle(), Y = br.ReadSingle() }); if (v.UVSets[0].X > 1.0f) { v.UVSets[0].X -= 1.0f; } } if (streamData[iStream].HasFlag(DataType.UVSets2)) { br.Skip(8); } if (streamData[iStream].HasFlag(DataType.UVSets3)) { br.Skip(8); } if (streamData[iStream].HasFlag(DataType.UVSets4)) { br.Skip(8); } if (streamData[iStream].HasFlag(DataType.BasisVectors)) { br.Skip(24); } if (v.Position == null || v.WeightCount == 0) { throw new Exception("wtf"); } mesh.AddVertex(v); } } for (uint iTriangle = 0; iTriangle < numTris; iTriangle++) { mesh.AddFace(br.ReadUInt16(), br.ReadUInt16(), br.ReadUInt16()); } if (lodType == PieceType.SkelMesh && !bUseMatrixPalettes) { var boneComboCount = br.ReadUInt32(); for (int iCombo = 0; iCombo < boneComboCount; iCombo++) { int m_BoneIndex_Start = br.ReadUInt16(); int m_BoneIndex_End = m_BoneIndex_Start + br.ReadUInt16(); Console.WriteLine($" Weight Combo: {m_BoneIndex_Start} to {m_BoneIndex_End}"); var bones = br.ReadBytes(4); uint m_iIndexIndex = br.ReadUInt32(); for (int iVertex = m_BoneIndex_Start; iVertex < m_BoneIndex_End; iVertex++) { for (int iBone = 0; iBone < 4 && bones[iBone] != 0xFF; iBone++) { if (mesh.Verticies[iVertex].Weights.Count <= iBone) { break; } mesh.Verticies[iVertex].Weights[iBone].BoneIndex = bones[iBone]; } } } } ltbFile.Meshes.Add(mesh); br.Skip(br.ReadByte()); } } } #endregion #region Bones uint[] boneTree = new uint[numBones]; for (int i = 0; i < numBones; i++) { var boneName = br.ReadStringWithUInt16Length(); var boneId = br.ReadByte(); var num2 = br.ReadUInt16(); Matrix4x4 transformMatrix = new Matrix4x4(); for (int j = 0; j < 4; j++) { for (int k = 0; k < 4; k++) { transformMatrix[j, k] = br.ReadSingle(); } } boneTree[i] = br.ReadUInt32(); var bone = new SEModelBone { BoneName = boneName.Replace('.', '_').Replace('-', '_').Replace(' ', '_'), GlobalRotation = new Quaternion(transformMatrix), GlobalPosition = new Vector3(transformMatrix) }; if (bFlip) { bone.GlobalPosition.X *= -1; bone.GlobalRotation.Y *= -1; bone.GlobalRotation.Z *= -1; } // rotate root bone; if (boneId == 0) { bone.GlobalRotation *= globalRotation; } ltbFile.Bones[boneId] = bone; } uint[] nSubbone = new uint[numBones]; nSubbone[0] = boneTree[0]; ltbFile.Bones[0].BoneParent = -1; // Build bone tree for (byte i = 1; i < numBones; i++) { nSubbone[i] = boneTree[i]; for (int j = i - 1; j >= 0; j--) { if (nSubbone[j] > 0) { nSubbone[j]--; ltbFile.Bones[i].BoneParent = j; break; } } } #endregion #region Random stuff Console.WriteLine("\nInternal filenames:"); var childModelCount = br.ReadUInt32(); for (int i = 0; i < childModelCount; i++) { Console.WriteLine(br.ReadStringWithUInt16Length()); br.Skip((int)br.ReadUInt32() * 4); } br.Skip(4); #endregion #region Animations if (nAnim > 0) { var animationCount = br.ReadUInt32(); Console.WriteLine($"\nAnimation count: {animationCount}\n"); for (int i = 0; i < animationCount; i++) { var seanim = new SEAnim(); var dim = new Vector3 { X = br.ReadSingle(), Y = br.ReadSingle(), Z = br.ReadSingle(), }; var animName = br.ReadStringWithUInt16Length(); Console.Write(animName); var compressionType = (AnimCompressionType)br.ReadUInt32(); var interpolationMS = br.ReadUInt32(); var keyFrameCount = br.ReadUInt32(); Console.WriteLine($" has {keyFrameCount} keyframes"); for (int iKeyFrame = 0; iKeyFrame < keyFrameCount; iKeyFrame++) { var time = br.ReadUInt32(); var animString = br.ReadStringWithUInt16Length(); if (!string.IsNullOrEmpty(animString)) { seanim.AddNoteTrack(animString, iKeyFrame); } } for (byte iBone = 0; iBone < numBones; iBone++) { if (compressionType != AnimCompressionType.None) { uint pFrames = br.ReadUInt32(); for (int iKeyFrame = 0; iKeyFrame < pFrames; iKeyFrame++) { var v = new Vector3(br.ReadInt16() / 16.0, br.ReadInt16() / 16.0, br.ReadInt16() / 16.0); if (bFlip) { v.X *= -1; } seanim.AddTranslationKey(ltbFile.Bones[iBone].BoneName, iKeyFrame, v.X, v.Y, v.Z); } uint rFrames = br.ReadUInt32(); for (int iKeyFrame = 0; iKeyFrame < rFrames; iKeyFrame++) { var q = new Quaternion(br.ReadInt16() / 16.0, -br.ReadInt16() / 16.0, -br.ReadInt16() / 16.0, br.ReadInt16() / 16.0); if (bFlip) { q.Y *= -1; q.Z *= -1; } // rotate root bone; if (iBone == 0) { q *= globalRotation; } seanim.AddRotationKey(ltbFile.Bones[iBone].BoneName, iKeyFrame, q.X, q.Y, q.Z, q.W); } } else if (compressionType == AnimCompressionType.None) { bool isVertexAnim = br.ReadBoolean(); if (isVertexAnim) { throw new Exception("Vertex animation not supported!"); } else { for (int iKeyFrame = 0; iKeyFrame < keyFrameCount; iKeyFrame++) { var v = new Vector3(-br.ReadSingle(), br.ReadSingle(), br.ReadSingle()); seanim.AddTranslationKey(ltbFile.Bones[iBone].BoneName, iKeyFrame, v.X, v.Y, v.Z); } for (int iKeyFrame = 0; iKeyFrame < keyFrameCount; iKeyFrame++) { var q = new Quaternion(br.ReadSingle(), -br.ReadSingle(), -br.ReadSingle(), br.ReadSingle()); // rotate root bone; if (iBone == 0) { q *= globalRotation; } seanim.AddRotationKey(ltbFile.Bones[iBone].BoneName, iKeyFrame, q.X, q.Y, q.Z, q.W); } } } } ltbFile.Animations.Add(animName + ".seanim", seanim); } } #endregion return(ltbFile); }
static void Main(string[] args) { // SELib Unit Test Console.Title = "SELib Unit Tests"; Console.WriteLine("SELib Unit Tests\n"); Console.WriteLine("- SEAnims\n"); #region SEAnim { // Log Console.Write("-- Test 1 "); // Make it var anim = new SEAnim(); // Add some keys anim.AddTranslationKey("shoulder", 0, 0, 0, 0); anim.AddTranslationKey("shoulder", 5, 1, 1, 1); anim.AddTranslationKey("shoulder", 10, 10, 10, 10); anim.AddTranslationKey("shoulder", 30, 20, 20, 20); anim.AddTranslationKey("shoulder", 40, 30, 30, 30); // Save it anim.Write("test1.seanim"); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 2 "); // Make it var anim = new SEAnim(); // Add some keys anim.AddTranslationKey("shoulder", 0, 0, 0, 0); anim.AddTranslationKey("shoulder", 5, 1, 1, 1); anim.AddTranslationKey("shoulder", 10, 10, 10, 10); anim.AddTranslationKey("shoulder", 30, 20, 20, 20); anim.AddTranslationKey("shoulder", 40, 30, 30, 30); // Add some scale anim.AddScaleKey("shoulder", 0, 1, 1, 1); anim.AddScaleKey("shoulder", 50, 3, 3, 3); // Save it anim.Write("test2.seanim"); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 3 "); // Make it var anim = new SEAnim(); // Add some keys anim.AddTranslationKey("shoulder", 0, 0, 0, 0); anim.AddTranslationKey("shoulder", 5, 1, 1, 1); anim.AddTranslationKey("shoulder", 10, 10, 10, 10); anim.AddTranslationKey("shoulder", 30, 20, 20, 20); anim.AddTranslationKey("shoulder", 40, 30, 30, 30); // Add some scale anim.AddScaleKey("shoulder", 0, 1, 1, 1); anim.AddScaleKey("shoulder", 50, 3, 3, 3); // Add some note anim.AddNoteTrack("hello_world", 3); anim.AddNoteTrack("bye", 50); // Save it anim.Write("test3.seanim"); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 4 "); // Make it var anim = new SEAnim(); // Add some keys anim.AddTranslationKey("shoulder", 0, 0, 0, 0); anim.AddTranslationKey("shoulder", 5, 1, 1, 1); anim.AddTranslationKey("shoulder", 10, 10, 10, 10); anim.AddTranslationKey("shoulder", 30, 20, 20, 20); anim.AddTranslationKey("shoulder", 40, 30, 30, 30); // Add some scale anim.AddScaleKey("shoulder", 0, 1, 1, 1); anim.AddScaleKey("shoulder", 50, 3, 3, 3); // Add some note anim.AddNoteTrack("hello_world", 3); anim.AddNoteTrack("bye", 50); // Save it (Really, we don't need doubles!!) anim.Write("test4.seanim", true); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 5 "); // Make it var anim = new SEAnim(); // Add some keys anim.AddTranslationKey("shoulder", 0, 0, 0, 0); anim.AddTranslationKey("shoulder", 5, 1, 1, 1); anim.AddTranslationKey("shoulder", 10, 10, 10, 10); anim.AddTranslationKey("shoulder", 30, 20, 20, 20); anim.AddTranslationKey("shoulder", 40, 30, 30, 30); // Add some scale anim.AddScaleKey("shoulder", 0, 1, 1, 1); anim.AddScaleKey("shoulder", 50, 3, 3, 3); // Add some rot anim.AddRotationKey("shoulder", 0, 0, 0, 0, 1); anim.AddRotationKey("shoulder", 50, 0.3, 0.2, 0.5, 1); // Random quat for test // Add some note anim.AddNoteTrack("hello_world", 3); anim.AddNoteTrack("bye", 50); // Save it anim.Write("test5.seanim"); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 6 "); // Read from test 5 var anim = SEAnim.Read("test5.seanim"); // Check data System.Diagnostics.Debug.Assert(anim.AnimationNotetracks.Count == 2); System.Diagnostics.Debug.Assert(anim.AnimationPositionKeys.Count == 1); System.Diagnostics.Debug.Assert(anim.AnimationRotationKeys.Count == 1); System.Diagnostics.Debug.Assert(anim.AnimationScaleKeys.Count == 1); System.Diagnostics.Debug.Assert(anim.BoneCount == 1); System.Diagnostics.Debug.Assert(anim.FrameCount == 51); System.Diagnostics.Debug.Assert(anim.FrameRate == 30.0); // Version System.Diagnostics.Debug.Assert(anim.APIVersion == "v1.0.1"); // Check functions System.Diagnostics.Debug.Assert(anim.RenameBone("shoulder", "shoulder") == false); System.Diagnostics.Debug.Assert(anim.RenameBone("shoulder", "new_shoulder") == true); // Done Console.WriteLine("DONE!"); } #endregion Console.WriteLine("\n- SEModels\n"); #region SEModel { // Log Console.Write("-- Test 1 "); // Make it var model = new SEModel(); // Add some bones model.AddBone("bone_0001", -1, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0002", 0, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0003", 0, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0004", 2, Vector3.Zero, Quaternion.Identity, new Vector3(22, 22, 22), Quaternion.Identity, Vector3.One); // Save it model.Write("test1.semodel"); // Done Console.WriteLine("DONE!"); } { // Log Console.Write("-- Test 2 "); // Make it var model = new SEModel(); // Allow globals too model.ModelBoneSupport = ModelBoneSupportTypes.SupportsBoth; // Add some bones model.AddBone("bone_0001", -1, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0002", 0, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0003", 0, Vector3.Zero, Quaternion.Identity, Vector3.Zero, Quaternion.Identity, Vector3.One); model.AddBone("bone_0004", 2, Vector3.Zero, Quaternion.Identity, new Vector3(22, 22, 22), Quaternion.Identity, Vector3.One); // Save it model.Write("test2.semodel"); // Done Console.WriteLine("DONE!"); } #endregion // Pause Console.Write("\nPress any key to continue..."); Console.ReadKey(); }