using Microsoft.Xna.Framework; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace OpenVIII.Battle { /// /// Magic Obj Reader /// /// /// /// /// /// /// public partial 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 static IEnumerable WithGeometries => All?.Where(x => (x.Geometries?.Count ?? 0) > 0) ?? null; public static IEnumerable Packed => All?.Where(x => x.isPackedMag) ?? null; public static IEnumerable MagTim => All?.Where(x => x.isTIM) ?? null; public static IEnumerable Unknown => All?.Where(x => x.UnknownType > 0).Select(x => x.UnknownType) ?? null; public static List All; public static void Init() { return; //All = new List(); //ArchiveBase aw = ArchiveWorker.Load(Memory.Archives.A_MAGIC); ////aw.CacheFS(); //IEnumerable magFiles = aw.GetListOfFiles().Where(x => Path.GetFileName(Path.GetDirectoryName(x)).IndexOf("magic", System.StringComparison.OrdinalIgnoreCase) >= 0 || Path.GetFileName(Path.GetDirectoryName(x)).IndexOf("battle", System.StringComparison.OrdinalIgnoreCase) >= 0 && Path.GetFileName(x).StartsWith("mag", System.StringComparison.OrdinalIgnoreCase)).Distinct(); //var v = magFiles.ToList(); //foreach (KeyValuePair i in magFiles.ToDictionary(x => x, x => aw.GetBinaryFile(x))) //{ // All.Add(Mag.Load(i.Key, i.Value)); //} } public static Mag Load(string filename, byte[] buffer) { using (var br = new BinaryReader(new MemoryStream(buffer, false))) return Load(filename, br); } public static Mag Load(string filename, BinaryReader br) { br.BaseStream.Seek(0, SeekOrigin.Begin); var m = new Mag { FileName = filename }; if (m.TryReadTIM(br) != null) return m; m.isTIM = false; br.BaseStream.Seek(0, SeekOrigin.Begin); //Offset Description //0x00 Probably always null if (!br.Read(out uint pPadding)) return null; if (pPadding != 0) return m; //0x04 Probably bones/ animation data, might be 0x00 if (!br.Read(out m.pBones)) return null; //0x08 Unknown(used to determinate texture size *), might be 0x64 if (!br.Read(out m.pTextureLimit)) return null; //0x0C Geometry pointer, might be 0xAC if (!br.Read(out m.pGeometry)) return null; //0x10 SCOT pointer, might be 0x00 if (!br.Read(out m.pSCOT)) return null; //0x14 Texture pointer, might be 0x30 if (!br.Read(out m.pTexture)) return null; //0x18 == 0x98 //0x1C == 0xAC if (m.pBones > br.BaseStream.Length || m.pTextureLimit > br.BaseStream.Length || m.pGeometry > br.BaseStream.Length || m.pSCOT > br.BaseStream.Length || m.pTexture > br.BaseStream.Length) { return m; } if (m.FileName == "c:\\ff8\\data\\eng\\battle\\mag201_b.19") { } else if (m.FileName == "c:\\ff8\\data\\eng\\battle\\mag204_b.04") { } m.isPackedMag = true; m.ReadGeometry(br); m.ReadTextures(br); return m; } #endregion Constructors #region Properties public byte DataType => getValue(2); public string FileName { get; set; } public byte IDnumber => getValue(1); public bool isPackedMag { get; set; } = false; public bool isTIM { get; set; } = false; public byte SequenceNumber => getValue(3); public TIM2[] TIM { get; set; } private bool bFileNameTest => FileName != null && Path.GetExtension(FileName).Trim('.').Length == 3; /// /// if quit loop because unknown type. /// public int UnknownType { get; set; } = int.MinValue; #endregion Properties #region Methods public TIM2[] TryReadTIM(BinaryReader br) { try { var tim = new TIM2(br, noExec: true); if (tim.NotTIM) 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); if (!br.Read(out int count)) return; //Count = Count > 0x24 ? 0x24 : Count; // unsure why. var positions = new List(); while (count-- > 0 && br.Read(out uint pos)) { if (pos > 0 && pos + pGeometry < br.BaseStream.Length) positions.Add(pos + pGeometry); } if (count > 0) return; Geometries = new List(positions.Count); foreach (var pos in positions) { var g = new Geometry(); const int PassFromStart = 24; var OnlyVertex = true; br.BaseStream.Seek(pos, SeekOrigin.Begin); if (!br.Read(out count)) break; if (count > 12u) continue; if (count != 2u) OnlyVertex = false; br.BaseStream.Seek(pos + 8, SeekOrigin.Begin); var _relativeJump = br.ReadUInt32() + pos; br.BaseStream.Seek(pos + PassFromStart, SeekOrigin.Begin); var _vertexCount = br.ReadUInt16() * 8; br.BaseStream.Seek(pos + PassFromStart - 4, SeekOrigin.Begin); var _verticesOffset = br.ReadUInt16() + pos; ReadVertices(); if (OnlyVertex) { continue; } if (_relativeJump > br.BaseStream.Length) return; br.BaseStream.Seek(_relativeJump, SeekOrigin.Begin); var _polygonType = br.ReadUInt16(); var polygons = br.ReadUInt16(); bool _generatefaces; bool isknownpolygon() => _knownPolygons.Any(x => x == _polygonType); long localoffset = _relativeJump + 4; var 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; var size = polygons * cnt; for (var 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; var size = polygons * cnt; for (var 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); var 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 (var 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); var 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) { var pos = br.ReadUInt32(); if (pos != 0) positions.Add(pos + pTexture); } if (positions.Count > 0) { //textures here? return null; } else return null; } } #endregion Methods }