#region References using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; #endregion namespace Ultima { public sealed class TileMatrix { private readonly HuedTile[][][][][] m_StaticTiles; private readonly Tile[][][] m_LandTiles; private bool[][] m_RemovedStaticBlock; private List[][] m_StaticTiles_ToAdd; public static Tile[] InvalidLandBlock { get; private set; } public static HuedTile[][][] EmptyStaticBlock { get; private set; } private FileStream m_Map; private BinaryReader m_UOPReader; private FileStream m_Statics; private Entry3D[] m_StaticIndex; public Entry3D[] StaticIndex { get { if (!StaticIndexInit) { InitStatics(); } return m_StaticIndex; } } public bool StaticIndexInit; public TileMatrixPatch Patch { get; private set; } public int BlockWidth { get; private set; } public int BlockHeight { get; private set; } public int Width { get; private set; } public int Height { get; private set; } private readonly string mapPath; private readonly string indexPath; private readonly string staticsPath; public void CloseStreams() { if (m_Map != null) { m_Map.Close(); } if (m_UOPReader != null) { m_UOPReader.Close(); } if (m_Statics != null) { m_Statics.Close(); } } public TileMatrix(int fileIndex, int mapID, int width, int height, string path) { Width = width; Height = height; BlockWidth = width >> 3; BlockHeight = height >> 3; if (path == null) { mapPath = Files.GetFilePath("map{0}.mul", fileIndex); if (String.IsNullOrEmpty(mapPath) || !File.Exists(mapPath)) { mapPath = Files.GetFilePath("map{0}LegacyMUL.uop", fileIndex); } if (mapPath != null && mapPath.EndsWith(".uop")) { IsUOPFormat = true; } } else { mapPath = Path.Combine(path, String.Format("map{0}.mul", fileIndex)); if (!File.Exists(mapPath)) { mapPath = Path.Combine(path, String.Format("map{0}LegacyMUL.uop", fileIndex)); } if (!File.Exists(mapPath)) { mapPath = null; } else if (mapPath != null && mapPath.EndsWith(".uop")) { IsUOPFormat = true; } } if (path == null) { indexPath = Files.GetFilePath("staidx{0}.mul", fileIndex); } else { indexPath = Path.Combine(path, String.Format("staidx{0}.mul", fileIndex)); if (!File.Exists(indexPath)) { indexPath = null; } } if (path == null) { staticsPath = Files.GetFilePath("statics{0}.mul", fileIndex); } else { staticsPath = Path.Combine(path, String.Format("statics{0}.mul", fileIndex)); if (!File.Exists(staticsPath)) { staticsPath = null; } } EmptyStaticBlock = new HuedTile[8][][]; for (int i = 0; i < 8; ++i) { EmptyStaticBlock[i] = new HuedTile[8][]; for (int j = 0; j < 8; ++j) { EmptyStaticBlock[i][j] = new HuedTile[0]; } } InvalidLandBlock = new Tile[196]; m_LandTiles = new Tile[BlockWidth][][]; m_StaticTiles = new HuedTile[BlockWidth][][][][]; Patch = new TileMatrixPatch(this, mapID, path); } public void SetStaticBlock(int x, int y, HuedTile[][][] value) { if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) { return; } if (m_StaticTiles[x] == null) { m_StaticTiles[x] = new HuedTile[BlockHeight][][][]; } m_StaticTiles[x][y] = value; } public HuedTile[][][] GetStaticBlock(int x, int y) { return GetStaticBlock(x, y, true); } public HuedTile[][][] GetStaticBlock(int x, int y, bool patch) { if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) { return EmptyStaticBlock; } if (m_StaticTiles[x] == null) { m_StaticTiles[x] = new HuedTile[BlockHeight][][][]; } HuedTile[][][] tiles = m_StaticTiles[x][y]; if (tiles == null) { tiles = m_StaticTiles[x][y] = ReadStaticBlock(x, y); } if ((Map.UseDiff) && (patch)) { if (Patch.StaticBlocksCount > 0) { if (Patch.StaticBlocks[x] != null) { if (Patch.StaticBlocks[x][y] != null) { tiles = Patch.StaticBlocks[x][y]; } } } } return tiles; } public HuedTile[] GetStaticTiles(int x, int y, bool patch) { return GetStaticBlock(x >> 3, y >> 3, patch)[x & 0x7][y & 0x7]; } public HuedTile[] GetStaticTiles(int x, int y) { return GetStaticBlock(x >> 3, y >> 3)[x & 0x7][y & 0x7]; } public void SetLandBlock(int x, int y, Tile[] value) { if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) { return; } if (m_LandTiles[x] == null) { m_LandTiles[x] = new Tile[BlockHeight][]; } m_LandTiles[x][y] = value; } public Tile[] GetLandBlock(int x, int y) { return GetLandBlock(x, y, true); } public Tile[] GetLandBlock(int x, int y, bool patch) { if (x < 0 || y < 0 || x >= BlockWidth || y >= BlockHeight) { return InvalidLandBlock; } if (m_LandTiles[x] == null) { m_LandTiles[x] = new Tile[BlockHeight][]; } Tile[] tiles = m_LandTiles[x][y]; if (tiles == null) { tiles = m_LandTiles[x][y] = ReadLandBlock(x, y); } if ((Map.UseDiff) && (patch)) { if (Patch.LandBlocksCount > 0) { if (Patch.LandBlocks[x] != null) { if (Patch.LandBlocks[x][y] != null) { tiles = Patch.LandBlocks[x][y]; } } } } return tiles; } public Tile GetLandTile(int x, int y, bool patch) { return GetLandBlock(x >> 3, y >> 3, patch)[((y & 0x7) << 3) + (x & 0x7)]; } public Tile GetLandTile(int x, int y) { return GetLandBlock(x >> 3, y >> 3)[((y & 0x7) << 3) + (x & 0x7)]; } private void InitStatics() { m_StaticIndex = new Entry3D[BlockHeight * BlockWidth]; if (indexPath == null) { return; } using (var index = new FileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.Read); var count = (int)(index.Length / 12); GCHandle gc = GCHandle.Alloc(m_StaticIndex, GCHandleType.Pinned); var buffer = new byte[index.Length]; index.Read(buffer, 0, (int)index.Length); Marshal.Copy(buffer, 0, gc.AddrOfPinnedObject(), (int)Math.Min(index.Length, BlockHeight * BlockWidth * 12)); gc.Free(); for (var i = (int)Math.Min(index.Length, BlockHeight * BlockWidth); i < BlockHeight * BlockWidth; ++i) { m_StaticIndex[i].lookup = -1; m_StaticIndex[i].length = -1; m_StaticIndex[i].extra = -1; } StaticIndexInit = true; } } private static HuedTileList[][] m_Lists; private static byte[] m_Buffer; private unsafe HuedTile[][][] ReadStaticBlock(int x, int y) { try { if (!StaticIndexInit) { InitStatics(); } if (m_Statics == null || !m_Statics.CanRead || !m_Statics.CanSeek) { if (staticsPath == null) { m_Statics = null; } else { m_Statics = new FileStream(staticsPath, FileMode.Open, FileAccess.Read, FileShare.Read); } } if (m_Statics == null) { return EmptyStaticBlock; } int lookup = m_StaticIndex[(x * BlockHeight) + y].lookup; int length = m_StaticIndex[(x * BlockHeight) + y].length; if (lookup < 0 || length <= 0) { return EmptyStaticBlock; } else { int count = length / 7; m_Statics.Seek(lookup, SeekOrigin.Begin); if (m_Buffer == null || m_Buffer.Length < length) { m_Buffer = new byte[length]; } GCHandle gc = GCHandle.Alloc(m_Buffer, GCHandleType.Pinned); try { m_Statics.Read(m_Buffer, 0, length); if (m_Lists == null) { m_Lists = new HuedTileList[8][]; for (int i = 0; i < 8; ++i) { m_Lists[i] = new HuedTileList[8]; for (int j = 0; j < 8; ++j) { m_Lists[i][j] = new HuedTileList(); } } } HuedTileList[][] lists = m_Lists; for (int i = 0; i < count; ++i) { var ptr = new IntPtr((long)gc.AddrOfPinnedObject() + i * sizeof(StaticTile)); var cur = (StaticTile)Marshal.PtrToStructure(ptr, typeof(StaticTile)); lists[cur.m_X & 0x7][cur.m_Y & 0x7].Add(Art.GetLegalItemID(cur.m_ID), cur.m_Hue, cur.m_Z); } var tiles = new HuedTile[8][][]; for (int i = 0; i < 8; ++i) { tiles[i] = new HuedTile[8][]; for (int j = 0; j < 8; ++j) { tiles[i][j] = lists[i][j].ToArray(); } } return tiles; } finally { gc.Free(); } } } finally { //if (m_Statics != null) // m_Statics.Close(); } } /* UOP map files support code, written by Wyatt (c) www.ruosi.org * It's not possible if some entry has unknown hash. Throwed exception * means that EA changed maps UOPs again. */ #region UOP public bool IsUOPFormat { get; set; } public bool IsUOPAlreadyRead { get; set; } private struct UOPFile { public readonly long Offset; public readonly int Length; public UOPFile(long offset, int length) { Offset = offset; Length = length; } } private UOPFile[] UOPFiles { get; set; } private long UOPLength { get { return m_Map.Length; } } private void ReadUOPFiles(string pattern) { m_UOPReader = new BinaryReader(m_Map); m_UOPReader.BaseStream.Seek(0, SeekOrigin.Begin); if (m_UOPReader.ReadInt32() != 0x50594D) { throw new ArgumentException("Bad UOP file."); } m_UOPReader.ReadInt64(); // version + signature long nextBlock = m_UOPReader.ReadInt64(); m_UOPReader.ReadInt32(); // block capacity int count = m_UOPReader.ReadInt32(); UOPFiles = new UOPFile[count]; var hashes = new Dictionary(); for (int i = 0; i < count; i++) { string file = string.Format("build/{0}/{1:D8}.dat", pattern, i); ulong hash = FileIndex.HashFileName(file); if (!hashes.ContainsKey(hash)) { hashes.Add(hash, i); } } m_UOPReader.BaseStream.Seek(nextBlock, SeekOrigin.Begin); do { int filesCount = m_UOPReader.ReadInt32(); nextBlock = m_UOPReader.ReadInt64(); for (int i = 0; i < filesCount; i++) { long offset = m_UOPReader.ReadInt64(); int headerLength = m_UOPReader.ReadInt32(); int compressedLength = m_UOPReader.ReadInt32(); int decompressedLength = m_UOPReader.ReadInt32(); ulong hash = m_UOPReader.ReadUInt64(); m_UOPReader.ReadUInt32(); // Adler32 short flag = m_UOPReader.ReadInt16(); int length = flag == 1 ? compressedLength : decompressedLength; if (offset == 0) { continue; } int idx; if (hashes.TryGetValue(hash, out idx)) { if (idx < 0 || idx > UOPFiles.Length) { throw new IndexOutOfRangeException("hashes dictionary and files collection have different count of entries!"); } UOPFiles[idx] = new UOPFile(offset + headerLength, length); } else { throw new ArgumentException( string.Format("File with hash 0x{0:X8} was not found in hashes dictionary! EA Mythic changed UOP format!", hash)); } } } while (m_UOPReader.BaseStream.Seek(nextBlock, SeekOrigin.Begin) != 0); } private long CalculateOffsetFromUOP(long offset) { long pos = 0; foreach (UOPFile t in UOPFiles) { long currPos = pos + t.Length; if (offset < currPos) { return t.Offset + (offset - pos); } pos = currPos; } return UOPLength; } #endregion private Tile[] ReadLandBlock(int x, int y) { if (m_Map == null || !m_Map.CanRead || !m_Map.CanSeek) { if (mapPath == null) { m_Map = null; } else { m_Map = new FileStream(mapPath, FileMode.Open, FileAccess.Read, FileShare.Read); } if (IsUOPFormat && mapPath != null && !IsUOPAlreadyRead) { var fi = new FileInfo(mapPath); string uopPattern = fi.Name.Replace(fi.Extension, "").ToLowerInvariant(); ReadUOPFiles(uopPattern); IsUOPAlreadyRead = true; } } var tiles = new Tile[64]; if (m_Map != null) { long offset = ((x * BlockHeight) + y) * 196 + 4; if (IsUOPFormat) { offset = CalculateOffsetFromUOP(offset); } m_Map.Seek(offset, SeekOrigin.Begin); GCHandle gc = GCHandle.Alloc(tiles, GCHandleType.Pinned); try { if (m_Buffer == null || m_Buffer.Length < 192) { m_Buffer = new byte[192]; } m_Map.Read(m_Buffer, 0, 192); Marshal.Copy(m_Buffer, 0, gc.AddrOfPinnedObject(), 192); } finally { gc.Free(); } //m_Map.Close(); } return tiles; } public void RemoveStaticBlock(int blockx, int blocky) { if (m_RemovedStaticBlock == null) { m_RemovedStaticBlock = new bool[BlockWidth][]; } if (m_RemovedStaticBlock[blockx] == null) { m_RemovedStaticBlock[blockx] = new bool[BlockHeight]; } m_RemovedStaticBlock[blockx][blocky] = true; if (m_StaticTiles[blockx] == null) { m_StaticTiles[blockx] = new HuedTile[BlockHeight][][][]; } m_StaticTiles[blockx][blocky] = EmptyStaticBlock; } public bool IsStaticBlockRemoved(int blockx, int blocky) { if (m_RemovedStaticBlock == null) { return false; } if (m_RemovedStaticBlock[blockx] == null) { return false; } return m_RemovedStaticBlock[blockx][blocky]; } public bool PendingStatic(int blockx, int blocky) { if (m_StaticTiles_ToAdd == null) { return false; } if (m_StaticTiles_ToAdd[blocky] == null) { return false; } if (m_StaticTiles_ToAdd[blocky][blockx] == null) { return false; } return true; } public void AddPendingStatic(int blockx, int blocky, StaticTile toadd) { if (m_StaticTiles_ToAdd == null) { m_StaticTiles_ToAdd = new List[BlockHeight][]; } if (m_StaticTiles_ToAdd[blocky] == null) { m_StaticTiles_ToAdd[blocky] = new List[BlockWidth]; } if (m_StaticTiles_ToAdd[blocky][blockx] == null) { m_StaticTiles_ToAdd[blocky][blockx] = new List(); } m_StaticTiles_ToAdd[blocky][blockx].Add(toadd); } public StaticTile[] GetPendingStatics(int blockx, int blocky) { if (m_StaticTiles_ToAdd == null) { return null; } if (m_StaticTiles_ToAdd[blocky] == null) { return null; } if (m_StaticTiles_ToAdd[blocky][blockx] == null) { return null; } return m_StaticTiles_ToAdd[blocky][blockx].ToArray(); } public void Dispose() { if (m_Map != null) { m_Map.Close(); } if (m_UOPReader != null) { m_UOPReader.Close(); } if (m_Statics != null) { m_Statics.Close(); } } } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct StaticTile { public ushort m_ID; public byte m_X; public byte m_Y; public sbyte m_Z; public short m_Hue; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct HuedTile { internal sbyte m_Z; internal ushort m_ID; internal int m_Hue; public ushort ID { get { return m_ID; } set { m_ID = value; } } public int Hue { get { return m_Hue; } set { m_Hue = value; } } public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } } public HuedTile(ushort id, short hue, sbyte z) { m_ID = id; m_Hue = hue; m_Z = z; } public void Set(ushort id, short hue, sbyte z) { m_ID = id; m_Hue = hue; m_Z = z; } } public struct MTile : IComparable { internal ushort m_ID; internal sbyte m_Z; internal TileFlag m_Flag; internal int m_Solver; public ushort ID { get { return m_ID; } } public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } } public TileFlag Flag { get { return m_Flag; } set { m_Flag = value; } } public int Solver { get { return m_Solver; } set { m_Solver = value; } } public MTile(ushort id, sbyte z) { m_ID = Art.GetLegalItemID(id); m_Z = z; m_Flag = TileFlag.Background; m_Solver = 0; } public MTile(ushort id, sbyte z, TileFlag flag) { m_ID = Art.GetLegalItemID(id); m_Z = z; m_Flag = flag; m_Solver = 0; } public void Set(ushort id, sbyte z) { m_ID = Art.GetLegalItemID(id); m_Z = z; } public void Set(ushort id, sbyte z, TileFlag flag) { m_ID = Art.GetLegalItemID(id); m_Z = z; m_Flag = flag; } public int CompareTo(object x) { if (x == null) { return 1; } if (!(x is MTile)) { throw new ArgumentNullException(); } var a = (MTile)x; ItemData ourData = TileData.ItemTable[m_ID]; ItemData theirData = TileData.ItemTable[a.ID]; int ourTreshold = 0; if (ourData.Height > 0) { ++ourTreshold; } if (!ourData.Background) { ++ourTreshold; } int ourZ = Z; int theirTreshold = 0; if (theirData.Height > 0) { ++theirTreshold; } if (!theirData.Background) { ++theirTreshold; } int theirZ = a.Z; ourZ += ourTreshold; theirZ += theirTreshold; int res = ourZ - theirZ; if (res == 0) { res = ourTreshold - theirTreshold; } if (res == 0) { res = m_Solver - a.Solver; } return res; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Tile : IComparable { internal ushort m_ID; internal sbyte m_Z; public ushort ID { get { return m_ID; } } public int Z { get { return m_Z; } set { m_Z = (sbyte)value; } } public Tile(ushort id, sbyte z) { m_ID = id; m_Z = z; } public Tile(ushort id, sbyte z, sbyte flag) { m_ID = id; m_Z = z; } public void Set(ushort id, sbyte z) { m_ID = id; m_Z = z; } public void Set(ushort id, sbyte z, sbyte flag) { m_ID = id; m_Z = z; } public int CompareTo(object x) { if (x == null) { return 1; } if (!(x is Tile)) { throw new ArgumentNullException(); } var a = (Tile)x; if (m_Z > a.m_Z) { return 1; } else if (a.m_Z > m_Z) { return -1; } ItemData ourData = TileData.ItemTable[m_ID]; ItemData theirData = TileData.ItemTable[a.m_ID]; if (ourData.Height > theirData.Height) { return 1; } else if (theirData.Height > ourData.Height) { return -1; } if (ourData.Background && !theirData.Background) { return -1; } else if (theirData.Background && !ourData.Background) { return 1; } return 0; } } }