#region References using System; using System.Collections; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Security.Cryptography; #endregion namespace Ultima { public sealed class Art { private static FileIndex m_FileIndex = new FileIndex( "Artidx.mul", "Art.mul", "artLegacyMUL.uop", 0x10000 /*0x13FDC*/, 4, ".tga", 0x13FDC, false); private static Bitmap[] m_Cache; private static bool[] m_Removed; private static readonly Hashtable m_patched = new Hashtable(); public static bool Modified = false; private static byte[] m_StreamBuffer; private static byte[] Validbuffer; private struct CheckSums { public byte[] checksum; public int pos; public int length; public int index; } private static List checksumsLand; private static List checksumsStatic; static Art() { m_Cache = new Bitmap[0xFFFF]; m_Removed = new bool[0xFFFF]; } public static int GetMaxItemID() { if (GetIdxLength() >= 0x13FDC) { return 0xFFFF; } if (GetIdxLength() == 0xC000) { return 0x7FFF; } return 0x3FFF; } public static bool IsUOAHS() { return (GetIdxLength() >= 0x13FDC); } public static ushort GetLegalItemID(int itemID, bool checkmaxid = true) { if (itemID < 0) { return 0; } if (checkmaxid) { int max = GetMaxItemID(); if (itemID > max) { return 0; } } return (ushort)itemID; } public static int GetIdxLength() { return (int)(m_FileIndex.IdxLength / 12); } /// /// ReReads Art.mul /// public static void Reload() { m_FileIndex = new FileIndex( "Artidx.mul", "Art.mul", "artLegacyMUL.uop", 0x10000 /*0x13FDC*/, 4, ".tga", 0x13FDC, false); m_Cache = new Bitmap[0xFFFF]; m_Removed = new bool[0xFFFF]; m_patched.Clear(); Modified = false; } /// /// Sets bmp of index in of Static /// /// /// public static void ReplaceStatic(int index, Bitmap bmp) { index = GetLegalItemID(index); index += 0x4000; m_Cache[index] = bmp; m_Removed[index] = false; if (m_patched.Contains(index)) { m_patched.Remove(index); } Modified = true; } /// /// Sets bmp of index in of Land /// /// /// public static void ReplaceLand(int index, Bitmap bmp) { index &= 0x3FFF; m_Cache[index] = bmp; m_Removed[index] = false; if (m_patched.Contains(index)) { m_patched.Remove(index); } Modified = true; } /// /// Removes Static index /// /// public static void RemoveStatic(int index) { index = GetLegalItemID(index); index += 0x4000; m_Removed[index] = true; Modified = true; } /// /// Removes Land index /// /// public static void RemoveLand(int index) { index &= 0x3FFF; m_Removed[index] = true; Modified = true; } /// /// Tests if Static is definied (width and hight check) /// /// /// public static unsafe bool IsValidStatic(int index) { index = GetLegalItemID(index); index += 0x4000; if (m_Removed[index]) { return false; } if (m_Cache[index] != null) { return true; } int length, extra; bool patched; Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched); if (stream == null) { return false; } if (Validbuffer == null) { Validbuffer = new byte[4]; } stream.Seek(4, SeekOrigin.Current); stream.Read(Validbuffer, 0, 4); fixed (byte* b = Validbuffer) { var dat = (short*)b; if (*dat++ <= 0 || *dat <= 0) { return false; } return true; } } /// /// Tests if LandTile is definied /// /// /// public static bool IsValidLand(int index) { index &= 0x3FFF; if (m_Removed[index]) { return false; } if (m_Cache[index] != null) { return true; } int length, extra; bool patched; return m_FileIndex.Valid(index, out length, out extra, out patched); } /// /// Returns Bitmap of LandTile (with Cache) /// /// /// public static Bitmap GetLand(int index) { bool patched; return GetLand(index, out patched); } /// /// Returns Bitmap of LandTile (with Cache) and verdata bool /// /// /// /// public static Bitmap GetLand(int index, out bool patched) { index &= 0x3FFF; if (m_patched.Contains(index)) { patched = (bool)m_patched[index]; } else { patched = false; } if (m_Removed[index]) { return null; } if (m_Cache[index] != null) { return m_Cache[index]; } int length, extra; Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched); if (stream == null) { return null; } if (patched) { m_patched[index] = true; } if (Files.CacheData) { return m_Cache[index] = LoadLand(stream, length); } else { return LoadLand(stream, length); } } public static byte[] GetRawLand(int index) { index &= 0x3FFF; int length, extra; bool patched; Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched); if (stream == null) { return null; } var buffer = new byte[length]; stream.Read(buffer, 0, length); stream.Close(); return buffer; } /// /// Returns Bitmap of Static (with Cache) /// /// /// public static Bitmap GetStatic(int index, bool checkmaxid = true) { bool patched; return GetStatic(index, out patched, checkmaxid); } /// /// Returns Bitmap of Static (with Cache) and verdata bool /// /// /// /// public static Bitmap GetStatic(int index, out bool patched, bool checkmaxid = true) { index = GetLegalItemID(index, checkmaxid); index += 0x4000; if (m_patched.Contains(index)) { patched = (bool)m_patched[index]; } else { patched = false; } if (m_Removed[index]) { return null; } if (m_Cache[index] != null) { return m_Cache[index]; } int length, extra; Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched); if (stream == null) { return null; } if (patched) { m_patched[index] = true; } if (Files.CacheData) { return m_Cache[index] = LoadStatic(stream, length); } else { return LoadStatic(stream, length); } } public static byte[] GetRawStatic(int index) { index = GetLegalItemID(index); index += 0x4000; int length, extra; bool patched; Stream stream = m_FileIndex.Seek(index, out length, out extra, out patched); if (stream == null) { return null; } var buffer = new byte[length]; stream.Read(buffer, 0, length); stream.Close(); return buffer; } public static unsafe void Measure(Bitmap bmp, out int xMin, out int yMin, out int xMax, out int yMax) { xMin = yMin = 0; xMax = yMax = -1; if (bmp == null || bmp.Width <= 0 || bmp.Height <= 0) { return; } BitmapData bd = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat); int delta = (bd.Stride >> 1) - bd.Width; int lineDelta = bd.Stride >> 1; var pBuffer = (ushort*)bd.Scan0; ushort* pLineEnd = pBuffer + bd.Width; ushort* pEnd = pBuffer + (bd.Height * lineDelta); bool foundPixel = false; int x = 0, y = 0; while (pBuffer < pEnd) { while (pBuffer < pLineEnd) { ushort c = *pBuffer++; if ((c & 0x8000) != 0) { if (!foundPixel) { foundPixel = true; xMin = xMax = x; yMin = yMax = y; } else { if (x < xMin) { xMin = x; } if (y < yMin) { yMin = y; } if (x > xMax) { xMax = x; } if (y > yMax) { yMax = y; } } } ++x; } pBuffer += delta; pLineEnd += lineDelta; ++y; x = 0; } bmp.UnlockBits(bd); } private static unsafe Bitmap LoadStatic(Stream stream, int length) { Bitmap bmp; if (m_StreamBuffer == null || m_StreamBuffer.Length < length) { m_StreamBuffer = new byte[length]; } stream.Read(m_StreamBuffer, 0, length); stream.Close(); fixed (byte* data = m_StreamBuffer) { var bindata = (ushort*)data; int count = 2; //bin.ReadInt32(); int width = bindata[count++]; int height = bindata[count++]; if (width <= 0 || height <= 0) { return null; } var lookups = new int[height]; int start = (height + 4); for (int i = 0; i < height; ++i) { lookups[i] = (start + (bindata[count++])); } bmp = new Bitmap(width, height, Settings.PixelFormat); BitmapData bd = bmp.LockBits( new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, Settings.PixelFormat); var line = (ushort*)bd.Scan0; int delta = bd.Stride >> 1; for (int y = 0; y < height; ++y, line += delta) { count = lookups[y]; ushort* cur = line; ushort* end; int xOffset, xRun; while (((xOffset = bindata[count++]) + (xRun = bindata[count++])) != 0) { if (xOffset > delta) { break; } cur += xOffset; if (xOffset + xRun > delta) { break; } end = cur + xRun; while (cur < end) { *cur++ = (ushort)(bindata[count++] ^ 0x8000); } } } bmp.UnlockBits(bd); } return bmp; } private static unsafe Bitmap LoadLand(Stream stream, int length) { var bmp = new Bitmap(44, 44, Settings.PixelFormat); BitmapData bd = bmp.LockBits(new Rectangle(0, 0, 44, 44), ImageLockMode.WriteOnly, Settings.PixelFormat); if (m_StreamBuffer == null || m_StreamBuffer.Length < length) { m_StreamBuffer = new byte[length]; } stream.Read(m_StreamBuffer, 0, length); stream.Close(); fixed (byte* bindata = m_StreamBuffer) { var bdata = (ushort*)bindata; int xOffset = 21; int xRun = 2; var line = (ushort*)bd.Scan0; int delta = bd.Stride >> 1; for (int y = 0; y < 22; ++y, --xOffset, xRun += 2, line += delta) { ushort* cur = line + xOffset; ushort* end = cur + xRun; while (cur < end) { *cur++ = (ushort)(*bdata++ | 0x8000); } } xOffset = 0; xRun = 44; for (int y = 0; y < 22; ++y, ++xOffset, xRun -= 2, line += delta) { ushort* cur = line + xOffset; ushort* end = cur + xRun; while (cur < end) { *cur++ = (ushort)(*bdata++ | 0x8000); } } } bmp.UnlockBits(bd); return bmp; } /// /// Saves mul /// /// public static unsafe void Save(string path) { checksumsLand = new List(); checksumsStatic = new List(); string idx = Path.Combine(path, "artidx.mul"); string mul = Path.Combine(path, "art.mul"); using ( FileStream fsidx = new FileStream(idx, FileMode.Create, FileAccess.Write, FileShare.Write), fsmul = new FileStream(mul, FileMode.Create, FileAccess.Write, FileShare.Write)) { var memidx = new MemoryStream(); var memmul = new MemoryStream(); var sha = new SHA256Managed(); //StreamWriter Tex = new StreamWriter(new FileStream("d:/artlog.txt", FileMode.Create, FileAccess.ReadWrite)); using (BinaryWriter binidx = new BinaryWriter(memidx), binmul = new BinaryWriter(memmul)) { for (int index = 0; index < GetIdxLength(); index++) { Files.FireFileSaveEvent(); if (m_Cache[index] == null) { if (index < 0x4000) { m_Cache[index] = GetLand(index); } else { m_Cache[index] = GetStatic(index - 0x4000, false); } } Bitmap bmp = m_Cache[index]; if ((bmp == null) || (m_Removed[index])) { binidx.Write(-1); // lookup binidx.Write(0); // length binidx.Write(-1); // extra //Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, (int)-1, (int)-1)); } else if (index < 0x4000) { var ms = new MemoryStream(); bmp.Save(ms, ImageFormat.Bmp); byte[] checksum = sha.ComputeHash(ms.ToArray()); CheckSums sum; if (compareSaveImagesLand(checksum, out sum)) { binidx.Write(sum.pos); //lookup binidx.Write(sum.length); binidx.Write(0); //Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, (int)sum.pos, (int)sum.length)); //Tex.WriteLine(System.String.Format("0x{0:X4} -> 0x{1:X4}", sum.index, index)); continue; } //land BitmapData bd = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat); var line = (ushort*)bd.Scan0; int delta = bd.Stride >> 1; binidx.Write((int)binmul.BaseStream.Position); //lookup var length = (int)binmul.BaseStream.Position; int x = 22; int y = 0; int linewidth = 2; for (int m = 0; m < 22; ++m, ++y, line += delta, linewidth += 2) { --x; ushort* cur = line; for (int n = 0; n < linewidth; ++n) { binmul.Write((ushort)(cur[x + n] ^ 0x8000)); } } x = 0; linewidth = 44; y = 22; line = (ushort*)bd.Scan0; line += delta * 22; for (int m = 0; m < 22; m++, y++, line += delta, ++x, linewidth -= 2) { ushort* cur = line; for (int n = 0; n < linewidth; n++) { binmul.Write((ushort)(cur[x + n] ^ 0x8000)); } } int start = length; length = (int)binmul.BaseStream.Position - length; binidx.Write(length); binidx.Write(0); bmp.UnlockBits(bd); var s = new CheckSums { pos = start, length = length, checksum = checksum, index = index }; //Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, start, length)); checksumsLand.Add(s); } else { var ms = new MemoryStream(); bmp.Save(ms, ImageFormat.Bmp); byte[] checksum = sha.ComputeHash(ms.ToArray()); CheckSums sum; if (compareSaveImagesStatic(checksum, out sum)) { binidx.Write(sum.pos); //lookup binidx.Write(sum.length); binidx.Write(0); //Tex.WriteLine(System.String.Format("0x{0:X4} -> 0x{1:X4}", sum.index, index)); //Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, sum.pos, sum.length)); continue; } // art BitmapData bd = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, Settings.PixelFormat); var line = (ushort*)bd.Scan0; int delta = bd.Stride >> 1; binidx.Write((int)binmul.BaseStream.Position); //lookup var length = (int)binmul.BaseStream.Position; binmul.Write(1234); // header binmul.Write((short)bmp.Width); binmul.Write((short)bmp.Height); var lookup = (int)binmul.BaseStream.Position; int streamloc = lookup + bmp.Height * 2; int width = 0; for (int i = 0; i < bmp.Height; ++i) // fill lookup { binmul.Write(width); } int X = 0; for (int Y = 0; Y < bmp.Height; ++Y, line += delta) { ushort* cur = line; width = (int)(binmul.BaseStream.Position - streamloc) / 2; binmul.BaseStream.Seek(lookup + Y * 2, SeekOrigin.Begin); binmul.Write(width); binmul.BaseStream.Seek(streamloc + width * 2, SeekOrigin.Begin); int i = 0; int j = 0; X = 0; while (i < bmp.Width) { i = X; for (i = X; i <= bmp.Width; ++i) { //first pixel set if (i < bmp.Width) { if (cur[i] != 0) { break; } } } if (i < bmp.Width) { for (j = (i + 1); j < bmp.Width; ++j) { //next non set pixel if (cur[j] == 0) { break; } } binmul.Write((short)(i - X)); //xoffset binmul.Write((short)(j - i)); //run for (int p = i; p < j; ++p) { binmul.Write((ushort)(cur[p] ^ 0x8000)); } X = j; } } binmul.Write((short)0); //xOffset binmul.Write((short)0); //Run } int start = length; length = (int)binmul.BaseStream.Position - length; binidx.Write(length); binidx.Write(0); bmp.UnlockBits(bd); var s = new CheckSums { pos = start, length = length, checksum = checksum, index = index }; //Tex.WriteLine(System.String.Format("0x{0:X4} : 0x{1:X4} 0x{2:X4}", index, start, length)); checksumsStatic.Add(s); } } memidx.WriteTo(fsidx); memmul.WriteTo(fsmul); } } } private static bool compareSaveImagesLand(byte[] newchecksum, out CheckSums sum) { sum = new CheckSums(); for (int i = 0; i < checksumsLand.Count; ++i) { byte[] cmp = checksumsLand[i].checksum; if (((cmp == null) || (newchecksum == null)) || (cmp.Length != newchecksum.Length)) { return false; } bool valid = true; for (int j = 0; j < cmp.Length; ++j) { if (cmp[j] != newchecksum[j]) { valid = false; break; } } if (valid) { sum = checksumsLand[i]; return true; } } return false; } private static bool compareSaveImagesStatic(byte[] newchecksum, out CheckSums sum) { sum = new CheckSums(); for (int i = 0; i < checksumsStatic.Count; ++i) { byte[] cmp = checksumsStatic[i].checksum; if (((cmp == null) || (newchecksum == null)) || (cmp.Length != newchecksum.Length)) { return false; } bool valid = true; for (int j = 0; j < cmp.Length; ++j) { if (cmp[j] != newchecksum[j]) { valid = false; break; } } if (valid) { sum = checksumsStatic[i]; return true; } } return false; } } }