using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace OpenVIII { public static class BinaryReaderExt { #region Methods public static Vector3 ReadVertex(this BinaryReader br) => new Vector3(br.ReadVertexDim(), 0 - br.ReadVertexDim(), br.ReadVertexDim()); public static float ReadVertexDim(this BinaryReader br) => br.ReadInt16() / 2000.0f; #endregion Methods } } namespace OpenVIII.Battle { /// /// Magic Obj Reader /// /// /// /// /// /// /// public class Mag { #region Fields public uint pBones, pTextureLimit, pGeometry, pSCOT, pTexture; private static readonly ushort[] _knownPolygons = new ushort[] { 0x2, 0x6, 0x7, 0x8, 0x9, 0xC, 0x10, 0x12, 0x11, 0x13, }; public List Geometries; #endregion Fields #region Constructors public Mag(string filename, BinaryReader br) { FileName = filename; br.BaseStream.Seek(0, SeekOrigin.Begin); if (TryReadTIM(br) == null) { isTIM = false; br.BaseStream.Seek(0, SeekOrigin.Begin); //Offset Description //0x00 Probably always null uint pPadding = br.ReadUInt32(); if (pPadding != 0) return; //0x04 Probably bones/ animation data, might be 0x00 pBones = br.ReadUInt32(); //0x08 Unknown(used to determinate texture size *), might be 0x64 pTextureLimit = br.ReadUInt32(); //0x0C Geometry pointer, might be 0xAC pGeometry = br.ReadUInt32(); //0x10 SCOT pointer, might be 0x00 pSCOT = br.ReadUInt32(); //0x14 Texture pointer, might be 0x30 pTexture = br.ReadUInt32(); //0x18 == 0x98 //0x1C == 0xAC if (pBones > br.BaseStream.Length || pTextureLimit > br.BaseStream.Length || pGeometry > br.BaseStream.Length || pSCOT > br.BaseStream.Length || pTexture > br.BaseStream.Length) { return; } if (FileName == "c:\\ff8\\data\\eng\\battle\\mag201_b.19") { } else if (FileName == "c:\\ff8\\data\\eng\\battle\\mag204_b.04") { } isPackedMag = true; ReadGeometry(br); ReadTextures(br); } } #endregion Constructors #region Properties public byte DataType => getValue(2); public string FileName { get; private set; } public byte IDnumber => getValue(1); public bool isPackedMag { get; private set; } = false; public bool isTIM { get; private set; } = false; public byte SequenceNumber => getValue(3); public TIM2[] TIM { get; private set; } private bool bFileNameTest => FileName != null && Path.GetExtension(FileName).Trim('.').Length == 3; /// /// if quit loop because unknown type. /// public int UnknownType { get; private set; } = int.MinValue; #endregion Properties #region Methods public TIM2[] TryReadTIM(BinaryReader br) { try { TIM2 tim = new TIM2(br, noExec: true); if (tim.NOT_TIM) return TIM = null; isTIM = true; return TIM = new TIM2[] { tim }; } catch (InvalidDataException) { return TIM = null; } } private byte getValue(int index) => bFileNameTest ? byte.Parse(FileName.Substring(FileName.Length - index, 1), System.Globalization.NumberStyles.HexNumber) : (byte)0xff; private void ReadGeometry(BinaryReader br) { if (pGeometry == 0) return; br.BaseStream.Seek(pGeometry, SeekOrigin.Begin); int count = br.ReadInt32(); //count = count > 0x24 ? 0x24 : count; // unsure why. List positions = new List(); while (count-- > 0) { uint pos = br.ReadUInt32(); if (pos > 0 && pos + pGeometry < br.BaseStream.Length) positions.Add(pos + pGeometry); } Geometries = new List(positions.Count); foreach (uint pos in positions) { Geometry g = new Geometry(); const int PassFromStart = 24; bool OnlyVertex = true; br.BaseStream.Seek(pos, SeekOrigin.Begin); count = br.ReadInt32(); if (count > 12u) continue; if (count != 2u) OnlyVertex = false; br.BaseStream.Seek(pos + 8, SeekOrigin.Begin); uint _relativeJump = br.ReadUInt32() + pos; br.BaseStream.Seek(pos + PassFromStart, SeekOrigin.Begin); int _vertexCount = br.ReadUInt16() * 8; br.BaseStream.Seek(pos + PassFromStart - 4, SeekOrigin.Begin); uint _verticesOffset = br.ReadUInt16() + pos; ReadVertices(); if (OnlyVertex) { continue; } br.BaseStream.Seek(_relativeJump, SeekOrigin.Begin); ushort _polygonType = br.ReadUInt16(); ushort polygons = br.ReadUInt16(); bool _generatefaces; bool isknownpolygon() => _knownPolygons.Any(x => x == _polygonType); long localoffset = _relativeJump + 4; int safeHandle = 0; while (!(_generatefaces = !isknownpolygon())) { switch (_polygonType) { case 0x2: case 0x7: GetTriangle(20, 0xC); break; case 0x6: GetTriangle(12, 0x4); break; case 0x8: GetTriangle(20, 0xA); break; case 0x9: GetTriangle(28, 0x12); break; case 0xC: GetQuad(28, 0x14); break; case 0x12: GetQuad(24, 0xC); break; case 0x13: GetQuad(36, 0x18); break; case 0x10: GetQuad(12, 0x4); break; case 0x11: GetQuad(24, 0x10); break; } br.BaseStream.Seek(localoffset + safeHandle, SeekOrigin.Begin); if (br.BaseStream.Position + 4 > br.BaseStream.Length) return; if (br.ReadUInt32() == uint.MaxValue) break; localoffset += safeHandle; _polygonType = br.ReadUInt16(); polygons = br.ReadUInt16(); localoffset += 4; void GetTriangle(int cnt, int off0) { int off1 = off0 + 2, off2 = off0 + 4; int size = polygons * cnt; for (int i = 0; i < size; i += cnt) { g.Triangles.Add(new Vector3(GetPolygonIndex(localoffset + i + off0), GetPolygonIndex(localoffset + i + off1), GetPolygonIndex(localoffset + i + off2))); } safeHandle = size; } void GetQuad(int cnt, int off0) { int off1 = off0 + 2, off2 = off0 + 6, off3 = off0 + 4; int size = polygons * cnt; for (int i = 0; i < size; i += cnt) { g.Quads.Add(new Vector4(GetPolygonIndex(localoffset + i + off0), GetPolygonIndex(localoffset + i + off1), GetPolygonIndex(localoffset + i + off2), GetPolygonIndex(localoffset + i + off3))); } safeHandle = size; } ushort GetPolygonIndex(long offset) { br.BaseStream.Seek(offset, SeekOrigin.Begin); ushort temp = checked((ushort)(br.ReadUInt16() / 8)); Debug.Assert(temp < g.Vertices.Count); //return temp == 0 ? 1 : (temp / 8) + 1; return temp; } } if (_generatefaces) { UnknownType = _polygonType; } void ReadVertices() { br.BaseStream.Seek(_verticesOffset, SeekOrigin.Begin); if (_vertexCount % 8 != 0) return; for (int i = 0; i < _vertexCount / 8; i++) { br.BaseStream.Seek(_verticesOffset + i * 8, SeekOrigin.Begin); g.Vertices.Add(br.ReadVertex()); } } Geometries.Add(g); } } private TIM2[] ReadTextures(BinaryReader br) { //this doesn't sound like a tim file per documentation. if (pTexture == 0 || pTexture >= pTextureLimit || pTexture + pTextureLimit >= br.BaseStream.Length || (pTextureLimit - pTexture) % 4 != 0 ) return null; br.BaseStream.Seek(pTexture, SeekOrigin.Begin); List positions = new List(); //Debug.Assert(pTextureSize > 0 && pTextureSize < br.BaseStream.Length); while (pTextureLimit > 0 && br.BaseStream.Position < pTextureLimit && br.BaseStream.Position + 4 < br.BaseStream.Length) { uint pos = br.ReadUInt32(); if (pos != 0) positions.Add(pos + pTexture); } if (positions.Count > 0) { //textures here? return null; } else return null; } #endregion Methods #region Classes public class Geometry { #region Constructors public Geometry() { Vertices = new List(); Triangles = new List(); Quads = new List(); } #endregion Constructors #region Properties public List Quads { get; private set; } public List Triangles { get; private set; } public List Vertices { get; private set; } #endregion Properties } #endregion Classes } }