using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.InteropServices; using ClassicAssist.Misc; namespace ClassicAssist.UO.Data { public static class Art { private static string _dataPath; private static bool _isUOPFormat; private static Lazy> _lazyIndex; public static bool Initialize( string dataPath ) { _dataPath = dataPath; if ( File.Exists( Path.Combine( dataPath, "artLegacyMUL.uop" ) ) ) { _isUOPFormat = true; } _lazyIndex = new Lazy>( () => _isUOPFormat ? LoadUOPIndex() : LoadMULIndex() ); return true; } private static Dictionary LoadMULIndex() { int entrySize = Marshal.SizeOf( typeof( Entry3D ) ); byte[] buffer = new byte[entrySize]; GCHandle pinnedBuffer = GCHandle.Alloc( buffer, GCHandleType.Pinned ); int index = 0; Dictionary indexes = new Dictionary(); using ( FileStream reader = File.Open( Path.Combine( _dataPath, "artidx.mul" ), FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) { int bytesRead; do { bytesRead = reader.Read( buffer, 0, entrySize ); indexes[index++] = (Entry3D) Marshal.PtrToStructure( pinnedBuffer.AddrOfPinnedObject(), typeof( Entry3D ) ); } while ( bytesRead > 0 ); } return indexes; } private static Dictionary LoadUOPIndex() { Dictionary hashes = new Dictionary(); Dictionary indexes = new Dictionary(); using ( FileStream reader = File.Open( Path.Combine( _dataPath, "artLegacyMUL.uop" ), FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) { using ( BinaryReader bin = new BinaryReader( reader ) ) { reader.Seek( 12, SeekOrigin.Current ); int firstAddress = bin.ReadInt32(); for ( int i = 0; i < 0x10000 /*formatHeader.NumberOfFiles*/; i++ ) { string entryName = $"build/artlegacymul/{i:D8}.tga"; ulong hash = HashFileName( entryName ); if ( !hashes.ContainsKey( hash ) ) { hashes.Add( hash, i ); } } long nextAddress = firstAddress; do { reader.Seek( nextAddress, SeekOrigin.Begin ); UOPBlockHeader blockHeader = reader.ReadStruct(); nextAddress = blockHeader.NextAddress; for ( int i = 0; i < blockHeader.NumberOfFiles; i++ ) { UOPFileHeader fileHeader = reader.ReadStruct(); if ( fileHeader.DataHeaderAddress == 0 ) { continue; } if ( hashes.ContainsKey( fileHeader.Hash ) ) { int index = hashes[fileHeader.Hash]; indexes[index] = new Entry3D( (int) fileHeader.DataHeaderAddress + fileHeader.Length, fileHeader.IsCompressed == 1 ? fileHeader.CompressedSize : fileHeader.DecompressedSize, 0 ); } } } while ( nextAddress > 0 ); } } return indexes; } public static Bitmap GetStatic( int itemID, int hue ) { Bitmap bmp = GetStatic( itemID ); if ( hue == 0 || bmp == null ) { return bmp; } StaticTile tileData = TileData.GetStaticTile( itemID ); Hues.ApplyHue( hue, bmp, tileData.Flags.HasFlag( TileFlags.PartialHue ) ); return bmp; } public static unsafe Bitmap GetStatic( int itemId ) { itemId += 0x4000; string fileName = Path.Combine( _dataPath, "art.mul" ); if ( _isUOPFormat ) { fileName = Path.Combine( _dataPath, "artLegacyMUL.uop" ); } FileStream artFile = File.Open( fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ); Entry3D entry; try { entry = _lazyIndex.Value[itemId]; } catch ( KeyNotFoundException ) { entry = _lazyIndex.Value[0x4000]; } artFile.Seek( entry.Lookup, SeekOrigin.Begin ); using ( BinaryReader reader = new BinaryReader( artFile ) ) { reader.ReadInt32(); int width = reader.ReadInt16(); int height = reader.ReadInt16(); if ( width <= 0 || height <= 0 ) { return null; } int[] lookups = new int[height]; int start = (int) reader.BaseStream.Position + height * 2; for ( int i = 0; i < height; i++ ) { lookups[i] = start + reader.ReadUInt16() * 2; } Bitmap bmp = new Bitmap( width, height, PixelFormat.Format16bppArgb1555 ); BitmapData bd = bmp.LockBits( new Rectangle( 0, 0, width, height ), ImageLockMode.WriteOnly, PixelFormat.Format16bppArgb1555 ); ushort* line = (ushort*) bd.Scan0; int delta = bd.Stride >> 1; int largestX = 0; int smallestX = width; int largestY = 0; int smallestY = height; for ( int y = 0; y < height; y++, line += delta ) { reader.BaseStream.Seek( lookups[y], SeekOrigin.Begin ); ushort* cur = line; int xOffset, xRun; int x = 0; if ( y > largestY ) { largestY = y + 1; } if ( y < smallestY ) { smallestY = y; } while ( ( xOffset = reader.ReadUInt16() ) + ( xRun = reader.ReadUInt16() ) != 0 ) { cur += xOffset; ushort* end = cur + xRun; x += xOffset; if ( x < smallestX ) { smallestX = x; } while ( cur < end ) { *cur++ = (ushort) ( reader.ReadUInt16() ^ 0x8000 ); x++; } if ( x > largestX ) { largestX = x; } } } bmp.UnlockBits( bd ); return bmp; } } private static ulong HashFileName( string s ) { uint ecx, edx, ebx, esi, edi; uint eax = ecx = edx = ebx = esi = edi = 0; ebx = edi = esi = (uint) s.Length + 0xDEADBEEF; int i = 0; for ( i = 0; i + 12 < s.Length; i += 12 ) { edi = (uint) ( ( s[i + 7] << 24 ) | ( s[i + 6] << 16 ) | ( s[i + 5] << 8 ) | s[i + 4] ) + edi; esi = (uint) ( ( s[i + 11] << 24 ) | ( s[i + 10] << 16 ) | ( s[i + 9] << 8 ) | s[i + 8] ) + esi; edx = (uint) ( ( s[i + 3] << 24 ) | ( s[i + 2] << 16 ) | ( s[i + 1] << 8 ) | s[i] ) - esi; edx = ( edx + ebx ) ^ ( esi >> 28 ) ^ ( esi << 4 ); esi += edi; edi = ( edi - edx ) ^ ( edx >> 26 ) ^ ( edx << 6 ); edx += esi; esi = ( esi - edi ) ^ ( edi >> 24 ) ^ ( edi << 8 ); edi += edx; ebx = ( edx - esi ) ^ ( esi >> 16 ) ^ ( esi << 16 ); esi += edi; edi = ( edi - ebx ) ^ ( ebx >> 13 ) ^ ( ebx << 19 ); ebx += esi; esi = ( esi - edi ) ^ ( edi >> 28 ) ^ ( edi << 4 ); edi += ebx; } if ( s.Length - i > 0 ) { switch ( s.Length - i ) { case 12: esi += (uint) s[i + 11] << 24; goto case 11; case 11: esi += (uint) s[i + 10] << 16; goto case 10; case 10: esi += (uint) s[i + 9] << 8; goto case 9; case 9: esi += s[i + 8]; goto case 8; case 8: edi += (uint) s[i + 7] << 24; goto case 7; case 7: edi += (uint) s[i + 6] << 16; goto case 6; case 6: edi += (uint) s[i + 5] << 8; goto case 5; case 5: edi += s[i + 4]; goto case 4; case 4: ebx += (uint) s[i + 3] << 24; goto case 3; case 3: ebx += (uint) s[i + 2] << 16; goto case 2; case 2: ebx += (uint) s[i + 1] << 8; goto case 1; case 1: ebx += s[i]; break; } esi = ( esi ^ edi ) - ( ( edi >> 18 ) ^ ( edi << 14 ) ); ecx = ( esi ^ ebx ) - ( ( esi >> 21 ) ^ ( esi << 11 ) ); edi = ( edi ^ ecx ) - ( ( ecx >> 7 ) ^ ( ecx << 25 ) ); esi = ( esi ^ edi ) - ( ( edi >> 16 ) ^ ( edi << 16 ) ); edx = ( esi ^ ecx ) - ( ( esi >> 28 ) ^ ( esi << 4 ) ); edi = ( edi ^ edx ) - ( ( edx >> 18 ) ^ ( edx << 14 ) ); eax = ( esi ^ edi ) - ( ( edi >> 8 ) ^ ( edi << 24 ) ); return ( (ulong) edi << 32 ) | eax; } return ( (ulong) esi << 32 ) | eax; } #region Structures [StructLayout( LayoutKind.Explicit )] private struct Entry3D { [FieldOffset( 0 )] public readonly int Lookup; [FieldOffset( 4 )] public readonly int Length; [FieldOffset( 8 )] public readonly int Extra; public Entry3D( int lookup, int length, int extra ) { Lookup = lookup; Length = length; Extra = extra; } } [StructLayout( LayoutKind.Explicit, Size = 12 )] private struct UOPBlockHeader { [FieldOffset( 0 )] public readonly int NumberOfFiles; [FieldOffset( 4 )] public readonly long NextAddress; } [StructLayout( LayoutKind.Explicit, Pack = 0, Size = 34 )] private struct UOPFileHeader { [FieldOffset( 0 )] public readonly long DataHeaderAddress; [FieldOffset( 8 )] public readonly int Length; [FieldOffset( 12 )] public readonly int CompressedSize; [FieldOffset( 16 )] public readonly int DecompressedSize; [FieldOffset( 20 )] public readonly ulong Hash; [FieldOffset( 28 )] public readonly int Unknown; [FieldOffset( 32 )] public readonly short IsCompressed; } #endregion } }