public UnknownBlock(ref BinaryReader reader, string type) { BlockType = type; // Switch from big-endian to little-endian int dataLength = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); int subdataLength = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); Unknown0C = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); if (dataLength > 0) { Data = reader.ReadBytes(dataLength); Utils.ReadPadding(ref reader); } if (subdataLength > 0) { byte[] subdata = reader.ReadBytes(subdataLength); Utils.ReadPadding(ref reader); BinaryReader subReader = new BinaryReader(new MemoryStream(subdata)); Children = SrdFile.ReadBlocks(ref subReader); subReader.Close(); } }
public VtxBlock(ref BinaryReader reader) { // Switch from big-endian to little-endian int dataLength = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); int subdataLength = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); Unknown0C = BitConverter.ToInt32(Utils.SwapEndian(reader.ReadBytes(4))); // Read and parse data if (dataLength > 0) { byte[] data = reader.ReadBytes(dataLength); Utils.ReadPadding(ref reader); Unknown10 = reader.ReadInt32(); Unknown14 = reader.ReadInt32(); VertexCount = reader.ReadInt32(); Unknown1C = reader.ReadInt32(); } if (subdataLength > 0) { byte[] subdata = reader.ReadBytes(subdataLength); Utils.ReadPadding(ref reader); BinaryReader subReader = new BinaryReader(new MemoryStream(subdata)); Children = SrdFile.ReadBlocks(ref subReader); subReader.Close(); } }
static void Main(string[] args) { Console.WriteLine("SRD Tool by CaptainSwag101\n" + "Version 0.0.2, built on 2019-08-15\n"); FileInfo info = new FileInfo(args[0]); if (!info.Exists) { Console.WriteLine("ERROR: \"{0}\" does not exist.", args[0]); return; } if (info.Extension.ToLower() != ".srd") { Console.WriteLine("ERROR: Input file does not have the \".srd\" extension."); return; } SrdFile srd = new SrdFile(); srd.Load(args[0]); Console.WriteLine("\"{0}\" contains the following blocks:\n", info.FullName); PrintBlocks(srd.Blocks); Console.WriteLine("Press Enter to close..."); Console.Read(); //srd.Save(args[0] + ".test"); }
private static Node GetNodeTree(SrdFile srd, List <Material> materials, List <Mesh> meshes) { var scn = srd.Blocks.Where(b => b is ScnBlock).First() as ScnBlock; var scnResources = scn.Children[0] as RsiBlock; var treBlocks = srd.Blocks.Where(b => b is TreBlock).ToList(); // Create a manual root node in case the scene has multiple roots Node RootNode = new Node("RootNode"); foreach (TreBlock tre in treBlocks) { var treResources = tre.Children[0] as RsiBlock; var flattenedTreeNodes = tre.RootNode.Flatten().ToList(); // This is done in two passes, one to generate the list of nodes, second to assign children to parents var flattenedNodes = new List <Node>(); // Generate a list of real nodes in the scene foreach (TreeNode treeNode in flattenedTreeNodes) { Node n = new Node(treeNode.StringValue); foreach (Mesh mesh in meshes) { if (mesh.Name == n.Name) { n.MeshIndices.Add(meshes.IndexOf(mesh)); } } flattenedNodes.Add(n); //nodeNameList.Add(n.Name); } foreach (TreeNode treeNode in flattenedTreeNodes) { int currentNodeIndex = flattenedTreeNodes.IndexOf(treeNode); foreach (TreeNode childTreeNode in treeNode) { int childNodeIndex = flattenedTreeNodes.IndexOf(childTreeNode); flattenedNodes[currentNodeIndex].Children.Add(flattenedNodes[childNodeIndex]); } } // Add any root nodes to the manual root node foreach (string rootNodeName in scn.SceneRootNodes) { foreach (Node matchingNode in flattenedNodes.Where(n => n.Name == rootNodeName)) { RootNode.Children.Add(matchingNode); } } } return(RootNode); }
static void Main(string[] args) { Console.WriteLine("SRD Tool by CaptainSwag101\n" + "Version 1.1.0, built on 2020-10-15\n"); // Setup text encoding so we can use Shift-JIS text later on Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); if (args.Length == 0) { Console.WriteLine("Usage: SrdTool.exe <SRD file> (optional auto-execute commands and parameters, encapsulated in {})"); Console.WriteLine("Example: SrdTool.exe test.srd {extract_textures} {n} {print_blocks} {exit}"); return; } FileInfo info = new FileInfo(args[0]); if (!info.Exists) { Console.WriteLine($"ERROR: \"{args[0]}\" does not exist."); return; } // If the file exists and is valid, load it Srd = new SrdFile(); SrdName = args[0]; // Search for linked files like SRDI and SRDV SrdiName = SrdName.Remove(SrdName.LastIndexOf(new FileInfo(SrdName).Extension)) + ".srdi"; if (!File.Exists(SrdiName)) { SrdiName = string.Empty; } SrdvName = SrdName.Remove(SrdName.LastIndexOf(new FileInfo(SrdName).Extension)) + ".srdv"; if (!File.Exists(SrdvName)) { SrdvName = string.Empty; } Srd.Load(SrdName, SrdiName, SrdvName); // Combine any remaining args into a single long string to be broken down by our regex Queue <string>?autoExecQueue = null; if (args.Length > 1) { autoExecQueue = new Queue <string>(); StringBuilder remainingArgsBuilder = new StringBuilder(); remainingArgsBuilder.AppendJoin(" ", args[1..args.Length]);
public static void ExportModel(SrdFile srd, string exportName) { // Setup the scene for the root of our model Scene scene = new Scene(); // Get all our various data scene.Materials.AddRange(GetMaterials(srd)); scene.Meshes.AddRange(GetMeshes(srd, scene.Materials)); scene.RootNode = GetNodeTree(srd, scene.Materials, scene.Meshes); AssimpContext exportContext = new AssimpContext(); var exportFormats = exportContext.GetSupportedExportFormats(); foreach (var format in exportFormats) { if (format.FileExtension == "gltf") { exportContext.ExportFile(scene, exportName + '.' + format.FileExtension, format.FormatId); break; } } }
private static List <Material> GetMaterials(SrdFile srd) { var materialList = new List <Material>(); var matBlocks = srd.Blocks.Where(b => b is MatBlock).ToList(); var txiBlocks = srd.Blocks.Where(b => b is TxiBlock).ToList(); foreach (MatBlock mat in matBlocks) { var matResources = mat.Children[0] as RsiBlock; Material material = new Material(); material.Name = matResources.ResourceStringList[0]; foreach (var pair in mat.MapTexturePairs) { // Find the TXI block associated with the current map TxiBlock matchingTxi = new TxiBlock(); foreach (TxiBlock txi in txiBlocks) { var txiResources = txi.Children[0] as RsiBlock; if (txiResources.ResourceStringList[0] == pair.Value) { matchingTxi = txi; break; } } TextureSlot texSlot = new TextureSlot { FilePath = matchingTxi.TextureFilename, Mapping = TextureMapping.FromUV, UVIndex = 0, }; // Determine map type if (pair.Key.StartsWith("COLORMAP")) { if (matchingTxi.TextureFilename.StartsWith("lm")) { texSlot.TextureType = TextureType.Lightmap; } else { texSlot.TextureType = TextureType.Diffuse; } } else if (pair.Key.StartsWith("NORMALMAP")) { texSlot.TextureType = TextureType.Normals; } else if (pair.Key.StartsWith("SPECULARMAP")) { texSlot.TextureType = TextureType.Specular; } else if (pair.Key.StartsWith("TRANSPARENCYMAP")) { texSlot.TextureType = TextureType.Opacity; } else if (pair.Key.StartsWith("REFLECTMAP")) { texSlot.TextureType = TextureType.Reflection; } else { Console.WriteLine($"WARNING: Texture map type {pair.Key} is not currently supported."); } texSlot.TextureIndex = material.GetMaterialTextureCount(texSlot.TextureType); if (!material.AddMaterialTexture(texSlot)) { Console.WriteLine($"WARNING: Adding map ({pair.Key}, {pair.Value}) did not update or create new data!"); } } materialList.Add(material); } return(materialList); }
private static List <Mesh> GetMeshes(SrdFile srd, List <Material> materials) { var meshList = new List <Mesh>(); //var treBlocks = srd.Blocks.Where(b => b is TreBlock).ToList(); var sklBlocks = srd.Blocks.Where(b => b is SklBlock).ToList(); var mshBlocks = srd.Blocks.Where(b => b is MshBlock).ToList(); var vtxBlocks = srd.Blocks.Where(b => b is VtxBlock).ToList(); // For debugging //var vtxNameList = new List<string>(); //var mshNameList = new List<string>(); //foreach (VtxBlock vtx in vtxBlocks) //{ // vtxNameList.Add((vtx.Children[0] as RsiBlock).ResourceStringList[0]); //} //foreach (MshBlock msh in mshBlocks) //{ // mshNameList.Add((msh.Children[0] as RsiBlock).ResourceStringList[0]); //} // Iterate through each VTX block simultaneously and extract the data we need var extractedData = new List <MeshData>(); foreach (MshBlock msh in mshBlocks) { var vtx = vtxBlocks.Where(b => (b.Children[0] as RsiBlock).ResourceStringList[0] == msh.VertexBlockName).First() as VtxBlock; var vtxResources = vtx.Children[0] as RsiBlock; // Extract position data using BinaryReader positionReader = new BinaryReader(new MemoryStream(vtxResources.ExternalData[0])); var curVertexList = new List <Vector3>(); var curNormalList = new List <Vector3>(); var curTexcoordList = new List <Vector2>(); var curWeightList = new List <float>(); foreach (var section in vtx.VertexDataSections) { positionReader.BaseStream.Seek(section.StartOffset, SeekOrigin.Begin); for (int vNum = 0; vNum < vtx.VertexCount; ++vNum) { long oldPos = positionReader.BaseStream.Position; switch (vtx.VertexDataSections.IndexOf(section)) { case 0: // Vertex/Normal data (and Texture UV for boneless models) { Vector3 vertex; vertex.X = positionReader.ReadSingle() * -1.0f; // X vertex.Y = positionReader.ReadSingle(); // Y vertex.Z = positionReader.ReadSingle(); // Z curVertexList.Add(vertex); Vector3 normal; normal.X = positionReader.ReadSingle() * -1.0f; // X normal.Y = positionReader.ReadSingle(); // Y normal.Z = positionReader.ReadSingle(); // Z curNormalList.Add(normal); if (vtx.VertexDataSections.Count == 1) { Console.WriteLine($"Mesh type: {vtx.MeshType}"); Vector2 texcoord; texcoord.X = float.PositiveInfinity; texcoord.X = positionReader.ReadSingle(); // U while (float.IsNaN(texcoord.X) || !float.IsFinite(texcoord.X)) { texcoord.X = positionReader.ReadSingle(); } texcoord.Y = positionReader.ReadSingle(); // V, invert for non-glTF exports while (float.IsNaN(texcoord.Y) || !float.IsFinite(texcoord.Y)) { texcoord.Y = positionReader.ReadSingle(); } if (float.IsNaN(texcoord.X) || float.IsNaN(texcoord.Y) || Math.Abs(texcoord.X) > 1 || Math.Abs(texcoord.Y) > 1) { Console.WriteLine($"INVALID UVs DETECTED!"); } curTexcoordList.Add(texcoord); } } break; case 1: // Bone weights? { var weightsPerVert = (section.SizePerVertex / sizeof(float)); // TODO: Is this always 8? for (int wNum = 0; wNum < weightsPerVert; ++wNum) { curWeightList.Add(positionReader.ReadSingle()); } } break; case 2: // Texture UVs (only for models with bones) { Vector2 texcoord; texcoord.X = positionReader.ReadSingle(); // U texcoord.Y = positionReader.ReadSingle(); // V, invert for non-glTF exports curTexcoordList.Add(texcoord); } break; default: Console.WriteLine($"WARNING: Unknown vertex sub-block index {vtx.VertexDataSections.IndexOf(section)} is present in VTX block {vtxBlocks.IndexOf(vtx)}!"); break; } // Skip data we don't currently use, though I may add support for this data later long remainingBytes = section.SizePerVertex - (positionReader.BaseStream.Position - oldPos); positionReader.BaseStream.Seek(remainingBytes, SeekOrigin.Current); } } // Extract index data using BinaryReader indexReader = new BinaryReader(new MemoryStream(vtxResources.ExternalData[1])); var curIndexList = new List <ushort[]>(); while (indexReader.BaseStream.Position < indexReader.BaseStream.Length) { ushort[] indices = new ushort[3]; for (int i = 0; i < 3; ++i) { ushort index = indexReader.ReadUInt16(); // We need to reverse the order of the indices to prevent the normals // from becoming permanently flipped due to the clockwise/counter-clockwise // order of the indices determining the face's direction indices[3 - (i + 1)] = index; } curIndexList.Add(indices); } // Add the extracted data to our list extractedData.Add(new MeshData(curVertexList, curNormalList, curTexcoordList, curIndexList, vtx.BindBoneList, curWeightList)); } // Now that we've extracted the data we need, convert it to Assimp equivalents for (int d = 0; d < extractedData.Count; ++d) { MeshData meshData = extractedData[d]; var msh = mshBlocks[d] as MshBlock; var mshResources = msh.Children[0] as RsiBlock; Mesh mesh = new Mesh() { Name = mshResources.ResourceStringList[0], PrimitiveType = PrimitiveType.Triangle, MaterialIndex = materials.IndexOf(materials.Where(m => m.Name == msh.MaterialName).First()), }; // Add vertices foreach (var vertex in meshData.Vertices) { Vector3D vec3D = new Vector3D(vertex.X, vertex.Y, vertex.Z); mesh.Vertices.Add(vec3D); } // Add normals foreach (var normal in meshData.Normals) { Vector3D vec3D = new Vector3D(normal.X, normal.Y, normal.Z); mesh.Normals.Add(vec3D); } // Add UVs mesh.UVComponentCount[0] = 2; mesh.TextureCoordinateChannels[0] = new List <Vector3D>(); foreach (var uv in meshData.Texcoords) { Vector3D vec3D = new Vector3D(uv.X, uv.Y, 0.0f); mesh.TextureCoordinateChannels[0].Add(vec3D); } // Add faces foreach (var indexArray in meshData.Indices) { Face face = new Face(); foreach (ushort index in indexArray) { face.Indices.Add(index); } mesh.Faces.Add(face); } // Add bones foreach (string boneName in meshData.Bones) { var boneInfoList = (sklBlocks.First() as SklBlock).BoneInfoList; var matchingBone = boneInfoList.Where(b => b.BoneName == boneName).First(); Bone bone = new Bone(); bone.Name = boneName; mesh.Bones.Add(bone); } // Add weights to those bones int weightsPerVert = (meshData.Weights.Count / meshData.Vertices.Count); for (int vNum = 0; vNum < meshData.Vertices.Count; ++vNum) { for (int wNum = 0; wNum < weightsPerVert; ++wNum) { // Make sure the bone actually exists if (mesh.BoneCount <= (wNum % weightsPerVert)) { break; } VertexWeight vWeight; vWeight.VertexID = vNum; vWeight.Weight = meshData.Weights[wNum + (vNum * weightsPerVert)]; mesh.Bones[wNum % weightsPerVert].VertexWeights.Add(vWeight); } } meshList.Add(mesh); } return(meshList); }