├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Source └── DataExtractor │ ├── DataExtractor.csproj │ ├── Framework │ ├── CASCLib │ │ ├── ArmadilloCrypt.cs │ │ ├── BLTEStream.cs │ │ ├── CASCConfig.cs │ │ ├── CASCEntry.cs │ │ ├── CASCGame.cs │ │ ├── CASCHandler.cs │ │ ├── CASCHandlerBase.cs │ │ ├── CDNCache.cs │ │ ├── CDNCacheStats.cs │ │ ├── CDNIndexHandler.cs │ │ ├── DownloadHandler.cs │ │ ├── EncodingHandler.cs │ │ ├── Extensions.cs │ │ ├── InstallHandler.cs │ │ ├── Jenkins96.cs │ │ ├── KeyService.cs │ │ ├── LocalIndexHandler.cs │ │ ├── MD5HashComparer.cs │ │ ├── MultiDictionary.cs │ │ ├── RibbitClient.cs │ │ ├── RootHandlers │ │ │ ├── RootHandlerBase.cs │ │ │ └── WowRootHandler.cs │ │ └── Salsa20.cs │ ├── ClientReader │ │ ├── BitReader.cs │ │ ├── DBReader.cs │ │ └── Structs.cs │ ├── Collision │ │ ├── BoundingIntervalHierarchy.cs │ │ ├── Callbacks.cs │ │ ├── Management │ │ │ └── VmapManager2.cs │ │ ├── Maps │ │ │ └── MapTree.cs │ │ └── Models │ │ │ ├── Model.cs │ │ │ └── WorldModel.cs │ ├── Constants │ │ └── SharedConst.cs │ ├── Detour │ │ ├── DetourCommon.cs │ │ ├── DetourNavMesh.cs │ │ ├── DetourNavMeshBuilder.cs │ │ ├── DetourNavMeshQuery.cs │ │ ├── DetourNode.cs │ │ └── DetourStatus.cs │ ├── Extensions.cs │ ├── GameMath │ │ ├── AxisAlignedBox.cs │ │ └── Matrix3.cs │ ├── IO │ │ ├── FileList.cs │ │ └── FileWriter.cs │ ├── MathFunctions.cs │ ├── MultiMap.cs │ ├── Recast │ │ ├── Recast.cs │ │ ├── RecastArea.cs │ │ ├── RecastContour.cs │ │ ├── RecastFilter.cs │ │ ├── RecastLayers.cs │ │ ├── RecastMesh.cs │ │ ├── RecastMeshDetail.cs │ │ ├── RecastRasterization.cs │ │ └── RecastRegion.cs │ └── Threading │ │ └── ProducerConsumerQueue.cs │ ├── Map │ ├── ADTStructures.cs │ ├── ChunkedFile.cs │ ├── MapFile.cs │ └── WDTStructures.cs │ ├── Mmap │ ├── MapBuilder.cs │ └── TerrainBuilder.cs │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ └── Vmap │ ├── ADTFile.cs │ ├── Collision │ └── TileAssembler.cs │ ├── Model.cs │ ├── VmapFile.cs │ ├── WDTFile.cs │ └── Wmo.cs ├── THANKS ├── Tools.sln ├── appveyor.yml └── default.props /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 19 | !packages/*/build/ 20 | 21 | # MSTest test Results 22 | [Tt]est[Rr]esult*/ 23 | [Bb]uild[Ll]og.* 24 | UnitTestResults.html 25 | 26 | *_i.c 27 | *_p.c 28 | *.ilk 29 | *.meta 30 | *.obj 31 | *.pch 32 | *.pdb 33 | *.pgc 34 | *.pgd 35 | *.rsp 36 | *.sbr 37 | *.tlb 38 | *.tli 39 | *.tlh 40 | *.tmp 41 | *.tmp_proj 42 | *.log 43 | *.vspscc 44 | *.vssscc 45 | .builds 46 | *.pidb 47 | *.log 48 | *.scc 49 | *.vs 50 | 51 | # Visual Studio cache files 52 | *.sln.ide/ 53 | 54 | # Visual C++ cache files 55 | ipch/ 56 | *.aps 57 | *.ncb 58 | *.opensdf 59 | *.sdf 60 | *.cachefile 61 | 62 | # Visual Studio profiler 63 | *.psess 64 | *.vsp 65 | *.vspx 66 | 67 | # Guidance Automation Toolkit 68 | *.gpState 69 | 70 | # ReSharper is a .NET coding add-in 71 | _ReSharper*/ 72 | *.[Rr]e[Ss]harper 73 | 74 | # TeamCity is a build add-in 75 | _TeamCity* 76 | 77 | # DotCover is a Code Coverage Tool 78 | *.dotCover 79 | 80 | # NCrunch 81 | *.ncrunch* 82 | .*crunch*.local.xml 83 | 84 | # Installshield output folder 85 | [Ee]xpress/ 86 | 87 | # DocProject is a documentation generator add-in 88 | DocProject/buildhelp/ 89 | DocProject/Help/*.HxT 90 | DocProject/Help/*.HxC 91 | DocProject/Help/*.hhc 92 | DocProject/Help/*.hhk 93 | DocProject/Help/*.hhp 94 | DocProject/Help/Html2 95 | DocProject/Help/html 96 | 97 | # Click-Once directory 98 | publish/ 99 | 100 | # Publish Web Output 101 | *.Publish.xml 102 | 103 | # NuGet Packages Directory 104 | packages/ 105 | 106 | # Windows Azure Build Output 107 | csx 108 | *.build.csdef 109 | 110 | # Windows Store app package directory 111 | AppPackages/ 112 | 113 | # Others 114 | sql/ 115 | *.Cache 116 | ClientBin/ 117 | [Ss]tyle[Cc]op.* 118 | ~$* 119 | *~ 120 | *.dbmdl 121 | *.[Pp]ublish.xml 122 | *.publishsettings 123 | 124 | # RIA/Silverlight projects 125 | Generated_Code/ 126 | 127 | # Backup & report files from converting an old project file to a newer 128 | # Visual Studio version. Backup files are not needed, because we have git ;-) 129 | _UpgradeReport_Files/ 130 | Backup*/ 131 | UpgradeLog*.XML 132 | UpgradeLog*.htm 133 | 134 | # SQL Server files 135 | App_Data/*.mdf 136 | App_Data/*.ldf 137 | 138 | 139 | #LightSwitch generated files 140 | GeneratedArtifacts/ 141 | _Pvt_Extensions/ 142 | ModelManifest.xml 143 | 144 | # ========================= 145 | # Windows detritus 146 | # ========================= 147 | 148 | # Windows image file caches 149 | Thumbs.db 150 | ehthumbs.db 151 | 152 | # Folder config file 153 | Desktop.ini 154 | 155 | # Recycle Bin used on file shares 156 | $RECYCLE.BIN/ 157 | 158 | # Mac desktop service store files 159 | .DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | dist: xenial 3 | sudo: required 4 | mono: none 5 | dotnet: 5.0 6 | script: 7 | - dotnet restore 8 | - dotnet build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CypherCore Tools [![Build Status](https://travis-ci.org/CypherCore/Tools.svg?branch=master)](https://travis-ci.org/CypherCore/Tools) [![Build status](https://ci.appveyor.com/api/projects/status/yb8yp44flip1gst3?svg=true)](https://ci.appveyor.com/project/hondacrx/tools) 2 | 3 | CypherCore is an open source server project for World of Warcraft written in C# 4 | 5 | The current support game version is: 9.0.2.37474 6 | 7 | ### Prerequisites 8 | * Visual Studio 2019 with .NET 5.0 [Download](https://www.visualstudio.com/downloads/) -------------------------------------------------------------------------------- /Source/DataExtractor/DataExtractor.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | true 7 | 9.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/ArmadilloCrypt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | 5 | namespace DataExtractor.CASCLib 6 | { 7 | public class ArmadilloCrypt 8 | { 9 | private byte[] _key; 10 | 11 | public byte[] Key => _key; 12 | 13 | public ArmadilloCrypt(byte[] key) 14 | { 15 | _key = key; 16 | } 17 | 18 | public ArmadilloCrypt(string keyName) 19 | { 20 | if (!LoadKeyFile(keyName, out _key)) 21 | throw new ArgumentException("keyName"); 22 | } 23 | 24 | static bool LoadKeyFile(string keyName, out byte[] key) 25 | { 26 | var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 27 | 28 | FileInfo fi = new(Path.Combine(appDataPath, "Battle.net\\Armadillo", keyName + ".ak")); 29 | 30 | key = null; 31 | 32 | if (!fi.Exists) 33 | return false; 34 | 35 | if (fi.Length != 20) 36 | return false; 37 | 38 | using (var file = fi.OpenRead()) 39 | { 40 | byte[] keyBytes = new byte[16]; 41 | 42 | if (file.Read(keyBytes, 0, keyBytes.Length) != 16) 43 | return false; 44 | 45 | byte[] checkSum = new byte[4]; 46 | 47 | if (file.Read(checkSum, 0, checkSum.Length) != 4) 48 | return false; 49 | 50 | byte[] keyMD5; 51 | 52 | using (MD5 md5 = MD5.Create()) 53 | { 54 | keyMD5 = md5.ComputeHash(keyBytes); 55 | } 56 | 57 | // check first 4 bytes 58 | for (int i = 0; i < checkSum.Length; i++) 59 | { 60 | if (checkSum[i] != keyMD5[i]) 61 | return false; 62 | } 63 | 64 | key = keyBytes; 65 | } 66 | 67 | return true; 68 | } 69 | 70 | public byte[] DecryptFile(string name) 71 | { 72 | using FileStream fs = new(name, FileMode.Open); 73 | return DecryptFile(name, fs); 74 | } 75 | 76 | public byte[] DecryptFile(string name, Stream stream) 77 | { 78 | string fileName = Path.GetFileNameWithoutExtension(name); 79 | 80 | if (fileName.Length != 32) 81 | throw new ArgumentException("name"); 82 | 83 | byte[] IV = fileName.Substring(16).ToByteArray(); 84 | 85 | ICryptoTransform decryptor = KeyService.SalsaInstance.CreateDecryptor(_key, IV); 86 | 87 | using CryptoStream cs = new(stream, decryptor, CryptoStreamMode.Read); 88 | using MemoryStream ms = new(); 89 | cs.CopyTo(ms); 90 | return ms.ToArray(); 91 | } 92 | 93 | public Stream DecryptFileToStream(string name, Stream stream) 94 | { 95 | string fileName = Path.GetFileNameWithoutExtension(name); 96 | 97 | if (fileName.Length != 32) 98 | throw new ArgumentException("name"); 99 | 100 | byte[] IV = fileName.Substring(16).ToByteArray(); 101 | 102 | ICryptoTransform decryptor = KeyService.SalsaInstance.CreateDecryptor(_key, IV); 103 | 104 | using CryptoStream cs = new(stream, decryptor, CryptoStreamMode.Read); 105 | MemoryStream ms = new(); 106 | cs.CopyTo(ms); 107 | ms.Position = 0; 108 | return ms; 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/BLTEStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | 7 | namespace DataExtractor.CASCLib 8 | { 9 | [Serializable] 10 | public class BLTEDecoderException : Exception 11 | { 12 | public int ErrorCode { get; } 13 | 14 | public BLTEDecoderException(int error, string message) : base(message) 15 | { 16 | ErrorCode = error; 17 | } 18 | 19 | public BLTEDecoderException(int error, string fmt, params object[] args) : base(string.Format(fmt, args)) 20 | { 21 | ErrorCode = error; 22 | } 23 | } 24 | 25 | class DataBlock 26 | { 27 | public int CompSize; 28 | public int DecompSize; 29 | public MD5Hash Hash; 30 | public byte[] Data; 31 | } 32 | 33 | public class BLTEStream : Stream 34 | { 35 | private BinaryReader _reader; 36 | private readonly MD5 _md5 = MD5.Create(); 37 | private MemoryStream _memStream; 38 | private DataBlock[] _dataBlocks; 39 | private Stream _stream; 40 | private int _blocksIndex; 41 | private long _length; 42 | 43 | private const byte ENCRYPTION_SALSA20 = 0x53; 44 | private const byte ENCRYPTION_ARC4 = 0x41; 45 | private const int BLTE_MAGIC = 0x45544c42; 46 | 47 | public override bool CanRead => true; 48 | 49 | public override bool CanSeek => true; 50 | 51 | public override bool CanWrite => false; 52 | 53 | public override long Length => _length; 54 | 55 | public override long Position 56 | { 57 | get { return _memStream.Position; } 58 | set 59 | { 60 | while (value > _memStream.Length) 61 | if (!ProcessNextBlock()) 62 | break; 63 | 64 | _memStream.Position = value; 65 | } 66 | } 67 | 68 | public BLTEStream(Stream src, MD5Hash md5) 69 | { 70 | _stream = src; 71 | _reader = new BinaryReader(src); 72 | 73 | Parse(md5); 74 | } 75 | 76 | private void Parse(MD5Hash md5) 77 | { 78 | int size = (int)_reader.BaseStream.Length; 79 | 80 | if (size < 8) 81 | throw new BLTEDecoderException(0, "not enough data: {0}", 8); 82 | 83 | int magic = _reader.ReadInt32(); 84 | 85 | if (magic != BLTE_MAGIC) 86 | throw new BLTEDecoderException(0, "frame header mismatch (bad BLTE file)"); 87 | 88 | int headerSize = _reader.ReadInt32BE(); 89 | 90 | if (CASCConfig.ValidateData) 91 | { 92 | long oldPos = _reader.BaseStream.Position; 93 | 94 | _reader.BaseStream.Position = 0; 95 | 96 | byte[] newHash = _md5.ComputeHash(_reader.ReadBytes(headerSize > 0 ? headerSize : size)); 97 | 98 | if (!md5.EqualsTo(newHash)) 99 | throw new BLTEDecoderException(0, "data corrupted"); 100 | 101 | _reader.BaseStream.Position = oldPos; 102 | } 103 | 104 | int numBlocks = 1; 105 | 106 | if (headerSize > 0) 107 | { 108 | if (size < 12) 109 | throw new BLTEDecoderException(0, "not enough data: {0}", 12); 110 | 111 | byte[] fcbytes = _reader.ReadBytes(4); 112 | 113 | numBlocks = fcbytes[1] << 16 | fcbytes[2] << 8 | fcbytes[3] << 0; 114 | 115 | if (fcbytes[0] != 0x0F || numBlocks == 0) 116 | throw new BLTEDecoderException(0, "bad table format 0x{0:x2}, numBlocks {1}", fcbytes[0], numBlocks); 117 | 118 | int frameHeaderSize = 24 * numBlocks + 12; 119 | 120 | if (headerSize != frameHeaderSize) 121 | throw new BLTEDecoderException(0, "header size mismatch"); 122 | 123 | if (size < frameHeaderSize) 124 | throw new BLTEDecoderException(0, "not enough data: {0}", frameHeaderSize); 125 | } 126 | 127 | _dataBlocks = new DataBlock[numBlocks]; 128 | 129 | for (int i = 0; i < numBlocks; i++) 130 | { 131 | DataBlock block = new(); 132 | 133 | if (headerSize != 0) 134 | { 135 | block.CompSize = _reader.ReadInt32BE(); 136 | block.DecompSize = _reader.ReadInt32BE(); 137 | block.Hash = _reader.Read(); 138 | } 139 | else 140 | { 141 | block.CompSize = size - 8; 142 | block.DecompSize = size - 8 - 1; 143 | block.Hash = default; 144 | } 145 | 146 | _dataBlocks[i] = block; 147 | } 148 | 149 | _memStream = new MemoryStream(_dataBlocks.Sum(b => b.DecompSize)); 150 | 151 | ProcessNextBlock(); 152 | 153 | _length = headerSize == 0 ? _memStream.Length : _memStream.Capacity; 154 | 155 | //for (int i = 0; i < _dataBlocks.Length; i++) 156 | //{ 157 | // ProcessNextBlock(); 158 | //} 159 | } 160 | 161 | private void HandleDataBlock(byte[] data, int index) 162 | { 163 | switch (data[0]) 164 | { 165 | case 0x45: // E (encrypted) 166 | (byte[] decryptedData, bool isDecrypted) = Decrypt(data, index); 167 | if (isDecrypted) 168 | HandleDataBlock(decryptedData, index); 169 | else 170 | _memStream.Write(new byte[_dataBlocks[index].DecompSize], 0, _dataBlocks[index].DecompSize); 171 | break; 172 | case 0x46: // F (frame, recursive) 173 | throw new BLTEDecoderException(1, "DecoderFrame not implemented"); 174 | case 0x4E: // N (not compressed) 175 | _memStream.Write(data, 1, data.Length - 1); 176 | break; 177 | case 0x5A: // Z (zlib compressed) 178 | Decompress(data, _memStream); 179 | break; 180 | default: 181 | throw new BLTEDecoderException(1, "unknown BLTE block type {0} (0x{1:X2})!", (char)data[0], data[0]); 182 | } 183 | } 184 | 185 | private static (byte[] data, bool isDecrypted) Decrypt(byte[] data, int index) 186 | { 187 | byte keyNameSize = data[1]; 188 | 189 | if (keyNameSize == 0 || keyNameSize != 8) 190 | throw new BLTEDecoderException(2, "keyNameSize == 0 || keyNameSize != 8"); 191 | 192 | byte[] keyNameBytes = new byte[keyNameSize]; 193 | Array.Copy(data, 2, keyNameBytes, 0, keyNameSize); 194 | 195 | ulong keyName = BitConverter.ToUInt64(keyNameBytes, 0); 196 | 197 | byte IVSize = data[keyNameSize + 2]; 198 | 199 | if (IVSize != 4 || IVSize > 0x10) 200 | throw new BLTEDecoderException(2, "IVSize != 4 || IVSize > 0x10"); 201 | 202 | byte[] IVpart = new byte[IVSize]; 203 | Array.Copy(data, keyNameSize + 3, IVpart, 0, IVSize); 204 | 205 | if (data.Length < IVSize + keyNameSize + 4) 206 | throw new BLTEDecoderException(2, "data.Length < IVSize + keyNameSize + 4"); 207 | 208 | int dataOffset = keyNameSize + IVSize + 3; 209 | 210 | byte encType = data[dataOffset]; 211 | 212 | if (encType != ENCRYPTION_SALSA20 && encType != ENCRYPTION_ARC4) // 'S' or 'A' 213 | throw new BLTEDecoderException(2, "encType != ENCRYPTION_SALSA20 && encType != ENCRYPTION_ARC4"); 214 | 215 | dataOffset++; 216 | 217 | // expand to 8 bytes 218 | byte[] IV = new byte[8]; 219 | Array.Copy(IVpart, IV, IVpart.Length); 220 | 221 | // magic 222 | for (int shift = 0, i = 0; i < sizeof(int); shift += 8, i++) 223 | { 224 | IV[i] ^= (byte)((index >> shift) & 0xFF); 225 | } 226 | 227 | byte[] key = KeyService.GetKey(keyName); 228 | 229 | bool hasKey = key != null; 230 | 231 | if (key == null) 232 | { 233 | key = new byte[16]; 234 | if (CASCConfig.ThrowOnMissingDecryptionKey && index == 0) 235 | throw new BLTEDecoderException(3, "unknown keyname {0:X16}", keyName); 236 | //return null; 237 | } 238 | 239 | if (encType == ENCRYPTION_SALSA20) 240 | { 241 | ICryptoTransform decryptor = KeyService.SalsaInstance.CreateDecryptor(key, IV); 242 | 243 | return (decryptor.TransformFinalBlock(data, dataOffset, data.Length - dataOffset), hasKey); 244 | } 245 | else 246 | { 247 | // ARC4 ? 248 | throw new BLTEDecoderException(2, "encType ENCRYPTION_ARC4 not implemented"); 249 | } 250 | } 251 | 252 | private static void Decompress(byte[] data, Stream outStream) 253 | { 254 | // skip first 3 bytes (zlib) 255 | using var ms = new MemoryStream(data, 3, data.Length - 3); 256 | using var dfltStream = new DeflateStream(ms, CompressionMode.Decompress); 257 | dfltStream.CopyTo(outStream); 258 | } 259 | 260 | public override void Flush() 261 | { 262 | throw new NotSupportedException(); 263 | } 264 | 265 | public override int Read(byte[] buffer, int offset, int count) 266 | { 267 | if (_memStream.Position + count > _memStream.Length && _blocksIndex < _dataBlocks.Length) 268 | { 269 | if (!ProcessNextBlock()) 270 | return 0; 271 | 272 | return Read(buffer, offset, count); 273 | } 274 | else 275 | { 276 | return _memStream.Read(buffer, offset, count); 277 | } 278 | } 279 | 280 | private bool ProcessNextBlock() 281 | { 282 | if (_blocksIndex == _dataBlocks.Length) 283 | return false; 284 | 285 | long oldPos = _memStream.Position; 286 | _memStream.Position = _memStream.Length; 287 | 288 | DataBlock block = _dataBlocks[_blocksIndex]; 289 | 290 | block.Data = _reader.ReadBytes(block.CompSize); 291 | 292 | if (!block.Hash.IsZeroed() && CASCConfig.ValidateData) 293 | { 294 | byte[] blockHash = _md5.ComputeHash(block.Data); 295 | 296 | if (!block.Hash.EqualsTo(blockHash)) 297 | throw new BLTEDecoderException(0, "MD5 mismatch"); 298 | } 299 | 300 | HandleDataBlock(block.Data, _blocksIndex); 301 | _blocksIndex++; 302 | 303 | _memStream.Position = oldPos; 304 | 305 | return true; 306 | } 307 | 308 | public override long Seek(long offset, SeekOrigin origin) 309 | { 310 | switch (origin) 311 | { 312 | case SeekOrigin.Begin: 313 | Position = offset; 314 | break; 315 | case SeekOrigin.Current: 316 | Position += offset; 317 | break; 318 | case SeekOrigin.End: 319 | Position = Length + offset; 320 | break; 321 | } 322 | 323 | return Position; 324 | } 325 | 326 | public override void SetLength(long value) 327 | { 328 | throw new NotSupportedException(); 329 | } 330 | 331 | public override void Write(byte[] buffer, int offset, int count) 332 | { 333 | throw new InvalidOperationException(); 334 | } 335 | 336 | 337 | protected override void Dispose(bool disposing) 338 | { 339 | try 340 | { 341 | _stream?.Dispose(); 342 | _reader?.Dispose(); 343 | _memStream?.Dispose(); 344 | } 345 | finally 346 | { 347 | _stream = null; 348 | _reader = null; 349 | _memStream = null; 350 | 351 | base.Dispose(disposing); 352 | } 353 | } 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CASCEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace DataExtractor.CASCLib 7 | { 8 | public interface ICASCEntry 9 | { 10 | string Name { get; } 11 | ulong Hash { get; } 12 | int CompareTo(ICASCEntry entry, int col, CASCHandler casc); 13 | } 14 | 15 | public class CASCFolder : ICASCEntry 16 | { 17 | public Dictionary Entries { get; set; } 18 | 19 | public CASCFolder(string name) 20 | { 21 | Entries = new Dictionary(StringComparer.OrdinalIgnoreCase); 22 | Name = name; 23 | } 24 | 25 | public string Name { get; private set; } 26 | 27 | public ulong Hash => 0; 28 | 29 | public ICASCEntry GetEntry(string name) 30 | { 31 | Entries.TryGetValue(name, out ICASCEntry entry); 32 | return entry; 33 | } 34 | 35 | public static IEnumerable GetFiles(IEnumerable entries, IEnumerable selection = null, bool recursive = true) 36 | { 37 | var entries2 = selection != null ? selection.Select(index => entries.ElementAt(index)) : entries; 38 | 39 | foreach (var entry in entries2) 40 | { 41 | if (entry is CASCFile file1) 42 | { 43 | yield return file1; 44 | } 45 | else 46 | { 47 | if (recursive) 48 | { 49 | var folder = entry as CASCFolder; 50 | 51 | foreach (var file in GetFiles(folder.Entries.Select(kv => kv.Value))) 52 | { 53 | yield return file; 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | public int CompareTo(ICASCEntry other, int col, CASCHandler casc) 61 | { 62 | int result = 0; 63 | 64 | if (other is CASCFile) 65 | return -1; 66 | 67 | switch (col) 68 | { 69 | case 0: 70 | case 1: 71 | case 2: 72 | case 3: 73 | result = Name.CompareTo(other.Name); 74 | break; 75 | case 4: 76 | break; 77 | } 78 | 79 | return result; 80 | } 81 | } 82 | 83 | public class CASCFile : ICASCEntry 84 | { 85 | public CASCFile(ulong hash, string fullname) 86 | { 87 | Hash = hash; 88 | FullName = fullname; 89 | } 90 | 91 | public string Name => Path.GetFileName(FullName); 92 | 93 | public string FullName { get; set; } 94 | 95 | public ulong Hash { get; private set; } 96 | 97 | public long GetSize(CASCHandler casc) => casc.GetEncodingEntry(Hash, out EncodingEntry enc) ? enc.Size : 0; 98 | 99 | public int CompareTo(ICASCEntry other, int col, CASCHandler casc) 100 | { 101 | int result = 0; 102 | 103 | if (other is CASCFolder) 104 | return 1; 105 | 106 | switch (col) 107 | { 108 | case 0: 109 | result = Name.CompareTo(other.Name); 110 | break; 111 | case 1: 112 | result = Path.GetExtension(Name).CompareTo(Path.GetExtension(other.Name)); 113 | break; 114 | case 2: 115 | { 116 | var e1 = casc.Root.GetEntries(Hash); 117 | var e2 = casc.Root.GetEntries(other.Hash); 118 | var flags1 = e1.Any() ? e1.First().LocaleFlags : LocaleFlags.None; 119 | var flags2 = e2.Any() ? e2.First().LocaleFlags : LocaleFlags.None; 120 | result = flags1.CompareTo(flags2); 121 | } 122 | break; 123 | case 3: 124 | { 125 | var e1 = casc.Root.GetEntries(Hash); 126 | var e2 = casc.Root.GetEntries(other.Hash); 127 | var flags1 = e1.Any() ? e1.First().ContentFlags : ContentFlags.None; 128 | var flags2 = e2.Any() ? e2.First().ContentFlags : ContentFlags.None; 129 | result = flags1.CompareTo(flags2); 130 | } 131 | break; 132 | case 4: 133 | var size1 = GetSize(casc); 134 | var size2 = (other as CASCFile).GetSize(casc); 135 | 136 | if (size1 == size2) 137 | result = 0; 138 | else 139 | result = size1 < size2 ? -1 : 1; 140 | break; 141 | } 142 | 143 | return result; 144 | } 145 | 146 | public static readonly Dictionary Files = new(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CASCGame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace DataExtractor.CASCLib 5 | { 6 | public enum CASCGameType 7 | { 8 | Unknown, 9 | HotS, 10 | WoW, 11 | D3, 12 | S2, 13 | Agent, 14 | Hearthstone, 15 | Overwatch, 16 | Bna, 17 | Client, 18 | S1, 19 | WC3, 20 | Destiny2, 21 | D2R 22 | } 23 | 24 | public class CASCGame 25 | { 26 | public static CASCGameType DetectLocalGame(string path) 27 | { 28 | if (Directory.Exists(Path.Combine(path, "Data"))) 29 | { 30 | string[] wowWinBins = new string[] { "Wow.exe", "WowT.exe", "WowB.exe", "WowClassic.exe", "WowClassicT.exe", "WowClassicB.exe" }; 31 | 32 | for (int i = 0; i < wowWinBins.Length; i++) 33 | { 34 | if (File.Exists(Path.Combine(path, wowWinBins[i]))) 35 | return CASCGameType.WoW; 36 | } 37 | 38 | string[] wowOsxBins = new string[] { "World of Warcraft.app", "World of Warcraft Test.app", "World of Warcraft Beta.app", "World of Warcraft Classic.app" }; 39 | 40 | for (int i = 0; i < wowOsxBins.Length; i++) 41 | { 42 | if (Directory.Exists(Path.Combine(path, wowOsxBins[i]))) 43 | return CASCGameType.WoW; 44 | } 45 | 46 | string[] subFolders = new string[] { "_retail_", "_ptr_", "_beta_", "_alpha_", "_event1_", "_classic_", "_classic_beta_", "_classic_ptr_", "_classic_era_", "_classic_era_beta_", "_classic_era_ptr_" }; 47 | 48 | foreach (var subFolder in subFolders) 49 | { 50 | foreach (var wowBin in wowWinBins) 51 | { 52 | if (File.Exists(Path.Combine(path, subFolder, wowBin))) 53 | return CASCGameType.WoW; 54 | } 55 | 56 | foreach (var wowBin in wowOsxBins) 57 | { 58 | if (Directory.Exists(Path.Combine(path, subFolder, wowBin))) 59 | return CASCGameType.WoW; 60 | } 61 | } 62 | } 63 | 64 | throw new Exception("Unable to detect game type by path"); 65 | } 66 | 67 | public static CASCGameType DetectGameByUid(string uid) 68 | { 69 | if (uid.StartsWith("wow")) 70 | return CASCGameType.WoW; 71 | 72 | throw new Exception("Unable to detect game type by uid"); 73 | } 74 | 75 | public static string GetDataFolder(CASCGameType gameType) 76 | { 77 | if (gameType == CASCGameType.WoW) 78 | return "Data"; 79 | 80 | throw new Exception("GetDataFolder called with unsupported gameType"); 81 | } 82 | 83 | public static bool SupportsLocaleSelection(CASCGameType gameType) 84 | { 85 | return gameType == CASCGameType.D3 || 86 | gameType == CASCGameType.WoW || 87 | gameType == CASCGameType.HotS || 88 | gameType == CASCGameType.S2 || 89 | gameType == CASCGameType.S1 || 90 | gameType == CASCGameType.WC3 || 91 | gameType == CASCGameType.Overwatch; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CASCHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace DataExtractor.CASCLib 6 | { 7 | public sealed class CASCHandler : CASCHandlerBase 8 | { 9 | private EncodingHandler EncodingHandler; 10 | private DownloadHandler DownloadHandler; 11 | private RootHandlerBase RootHandler; 12 | private InstallHandler InstallHandler; 13 | 14 | public EncodingHandler Encoding => EncodingHandler; 15 | public DownloadHandler Download => DownloadHandler; 16 | public RootHandlerBase Root => RootHandler; 17 | public InstallHandler Install => InstallHandler; 18 | 19 | private CASCHandler(CASCConfig config) : base(config) 20 | { 21 | using (var fs = OpenEncodingFile(this)) 22 | EncodingHandler = new EncodingHandler(fs); 23 | 24 | if ((CASCConfig.LoadFlags & LoadFlags.Download) != 0) 25 | { 26 | using var fs = OpenDownloadFile(EncodingHandler, this); 27 | DownloadHandler = new DownloadHandler(fs); 28 | } 29 | 30 | KeyService.LoadKeys(); 31 | 32 | using (var fs = OpenRootFile(EncodingHandler, this)) 33 | RootHandler = new WowRootHandler(fs); 34 | 35 | if ((CASCConfig.LoadFlags & LoadFlags.Install) != 0) 36 | { 37 | using var fs = OpenInstallFile(EncodingHandler, this); 38 | InstallHandler = new InstallHandler(fs); 39 | } 40 | } 41 | 42 | public static CASCHandler OpenStorage(CASCConfig config) => Open(config); 43 | 44 | public static CASCHandler OpenLocalStorage(string basePath, string product = null) 45 | { 46 | CASCConfig config = CASCConfig.LoadLocalStorageConfig(basePath, product); 47 | 48 | return Open(config); 49 | } 50 | 51 | public static CASCHandler OpenOnlineStorage(string product, string region = "us") 52 | { 53 | CASCConfig config = CASCConfig.LoadOnlineStorageConfig(product, region); 54 | 55 | return Open(config); 56 | } 57 | 58 | private static CASCHandler Open(CASCConfig config) 59 | { 60 | return new CASCHandler(config); 61 | } 62 | 63 | public override bool FileExists(int fileDataId) 64 | { 65 | if (Root is WowRootHandler rh) 66 | return rh.FileExist(fileDataId); 67 | return false; 68 | } 69 | 70 | public override bool FileExists(string file) => FileExists(Hasher.ComputeHash(file)); 71 | 72 | public override bool FileExists(ulong hash) => RootHandler.GetAllEntries(hash).Any(); 73 | 74 | public bool GetEncodingEntry(ulong hash, out EncodingEntry enc) 75 | { 76 | var rootInfos = RootHandler.GetEntries(hash); 77 | if (rootInfos.Any()) 78 | return EncodingHandler.GetEntry(rootInfos.First().MD5, out enc); 79 | 80 | if ((CASCConfig.LoadFlags & LoadFlags.Install) != 0) 81 | { 82 | var installInfos = Install.GetEntries().Where(e => Hasher.ComputeHash(e.Name) == hash && e.Tags.Any(t => t.Type == 1 && t.Name == RootHandler.Locale.ToString())); 83 | if (installInfos.Any()) 84 | return EncodingHandler.GetEntry(installInfos.First().MD5, out enc); 85 | 86 | installInfos = Install.GetEntries().Where(e => Hasher.ComputeHash(e.Name) == hash); 87 | if (installInfos.Any()) 88 | return EncodingHandler.GetEntry(installInfos.First().MD5, out enc); 89 | } 90 | 91 | enc = default; 92 | return false; 93 | } 94 | 95 | public override Stream OpenFile(int fileDataId) 96 | { 97 | if (Root is WowRootHandler rh) 98 | return OpenFile(rh.GetHashByFileDataId(fileDataId)); 99 | 100 | if (CASCConfig.ThrowOnFileNotFound) 101 | throw new FileNotFoundException("FileData: " + fileDataId.ToString()); 102 | return null; 103 | } 104 | 105 | public override Stream OpenFile(string name) => OpenFile(Hasher.ComputeHash(name)); 106 | 107 | public override Stream OpenFile(ulong hash) 108 | { 109 | if (GetEncodingEntry(hash, out EncodingEntry encInfo)) 110 | return OpenFile(encInfo.Key); 111 | 112 | return null; 113 | } 114 | 115 | protected override Stream OpenFileOnline(MD5Hash key) 116 | { 117 | IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); 118 | return OpenFileOnlineInternal(idxInfo, key); 119 | } 120 | 121 | protected override Stream GetLocalDataStream(MD5Hash key) 122 | { 123 | IndexEntry idxInfo = LocalIndex.GetIndexInfo(key); 124 | return GetLocalDataStreamInternal(idxInfo, key); 125 | } 126 | 127 | protected override void ExtractFileOnline(MD5Hash key, string path, string name) 128 | { 129 | IndexEntry idxInfo = CDNIndex.GetIndexInfo(key); 130 | ExtractFileOnlineInternal(idxInfo, key, path, name); 131 | } 132 | 133 | public void Clear() 134 | { 135 | CDNIndex?.Clear(); 136 | CDNIndex = null; 137 | 138 | foreach (var stream in DataStreams) 139 | stream.Value.Dispose(); 140 | 141 | DataStreams.Clear(); 142 | 143 | EncodingHandler?.Clear(); 144 | EncodingHandler = null; 145 | 146 | InstallHandler?.Clear(); 147 | InstallHandler = null; 148 | 149 | LocalIndex?.Clear(); 150 | LocalIndex = null; 151 | 152 | RootHandler?.Clear(); 153 | RootHandler = null; 154 | 155 | DownloadHandler?.Clear(); 156 | DownloadHandler = null; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CASCHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace DataExtractor.CASCLib 7 | { 8 | public abstract class CASCHandlerBase 9 | { 10 | protected LocalIndexHandler LocalIndex; 11 | protected CDNIndexHandler CDNIndex; 12 | 13 | protected static readonly Jenkins96 Hasher = new(); 14 | 15 | protected readonly Dictionary DataStreams = new(); 16 | 17 | public CASCConfig Config { get; protected set; } 18 | 19 | public CASCHandlerBase(CASCConfig config) 20 | { 21 | Config = config; 22 | CDNIndex = CDNIndexHandler.Initialize(config); 23 | 24 | if (!config.OnlineMode) 25 | { 26 | CDNCache.Enabled = false; 27 | LocalIndex = LocalIndexHandler.Initialize(config); 28 | } 29 | } 30 | 31 | public abstract bool FileExists(int fileDataId); 32 | public abstract bool FileExists(string file); 33 | public abstract bool FileExists(ulong hash); 34 | 35 | public abstract Stream OpenFile(int filedata); 36 | public abstract Stream OpenFile(string name); 37 | public abstract Stream OpenFile(ulong hash); 38 | 39 | public Stream OpenFile(MD5Hash key) 40 | { 41 | try 42 | { 43 | if (Config.OnlineMode) 44 | return OpenFileOnline(key); 45 | else 46 | return OpenFileLocal(key); 47 | } 48 | catch (BLTEDecoderException exc) when (exc.ErrorCode == 3) 49 | { 50 | if (CASCConfig.ThrowOnMissingDecryptionKey) 51 | throw exc; 52 | return null; 53 | } 54 | catch// (Exception exc) when (!(exc is BLTEDecoderException)) 55 | { 56 | return OpenFileOnline(key); 57 | } 58 | } 59 | 60 | protected abstract Stream OpenFileOnline(MD5Hash key); 61 | 62 | protected Stream OpenFileOnlineInternal(IndexEntry idxInfo, MD5Hash key) 63 | { 64 | Stream s; 65 | 66 | if (idxInfo != null) 67 | s = CDNIndex.OpenDataFile(idxInfo); 68 | else 69 | s = CDNIndex.OpenDataFileDirect(key); 70 | 71 | BLTEStream blte; 72 | 73 | try 74 | { 75 | blte = new BLTEStream(s, key); 76 | } 77 | catch (BLTEDecoderException exc) when (exc.ErrorCode == 0) 78 | { 79 | CDNCache.Instance.InvalidateFile(idxInfo != null ? Config.Archives[idxInfo.Index] : key.ToHexString()); 80 | return OpenFileOnlineInternal(idxInfo, key); 81 | } 82 | 83 | return blte; 84 | } 85 | 86 | private Stream OpenFileLocal(MD5Hash key) 87 | { 88 | Stream stream = GetLocalDataStream(key); 89 | 90 | return new BLTEStream(stream, key); 91 | } 92 | 93 | protected abstract Stream GetLocalDataStream(MD5Hash key); 94 | 95 | protected Stream GetLocalDataStreamInternal(IndexEntry idxInfo, MD5Hash key) 96 | { 97 | if (idxInfo == null) 98 | throw new Exception("local index missing"); 99 | 100 | Stream dataStream = GetDataStream(idxInfo.Index); 101 | dataStream.Position = idxInfo.Offset; 102 | 103 | using BinaryReader reader = new(dataStream, Encoding.ASCII, true); 104 | byte[] md5 = reader.ReadBytes(16); 105 | Array.Reverse(md5); 106 | 107 | if (!key.EqualsTo9(md5)) 108 | throw new Exception("local data corrupted"); 109 | 110 | int size = reader.ReadInt32(); 111 | 112 | if (size != idxInfo.Size) 113 | throw new Exception("local data corrupted"); 114 | 115 | //byte[] unkData1 = reader.ReadBytes(2); 116 | //byte[] unkData2 = reader.ReadBytes(8); 117 | dataStream.Position += 10; 118 | 119 | byte[] data = reader.ReadBytes(idxInfo.Size - 30); 120 | 121 | return new MemoryStream(data); 122 | } 123 | 124 | protected abstract void ExtractFileOnline(MD5Hash key, string path, string name); 125 | 126 | protected void ExtractFileOnlineInternal(IndexEntry idxInfo, MD5Hash key, string path, string name) 127 | { 128 | if (idxInfo != null) 129 | { 130 | using Stream s = CDNIndex.OpenDataFile(idxInfo); 131 | using BLTEStream blte = new(s, key); 132 | blte.ExtractToFile(path, name); 133 | } 134 | else 135 | { 136 | using Stream s = CDNIndex.OpenDataFileDirect(key); 137 | using BLTEStream blte = new(s, key); 138 | blte.ExtractToFile(path, name); 139 | } 140 | } 141 | 142 | protected static BinaryReader OpenInstallFile(EncodingHandler enc, CASCHandlerBase casc) 143 | { 144 | if (!enc.GetEntry(casc.Config.InstallMD5, out EncodingEntry encInfo)) 145 | throw new FileNotFoundException("encoding info for install file missing!"); 146 | 147 | //ExtractFile(encInfo.Key, ".", "install"); 148 | 149 | return new BinaryReader(casc.OpenFile(encInfo.Key)); 150 | } 151 | 152 | protected BinaryReader OpenDownloadFile(EncodingHandler enc, CASCHandlerBase casc) 153 | { 154 | if (!enc.GetEntry(casc.Config.DownloadMD5, out EncodingEntry encInfo)) 155 | throw new FileNotFoundException("encoding info for download file missing!"); 156 | 157 | //ExtractFile(encInfo.Key, ".", "download"); 158 | 159 | return new BinaryReader(casc.OpenFile(encInfo.Key)); 160 | } 161 | 162 | protected BinaryReader OpenRootFile(EncodingHandler enc, CASCHandlerBase casc) 163 | { 164 | if (!enc.GetEntry(casc.Config.RootMD5, out EncodingEntry encInfo)) 165 | throw new FileNotFoundException("encoding info for root file missing!"); 166 | 167 | //ExtractFile(encInfo.Key, ".", "root"); 168 | 169 | return new BinaryReader(casc.OpenFile(encInfo.Key)); 170 | } 171 | 172 | protected BinaryReader OpenEncodingFile(CASCHandlerBase casc) 173 | { 174 | //ExtractFile(Config.EncodingKey, ".", "encoding"); 175 | 176 | return new BinaryReader(casc.OpenFile(casc.Config.EncodingKey)); 177 | } 178 | 179 | private Stream GetDataStream(int index) 180 | { 181 | if (DataStreams.TryGetValue(index, out Stream stream)) 182 | return stream; 183 | 184 | string dataFolder = CASCGame.GetDataFolder(Config.GameType); 185 | 186 | string dataFile = Path.Combine(Config.BasePath, dataFolder, "data", string.Format("data.{0:D3}", index)); 187 | 188 | stream = new FileStream(dataFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 189 | 190 | DataStreams[index] = stream; 191 | 192 | return stream; 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CDNCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Security.Cryptography; 6 | 7 | namespace DataExtractor.CASCLib 8 | { 9 | public class CacheMetaData 10 | { 11 | public long Size { get; } 12 | public string MD5 { get; } 13 | 14 | public CacheMetaData(long size, string md5) 15 | { 16 | Size = size; 17 | MD5 = md5; 18 | } 19 | } 20 | 21 | public class CDNCache 22 | { 23 | public static bool Enabled { get; set; } = true; 24 | public static bool CacheData { get; set; } = false; 25 | public static bool Validate { get; set; } = true; 26 | public static bool ValidateFast { get; set; } = true; 27 | public static string CachePath { get; set; } = "cache"; 28 | 29 | private readonly MD5 _md5 = MD5.Create(); 30 | 31 | private readonly Dictionary _dataStreams = new(StringComparer.OrdinalIgnoreCase); 32 | 33 | private readonly Dictionary _metaData; 34 | 35 | private readonly CASCConfig _config; 36 | 37 | private static CDNCache _instance; 38 | public static CDNCache Instance => _instance; 39 | 40 | private CDNCache(CASCConfig config) 41 | { 42 | if (Enabled) 43 | { 44 | _config = config; 45 | 46 | string metaFile = Path.Combine(CachePath, "cache.meta"); 47 | 48 | _metaData = new Dictionary(StringComparer.OrdinalIgnoreCase); 49 | 50 | if (File.Exists(metaFile)) 51 | { 52 | var lines = File.ReadLines(metaFile); 53 | 54 | foreach (var line in lines) 55 | { 56 | string[] tokens = line.Split(' '); 57 | _metaData[tokens[0]] = new CacheMetaData(Convert.ToInt64(tokens[1]), tokens[2]); 58 | } 59 | } 60 | } 61 | } 62 | 63 | public static void Init(CASCConfig config) 64 | { 65 | _instance = new CDNCache(config); 66 | } 67 | 68 | public Stream OpenFile(string cdnPath, bool isData) 69 | { 70 | if (!Enabled) 71 | return null; 72 | 73 | if (isData && !CacheData) 74 | return null; 75 | 76 | string file = Path.Combine(CachePath, cdnPath); 77 | 78 | Stream stream = GetDataStream(file, cdnPath); 79 | 80 | if (stream != null) 81 | CDNCacheStats.numFilesOpened++; 82 | 83 | return stream; 84 | } 85 | 86 | private Stream GetDataStream(string file, string cdnPath) 87 | { 88 | string fileName = Path.GetFileName(file); 89 | 90 | if (_dataStreams.TryGetValue(fileName, out Stream stream)) 91 | return stream; 92 | 93 | FileInfo fi = new(file); 94 | 95 | if (!fi.Exists && !DownloadFile(cdnPath, file)) 96 | return null; 97 | 98 | stream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 99 | 100 | if (Validate || ValidateFast) 101 | { 102 | if (!_metaData.TryGetValue(fileName, out CacheMetaData meta)) 103 | meta = GetMetaData(cdnPath, fileName); 104 | 105 | if (meta == null) 106 | throw new InvalidDataException(string.Format("unable to validate file {0}", file)); 107 | 108 | bool sizeOk, md5Ok; 109 | 110 | sizeOk = stream.Length == meta.Size; 111 | md5Ok = ValidateFast || _md5.ComputeHash(stream).ToHexString() == meta.MD5; 112 | 113 | if (sizeOk && md5Ok) 114 | { 115 | _dataStreams.Add(fileName, stream); 116 | return stream; 117 | } 118 | else 119 | { 120 | stream.Close(); 121 | _metaData.Remove(fileName); 122 | fi.Delete(); 123 | return GetDataStream(file, cdnPath); 124 | } 125 | } 126 | 127 | _dataStreams.Add(fileName, stream); 128 | return stream; 129 | } 130 | 131 | private CacheMetaData CacheFile(HttpWebResponse resp, string fileName) 132 | { 133 | var etag = resp.Headers[HttpResponseHeader.ETag]; 134 | 135 | string md5; 136 | 137 | if (etag != null) 138 | { 139 | md5 = etag.Split(':')[0].Substring(1); 140 | } 141 | else 142 | { 143 | md5 = "0"; 144 | 145 | } 146 | 147 | CacheMetaData meta = new(resp.ContentLength, md5); 148 | _metaData[fileName] = meta; 149 | 150 | using (var sw = File.AppendText(Path.Combine(CachePath, "cache.meta"))) 151 | { 152 | sw.WriteLine(string.Format("{0} {1} {2}", fileName, resp.ContentLength, md5.ToUpper())); 153 | } 154 | 155 | return meta; 156 | } 157 | 158 | public void InvalidateFile(string fileName) 159 | { 160 | fileName = fileName.ToLower(); 161 | _metaData.Remove(fileName); 162 | 163 | if (_dataStreams.TryGetValue(fileName, out Stream stream)) 164 | stream.Dispose(); 165 | 166 | _dataStreams.Remove(fileName); 167 | 168 | string file = _config.CDNPath + "/data/" + fileName.Substring(0, 2) + "/" + fileName.Substring(2, 2) + "/" + fileName; 169 | 170 | File.Delete(Path.Combine(CachePath, file)); 171 | 172 | using var sw = File.AppendText(Path.Combine(CachePath, "cache.meta")); 173 | foreach (var meta in _metaData) 174 | { 175 | sw.WriteLine($"{meta.Key} {meta.Value.Size} {meta.Value.MD5}"); 176 | } 177 | } 178 | 179 | private bool DownloadFile(string cdnPath, string path, int numRetries = 0) 180 | { 181 | if (numRetries >= 5) 182 | return false; 183 | 184 | string url = "http://" + _config.CDNHost + "/" + cdnPath; 185 | 186 | Directory.CreateDirectory(Path.GetDirectoryName(path)); 187 | 188 | //using (var client = new HttpClient()) 189 | //{ 190 | // var msg = client.GetAsync(url).Result; 191 | 192 | // using (Stream fs = new FileStream(path, FileMode.Create, FileAccess.Write)) 193 | // { 194 | // //CacheMetaData.AddToCache(resp, path); 195 | // //CopyToStream(stream, fs, resp.ContentLength); 196 | 197 | // msg.Content.CopyToAsync(fs).Wait(); 198 | // } 199 | //} 200 | 201 | DateTime startTime = DateTime.Now; 202 | 203 | //long fileSize = GetFileSize(cdnPath); 204 | 205 | //if (fileSize == -1) 206 | // return false; 207 | 208 | HttpWebRequest req = WebRequest.CreateHttp(url); 209 | req.ReadWriteTimeout = 15000; 210 | 211 | //req.AddRange(0, fileSize - 1); 212 | 213 | HttpWebResponse resp; 214 | 215 | try 216 | { 217 | using (resp = (HttpWebResponse)req.GetResponse()) 218 | using (Stream stream = resp.GetResponseStream()) 219 | using (Stream fs = new FileStream(path, FileMode.Create, FileAccess.Write)) 220 | { 221 | stream.CopyToStream(fs, resp.ContentLength); 222 | CacheFile(resp, Path.GetFileName(path)); 223 | } 224 | } 225 | catch (WebException exc) 226 | { 227 | resp = (HttpWebResponse)exc.Response; 228 | 229 | if (exc.Status == WebExceptionStatus.ProtocolError && (resp.StatusCode == HttpStatusCode.NotFound || resp.StatusCode == (HttpStatusCode)429)) 230 | return DownloadFile(cdnPath, path, numRetries + 1); 231 | else 232 | return false; 233 | } 234 | 235 | TimeSpan timeSpent = DateTime.Now - startTime; 236 | CDNCacheStats.timeSpentDownloading += timeSpent; 237 | CDNCacheStats.numFilesDownloaded++; 238 | 239 | return true; 240 | } 241 | 242 | private long GetFileSize(string cdnPath, int numRetries = 0) 243 | { 244 | if (numRetries >= 5) 245 | return -1; 246 | 247 | string url = "http://" + _config.CDNHost + "/" + cdnPath; 248 | 249 | HttpWebRequest req = WebRequest.CreateHttp(url); 250 | req.Method = "HEAD"; 251 | 252 | HttpWebResponse resp; 253 | 254 | try 255 | { 256 | using (resp = (HttpWebResponse)req.GetResponse()) 257 | { 258 | return resp.ContentLength; 259 | } 260 | } 261 | catch (WebException exc) 262 | { 263 | resp = (HttpWebResponse)exc.Response; 264 | 265 | if (exc.Status == WebExceptionStatus.ProtocolError && (resp.StatusCode == HttpStatusCode.NotFound || resp.StatusCode == (HttpStatusCode)429)) 266 | return GetFileSize(cdnPath, numRetries + 1); 267 | else 268 | return -1; 269 | } 270 | } 271 | 272 | private CacheMetaData GetMetaData(string cdnPath, string fileName, int numRetries = 0) 273 | { 274 | if (numRetries >= 5) 275 | return null; 276 | 277 | string url = "http://" + _config.CDNHost + "/" + cdnPath; 278 | 279 | HttpWebRequest req = WebRequest.CreateHttp(url); 280 | req.Method = "HEAD"; 281 | 282 | HttpWebResponse resp; 283 | 284 | try 285 | { 286 | using (resp = (HttpWebResponse)req.GetResponse()) 287 | { 288 | return CacheFile(resp, fileName); 289 | } 290 | } 291 | catch (WebException exc) 292 | { 293 | resp = (HttpWebResponse)exc.Response; 294 | 295 | if (exc.Status == WebExceptionStatus.ProtocolError && (resp.StatusCode == HttpStatusCode.NotFound || resp.StatusCode == (HttpStatusCode)429)) 296 | return GetMetaData(cdnPath, fileName, numRetries + 1); 297 | else 298 | return null; 299 | } 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CDNCacheStats.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DataExtractor.CASCLib 4 | { 5 | public static class CDNCacheStats 6 | { 7 | public static TimeSpan timeSpentDownloading = TimeSpan.Zero; 8 | public static int numFilesOpened = 0; 9 | public static int numFilesDownloaded = 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/CDNIndexHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | 6 | namespace DataExtractor.CASCLib 7 | { 8 | public class IndexEntry 9 | { 10 | public int Index; 11 | public int Offset; 12 | public int Size; 13 | } 14 | 15 | public class CDNIndexHandler 16 | { 17 | private static readonly MD5HashComparer comparer = new(); 18 | private Dictionary CDNIndexData = new(comparer); 19 | 20 | private CASCConfig config; 21 | 22 | public IReadOnlyDictionary Data => CDNIndexData; 23 | public int Count => CDNIndexData.Count; 24 | 25 | private CDNIndexHandler(CASCConfig cascConfig) 26 | { 27 | config = cascConfig; 28 | } 29 | 30 | public static CDNIndexHandler Initialize(CASCConfig config) 31 | { 32 | var handler = new CDNIndexHandler(config); 33 | 34 | for (int i = 0; i < config.Archives.Count; i++) 35 | { 36 | string archive = config.Archives[i]; 37 | 38 | if (config.OnlineMode) 39 | handler.DownloadIndexFile(archive, i); 40 | else 41 | handler.OpenIndexFile(archive, i); 42 | } 43 | 44 | return handler; 45 | } 46 | 47 | private void ParseIndex(Stream stream, int i) 48 | { 49 | using var br = new BinaryReader(stream); 50 | stream.Seek(-12, SeekOrigin.End); 51 | int count = br.ReadInt32(); 52 | stream.Seek(0, SeekOrigin.Begin); 53 | 54 | if (count * (16 + 4 + 4) > stream.Length) 55 | throw new Exception("ParseIndex failed"); 56 | 57 | for (int j = 0; j < count; ++j) 58 | { 59 | MD5Hash key = br.Read(); 60 | 61 | if (key.IsZeroed()) // wtf? 62 | key = br.Read(); 63 | 64 | if (key.IsZeroed()) // wtf? 65 | throw new Exception("key.IsZeroed()"); 66 | 67 | IndexEntry entry = new() 68 | { 69 | Index = i, 70 | Size = br.ReadInt32BE(), 71 | Offset = br.ReadInt32BE() 72 | }; 73 | CDNIndexData.Add(key, entry); 74 | } 75 | } 76 | 77 | private void DownloadIndexFile(string archive, int i) 78 | { 79 | try 80 | { 81 | string file = config.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive + ".index"; 82 | 83 | Stream stream = CDNCache.Instance.OpenFile(file, false); 84 | 85 | if (stream != null) 86 | { 87 | ParseIndex(stream, i); 88 | } 89 | else 90 | { 91 | string url = "http://" + config.CDNHost + "/" + file; 92 | 93 | using var fs = OpenFile(url); 94 | ParseIndex(fs, i); 95 | } 96 | } 97 | catch (Exception exc) 98 | { 99 | throw new Exception($"DownloadFile failed: {archive} - {exc}"); 100 | } 101 | } 102 | 103 | private void OpenIndexFile(string archive, int i) 104 | { 105 | try 106 | { 107 | string dataFolder = CASCGame.GetDataFolder(config.GameType); 108 | 109 | string path = Path.Combine(config.BasePath, dataFolder, "indices", archive + ".index"); 110 | 111 | if (File.Exists(path)) 112 | { 113 | using FileStream fs = new(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 114 | ParseIndex(fs, i); 115 | } 116 | else 117 | { 118 | DownloadIndexFile(archive, i); 119 | } 120 | } 121 | catch (Exception exc) 122 | { 123 | throw new Exception($"OpenFile failed: {archive} - {exc}"); 124 | } 125 | } 126 | 127 | public Stream OpenDataFile(IndexEntry entry, int numRetries = 0) 128 | { 129 | var archive = config.Archives[entry.Index]; 130 | 131 | string file = config.CDNPath + "/data/" + archive.Substring(0, 2) + "/" + archive.Substring(2, 2) + "/" + archive; 132 | 133 | if (numRetries >= 5) 134 | return null; 135 | 136 | Stream stream = CDNCache.Instance.OpenFile(file, true); 137 | 138 | if (stream != null) 139 | { 140 | stream.Position = entry.Offset; 141 | MemoryStream ms = new(entry.Size); 142 | stream.CopyBytes(ms, entry.Size); 143 | ms.Position = 0; 144 | return ms; 145 | } 146 | 147 | //using (HttpClient client = new HttpClient()) 148 | //{ 149 | // client.DefaultRequestHeaders.Range = new RangeHeaderValue(entry.Offset, entry.Offset + entry.Size - 1); 150 | 151 | // var resp = client.GetStreamAsync(url).Result; 152 | 153 | // MemoryStream ms = new MemoryStream(entry.Size); 154 | // resp.CopyBytes(ms, entry.Size); 155 | // ms.Position = 0; 156 | // return ms; 157 | //} 158 | 159 | string url = "http://" + config.CDNHost + "/" + file; 160 | 161 | HttpWebRequest req = WebRequest.CreateHttp(url); 162 | req.ReadWriteTimeout = 15000; 163 | //req.Headers[HttpRequestHeader.Range] = string.Format("bytes={0}-{1}", entry.Offset, entry.Offset + entry.Size - 1); 164 | req.AddRange(entry.Offset, entry.Offset + entry.Size - 1); 165 | 166 | HttpWebResponse resp; 167 | 168 | try 169 | { 170 | using (resp = (HttpWebResponse)req.GetResponse()) 171 | using (Stream rstream = resp.GetResponseStream()) 172 | { 173 | MemoryStream ms = new(entry.Size); 174 | rstream.CopyBytes(ms, entry.Size); 175 | ms.Position = 0; 176 | return ms; 177 | } 178 | } 179 | catch (WebException exc) 180 | { 181 | resp = (HttpWebResponse)exc.Response; 182 | 183 | if (exc.Status == WebExceptionStatus.ProtocolError && (resp.StatusCode == HttpStatusCode.NotFound || resp.StatusCode == (HttpStatusCode)429)) 184 | return OpenDataFile(entry, numRetries + 1); 185 | else 186 | return null; 187 | } 188 | } 189 | 190 | public Stream OpenDataFileDirect(MD5Hash key) 191 | { 192 | var keyStr = key.ToHexString().ToLower(); 193 | 194 | string file = config.CDNPath + "/data/" + keyStr.Substring(0, 2) + "/" + keyStr.Substring(2, 2) + "/" + keyStr; 195 | 196 | Stream stream = CDNCache.Instance.OpenFile(file, false); 197 | 198 | if (stream != null) 199 | { 200 | stream.Position = 0; 201 | MemoryStream ms = new(); 202 | stream.CopyTo(ms); 203 | ms.Position = 0; 204 | return ms; 205 | } 206 | 207 | string url = "http://" + config.CDNHost + "/" + file; 208 | 209 | return OpenFile(url); 210 | } 211 | 212 | public static Stream OpenConfigFileDirect(CASCConfig cfg, string key) 213 | { 214 | string file = cfg.CDNPath + "/config/" + key.Substring(0, 2) + "/" + key.Substring(2, 2) + "/" + key; 215 | 216 | Stream stream = CDNCache.Instance.OpenFile(file, false); 217 | 218 | if (stream != null) 219 | return stream; 220 | 221 | string url = "http://" + cfg.CDNHost + "/" + file; 222 | 223 | return OpenFileDirect(url); 224 | } 225 | 226 | public static Stream OpenFileDirect(string url) 227 | { 228 | //using (HttpClient client = new HttpClient()) 229 | //{ 230 | // var resp = client.GetStreamAsync(url).Result; 231 | 232 | // MemoryStream ms = new MemoryStream(); 233 | // resp.CopyTo(ms); 234 | // ms.Position = 0; 235 | // return ms; 236 | //} 237 | 238 | HttpWebRequest req = WebRequest.CreateHttp(url); 239 | //long fileSize = GetFileSize(url); 240 | //req.AddRange(0, fileSize - 1); 241 | using HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); 242 | using Stream stream = resp.GetResponseStream(); 243 | MemoryStream ms = new(); 244 | stream.CopyToStream(ms, resp.ContentLength); 245 | ms.Position = 0; 246 | return ms; 247 | } 248 | 249 | private Stream OpenFile(string url) 250 | { 251 | HttpWebRequest req = WebRequest.CreateHttp(url); 252 | req.ReadWriteTimeout = 15000; 253 | //long fileSize = GetFileSize(url); 254 | //req.AddRange(0, fileSize - 1); 255 | using HttpWebResponse resp = (HttpWebResponse)req.GetResponse(); 256 | using Stream stream = resp.GetResponseStream(); 257 | MemoryStream ms = new(); 258 | stream.CopyToStream(ms, resp.ContentLength); 259 | ms.Position = 0; 260 | return ms; 261 | } 262 | 263 | public IndexEntry GetIndexInfo(MD5Hash key) 264 | { 265 | return CDNIndexData.GetValueOrDefault(key); 266 | } 267 | 268 | public void Clear() 269 | { 270 | CDNIndexData.Clear(); 271 | CDNIndexData = null; 272 | 273 | config = null; 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/DownloadHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace DataExtractor.CASCLib 8 | { 9 | public class DownloadEntry 10 | { 11 | public int Index; 12 | //public byte[] Unk; 13 | 14 | public IEnumerable> Tags; 15 | } 16 | 17 | public class DownloadTag 18 | { 19 | public short Type; 20 | public BitArray Bits; 21 | } 22 | 23 | public class DownloadHandler 24 | { 25 | private static readonly MD5HashComparer comparer = new(); 26 | private Dictionary DownloadData = new(comparer); 27 | private Dictionary Tags = new(); 28 | 29 | public int Count => DownloadData.Count; 30 | 31 | public DownloadHandler(BinaryReader stream) 32 | { 33 | stream.Skip(2); // DL 34 | 35 | byte b1 = stream.ReadByte(); 36 | byte b2 = stream.ReadByte(); 37 | byte b3 = stream.ReadByte(); 38 | 39 | int numFiles = stream.ReadInt32BE(); 40 | 41 | short numTags = stream.ReadInt16BE(); 42 | 43 | int numMaskBytes = (numFiles + 7) / 8; 44 | 45 | for (int i = 0; i < numFiles; i++) 46 | { 47 | MD5Hash key = stream.Read(); 48 | 49 | //byte[] unk = stream.ReadBytes(0xA); 50 | stream.Skip(0xA); 51 | 52 | //var entry = new DownloadEntry() { Index = i, Unk = unk }; 53 | var entry = new DownloadEntry() { Index = i }; 54 | 55 | DownloadData.Add(key, entry); 56 | } 57 | 58 | for (int i = 0; i < numTags; i++) 59 | { 60 | DownloadTag tag = new(); 61 | string name = stream.ReadCString(); 62 | tag.Type = stream.ReadInt16BE(); 63 | 64 | byte[] bits = stream.ReadBytes(numMaskBytes); 65 | 66 | for (int j = 0; j < numMaskBytes; j++) 67 | bits[j] = (byte)((bits[j] * 0x0202020202 & 0x010884422010) % 1023); 68 | 69 | tag.Bits = new BitArray(bits); 70 | 71 | Tags.Add(name, tag); 72 | } 73 | } 74 | 75 | public void Dump() 76 | { 77 | foreach (var entry in DownloadData) 78 | { 79 | if (entry.Value.Tags == null) 80 | entry.Value.Tags = Tags.Where(kv => kv.Value.Bits[entry.Value.Index]); 81 | } 82 | } 83 | 84 | public DownloadEntry GetEntry(MD5Hash key) 85 | { 86 | DownloadData.TryGetValue(key, out DownloadEntry entry); 87 | 88 | if (entry != null && entry.Tags == null) 89 | entry.Tags = Tags.Where(kv => kv.Value.Bits[entry.Index]); 90 | 91 | return entry; 92 | } 93 | 94 | public void Clear() 95 | { 96 | Tags.Clear(); 97 | Tags = null; 98 | DownloadData.Clear(); 99 | DownloadData = null; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/EncodingHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace DataExtractor.CASCLib 6 | { 7 | public struct EncodingEntry 8 | { 9 | public MD5Hash Key; 10 | public long Size; 11 | } 12 | 13 | public class EncodingHandler 14 | { 15 | private static readonly MD5HashComparer comparer = new(); 16 | private Dictionary EncodingData = new(comparer); 17 | 18 | private const int CHUNK_SIZE = 4096; 19 | 20 | public int Count => EncodingData.Count; 21 | 22 | public EncodingHandler(BinaryReader stream) 23 | { 24 | stream.Skip(2); // EN 25 | byte Version = stream.ReadByte(); // must be 1 26 | byte CKeyLength = stream.ReadByte(); 27 | byte EKeyLength = stream.ReadByte(); 28 | int CKeyPageSize = stream.ReadInt16BE() * 1024; // KB to bytes 29 | int EKeyPageSize = stream.ReadInt16BE() * 1024; // KB to bytes 30 | int CKeyPageCount = stream.ReadInt32BE(); 31 | int EKeyPageCount = stream.ReadInt32BE(); 32 | byte unk1 = stream.ReadByte(); // must be 0 33 | int ESpecBlockSize = stream.ReadInt32BE(); 34 | 35 | stream.Skip(ESpecBlockSize); 36 | //string[] strings = Encoding.ASCII.GetString(stream.ReadBytes(ESpecBlockSize)).Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries); 37 | 38 | stream.Skip(CKeyPageCount * 32); 39 | //ValueTuple[] aEntries = new ValueTuple[CKeyPageCount]; 40 | 41 | //for (int i = 0; i < CKeyPageCount; ++i) 42 | //{ 43 | // byte[] firstHash = stream.ReadBytes(16); 44 | // byte[] blockHash = stream.ReadBytes(16); 45 | // aEntries[i] = (firstHash, blockHash); 46 | //} 47 | 48 | long chunkStart = stream.BaseStream.Position; 49 | 50 | for (int i = 0; i < CKeyPageCount; ++i) 51 | { 52 | byte keysCount; 53 | 54 | while ((keysCount = stream.ReadByte()) != 0) 55 | { 56 | long fileSize = stream.ReadInt40BE(); 57 | MD5Hash cKey = stream.Read(); 58 | 59 | EncodingEntry entry = new() 60 | { 61 | Size = fileSize 62 | }; 63 | 64 | // how do we handle multiple keys? 65 | for (int ki = 0; ki < keysCount; ++ki) 66 | { 67 | MD5Hash eKey = stream.Read(); 68 | 69 | // use first key for now 70 | if (ki == 0) 71 | entry.Key = eKey; 72 | //else 73 | // Logger.WriteLine("Multiple encoding keys for MD5 {0}: {1}", md5.ToHexString(), key.ToHexString()); 74 | 75 | //Logger.WriteLine("Encoding {0:D2} {1} {2} {3} {4}", keysCount, aEntries[i].Item1.ToHexString(), aEntries[i].Item2.ToHexString(), md5.ToHexString(), key.ToHexString()); 76 | } 77 | 78 | //Encodings[md5] = entry; 79 | EncodingData.Add(cKey, entry); 80 | } 81 | 82 | // each chunk is 4096 bytes, and zero padding at the end 83 | long remaining = CHUNK_SIZE - ((stream.BaseStream.Position - chunkStart) % CHUNK_SIZE); 84 | 85 | if (remaining > 0) 86 | stream.BaseStream.Position += remaining; 87 | } 88 | 89 | stream.Skip(EKeyPageCount * 32); 90 | //for (int i = 0; i < EKeyPageCount; ++i) 91 | //{ 92 | // byte[] firstKey = stream.ReadBytes(16); 93 | // byte[] blockHash = stream.ReadBytes(16); 94 | //} 95 | 96 | long chunkStart2 = stream.BaseStream.Position; 97 | 98 | for (int i = 0; i < EKeyPageCount; ++i) 99 | { 100 | byte[] eKey = stream.ReadBytes(16); 101 | int eSpecIndex = stream.ReadInt32BE(); 102 | long fileSize = stream.ReadInt40BE(); 103 | 104 | // each chunk is 4096 bytes, and zero padding at the end 105 | long remaining = CHUNK_SIZE - ((stream.BaseStream.Position - chunkStart2) % CHUNK_SIZE); 106 | 107 | if (remaining > 0) 108 | stream.BaseStream.Position += remaining; 109 | } 110 | 111 | // string block till the end of file 112 | 113 | //EncodingData.Dump(); 114 | } 115 | 116 | public IEnumerable> Entries 117 | { 118 | get 119 | { 120 | foreach (var entry in EncodingData) 121 | yield return entry; 122 | } 123 | } 124 | 125 | public bool GetEntry(MD5Hash md5, out EncodingEntry enc) => EncodingData.TryGetValue(md5, out enc); 126 | 127 | public void Clear() 128 | { 129 | EncodingData.Clear(); 130 | EncodingData = null; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using System.Runtime.CompilerServices; 8 | using System.Text; 9 | 10 | namespace DataExtractor.CASCLib 11 | { 12 | public static class Extensions 13 | { 14 | public static int ReadInt32BE(this BinaryReader reader) 15 | { 16 | byte[] val = reader.ReadBytes(4); 17 | return val[3] | val[2] << 8 | val[1] << 16 | val[0] << 24; 18 | } 19 | 20 | public static long ReadInt40BE(this BinaryReader reader) 21 | { 22 | byte[] val = reader.ReadBytes(5); 23 | return val[4] | val[3] << 8 | val[2] << 16 | val[1] << 24 | val[0] << 32; 24 | } 25 | 26 | public static void Skip(this BinaryReader reader, int bytes) 27 | { 28 | reader.BaseStream.Position += bytes; 29 | } 30 | 31 | public static uint ReadUInt32BE(this BinaryReader reader) 32 | { 33 | byte[] val = reader.ReadBytes(4); 34 | return (uint)(val[3] | val[2] << 8 | val[1] << 16 | val[0] << 24); 35 | } 36 | 37 | public static Action GetSetter(this FieldInfo fieldInfo) 38 | { 39 | var paramExpression = Expression.Parameter(typeof(T)); 40 | var fieldExpression = Expression.Field(paramExpression, fieldInfo); 41 | var valueExpression = Expression.Parameter(fieldInfo.FieldType); 42 | var assignExpression = Expression.Assign(fieldExpression, valueExpression); 43 | 44 | return Expression.Lambda>(assignExpression, paramExpression, valueExpression).Compile(); 45 | } 46 | 47 | public static Func GetGetter(this FieldInfo fieldInfo) 48 | { 49 | var paramExpression = Expression.Parameter(typeof(T)); 50 | var fieldExpression = Expression.Field(paramExpression, fieldInfo); 51 | 52 | return Expression.Lambda>(fieldExpression, paramExpression).Compile(); 53 | } 54 | 55 | public static T Read(this BinaryReader reader) where T : unmanaged 56 | { 57 | byte[] result = reader.ReadBytes(Unsafe.SizeOf()); 58 | 59 | return Unsafe.ReadUnaligned(ref result[0]); 60 | } 61 | 62 | public static T[] ReadArray(this BinaryReader reader) where T : unmanaged 63 | { 64 | int numBytes = (int)reader.ReadInt64(); 65 | 66 | byte[] source = reader.ReadBytes(numBytes); 67 | 68 | reader.BaseStream.Position += (0 - numBytes) & 0x07; 69 | 70 | return source.CopyTo(); 71 | } 72 | 73 | public static T[] ReadArray(this BinaryReader reader, int size) where T : unmanaged 74 | { 75 | int numBytes = Unsafe.SizeOf() * size; 76 | 77 | byte[] source = reader.ReadBytes(numBytes); 78 | 79 | return source.CopyTo(); 80 | } 81 | 82 | public static unsafe T[] CopyTo(this byte[] src) where T : unmanaged 83 | { 84 | T[] result = new T[src.Length / Unsafe.SizeOf()]; 85 | 86 | if (src.Length > 0) 87 | Unsafe.CopyBlockUnaligned(Unsafe.AsPointer(ref result[0]), Unsafe.AsPointer(ref src[0]), (uint)src.Length); 88 | 89 | return result; 90 | } 91 | 92 | public static short ReadInt16BE(this BinaryReader reader) 93 | { 94 | byte[] val = reader.ReadBytes(2); 95 | return (short)(val[1] | val[0] << 8); 96 | } 97 | 98 | public static void CopyBytes(this Stream input, Stream output, int bytes) 99 | { 100 | byte[] buffer = new byte[0x4000]; 101 | int read; 102 | while (bytes > 0 && (read = input.Read(buffer, 0, Math.Min(buffer.Length, bytes))) > 0) 103 | { 104 | output.Write(buffer, 0, read); 105 | bytes -= read; 106 | } 107 | } 108 | 109 | public static void CopyToStream(this Stream src, Stream dst, long len) 110 | { 111 | long done = 0; 112 | 113 | // TODO: Span+stackalloc 114 | byte[] buf = new byte[0x10000]; 115 | 116 | int count; 117 | do 118 | { 119 | count = src.Read(buf, 0, buf.Length); 120 | dst.Write(buf, 0, count); 121 | 122 | done += count; 123 | } while (count > 0); 124 | } 125 | 126 | public static void ExtractToFile(this Stream input, string path, string name) 127 | { 128 | string fullPath = Path.Combine(path, name); 129 | string dir = Path.GetDirectoryName(fullPath); 130 | 131 | if (!Directory.Exists(dir)) 132 | Directory.CreateDirectory(dir); 133 | 134 | using var fileStream = File.Open(fullPath, FileMode.Create); 135 | input.Position = 0; 136 | input.CopyTo(fileStream); 137 | } 138 | 139 | public static string ToHexString(this byte[] data) 140 | { 141 | return BitConverter.ToString(data).Replace("-", string.Empty); 142 | } 143 | 144 | public static bool EqualsTo(this byte[] hash, byte[] other) 145 | { 146 | if (hash.Length != other.Length) 147 | return false; 148 | for (var i = 0; i < hash.Length; ++i) 149 | if (hash[i] != other[i]) 150 | return false; 151 | return true; 152 | } 153 | 154 | public static bool EqualsToIgnoreLength(this byte[] array, byte[] other) 155 | { 156 | for (var i = 0; i < array.Length; ++i) 157 | if (array[i] != other[i]) 158 | return false; 159 | return true; 160 | } 161 | 162 | public static byte[] Copy(this byte[] array, int len) 163 | { 164 | byte[] ret = new byte[len]; 165 | for (int i = 0; i < len; ++i) 166 | ret[i] = array[i]; 167 | return ret; 168 | } 169 | 170 | public static string ToBinaryString(this BitArray bits) 171 | { 172 | StringBuilder sb = new(bits.Length); 173 | 174 | for (int i = bits.Length - 1; i >= 0; --i) 175 | { 176 | sb.Append(bits[i] ? '1' : '0'); 177 | } 178 | 179 | return sb.ToString(); 180 | } 181 | 182 | public static unsafe bool EqualsTo(this MD5Hash key, byte[] array) 183 | { 184 | if (array.Length != 16) 185 | return false; 186 | 187 | MD5Hash other; 188 | 189 | fixed (byte* ptr = array) 190 | other = *(MD5Hash*)ptr; 191 | 192 | for (int i = 0; i < 2; ++i) 193 | { 194 | ulong keyPart = *(ulong*)(key.Value + i * 8); 195 | ulong otherPart = *(ulong*)(other.Value + i * 8); 196 | 197 | if (keyPart != otherPart) 198 | return false; 199 | } 200 | return true; 201 | } 202 | 203 | public static unsafe bool EqualsTo9(this MD5Hash key, byte[] array) 204 | { 205 | if (array.Length < 9) 206 | return false; 207 | 208 | MD5Hash other; 209 | 210 | fixed (byte* ptr = array) 211 | other = *(MD5Hash*)ptr; 212 | 213 | ulong keyPart = *(ulong*)(key.Value); 214 | ulong otherPart = *(ulong*)(other.Value); 215 | 216 | if (keyPart != otherPart) 217 | return false; 218 | 219 | if (key.Value[8] != other.Value[8]) 220 | return false; 221 | 222 | //for (int i = 0; i < 2; ++i) 223 | //{ 224 | // ulong keyPart = *(ulong*)(key.Value + i * 8); 225 | // ulong otherPart = *(ulong*)(other.Value + i * 8); 226 | 227 | // if (keyPart != otherPart) 228 | // return false; 229 | //} 230 | 231 | return true; 232 | } 233 | 234 | public static unsafe bool EqualsTo(this MD5Hash key, MD5Hash other) 235 | { 236 | for (int i = 0; i < 2; ++i) 237 | { 238 | ulong keyPart = *(ulong*)(key.Value + i * 8); 239 | ulong otherPart = *(ulong*)(other.Value + i * 8); 240 | 241 | if (keyPart != otherPart) 242 | return false; 243 | } 244 | return true; 245 | } 246 | 247 | public static unsafe string ToHexString(this MD5Hash key) 248 | { 249 | byte[] array = new byte[16]; 250 | 251 | fixed (byte* aptr = array) 252 | { 253 | *(MD5Hash*)aptr = key; 254 | } 255 | 256 | return array.ToHexString(); 257 | } 258 | 259 | public static unsafe bool IsZeroed(this MD5Hash key) 260 | { 261 | for (var i = 0; i < 16; ++i) 262 | if (key.Value[i] != 0) 263 | return false; 264 | return true; 265 | } 266 | 267 | public static unsafe MD5Hash ToMD5(this byte[] array) 268 | { 269 | if (array.Length != 16) 270 | throw new ArgumentException("array size != 16"); 271 | 272 | fixed (byte* ptr = array) 273 | { 274 | return *(MD5Hash*)ptr; 275 | } 276 | } 277 | } 278 | 279 | public static class CStringExtensions 280 | { 281 | /// Reads the NULL terminated string from 282 | /// the current stream and advances the current position of the stream by string length + 1. 283 | /// 284 | /// 285 | public static string ReadCString(this BinaryReader reader) 286 | { 287 | return reader.ReadCString(Encoding.UTF8); 288 | } 289 | 290 | /// Reads the NULL terminated string from 291 | /// the current stream and advances the current position of the stream by string length + 1. 292 | /// 293 | /// 294 | public static string ReadCString(this BinaryReader reader, Encoding encoding) 295 | { 296 | var bytes = new List(); 297 | byte b; 298 | while ((b = reader.ReadByte()) != 0) 299 | bytes.Add(b); 300 | return encoding.GetString(bytes.ToArray()); 301 | } 302 | 303 | public static void WriteCString(this BinaryWriter writer, string str) 304 | { 305 | var bytes = Encoding.UTF8.GetBytes(str); 306 | writer.Write(bytes); 307 | writer.Write((byte)0); 308 | } 309 | 310 | public static byte[] ToByteArray(this string str) 311 | { 312 | str = str.Replace(" ", string.Empty); 313 | 314 | var res = new byte[str.Length / 2]; 315 | for (int i = 0; i < res.Length; ++i) 316 | { 317 | res[i] = Convert.ToByte(str.Substring(i * 2, 2), 16); 318 | } 319 | return res; 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/InstallHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | namespace DataExtractor.CASCLib 8 | { 9 | public class InstallEntry 10 | { 11 | public string Name; 12 | public MD5Hash MD5; 13 | public int Size; 14 | 15 | public List Tags; 16 | } 17 | 18 | public class InstallTag 19 | { 20 | public string Name; 21 | public short Type; 22 | public BitArray Bits; 23 | } 24 | 25 | public class InstallHandler 26 | { 27 | private List InstallData = new(); 28 | private static readonly Jenkins96 Hasher = new(); 29 | 30 | public int Count => InstallData.Count; 31 | 32 | public InstallHandler(BinaryReader stream) 33 | { 34 | stream.ReadBytes(2); // IN 35 | 36 | byte b1 = stream.ReadByte(); 37 | byte b2 = stream.ReadByte(); 38 | short numTags = stream.ReadInt16BE(); 39 | int numFiles = stream.ReadInt32BE(); 40 | 41 | int numMaskBytes = (numFiles + 7) / 8; 42 | 43 | List Tags = new(); 44 | 45 | for (int i = 0; i < numTags; i++) 46 | { 47 | InstallTag tag = new() 48 | { 49 | Name = stream.ReadCString(), 50 | Type = stream.ReadInt16BE() 51 | }; 52 | byte[] bits = stream.ReadBytes(numMaskBytes); 53 | 54 | for (int j = 0; j < numMaskBytes; j++) 55 | bits[j] = (byte)((bits[j] * 0x0202020202 & 0x010884422010) % 1023); 56 | 57 | tag.Bits = new BitArray(bits); 58 | 59 | Tags.Add(tag); 60 | } 61 | 62 | for (int i = 0; i < numFiles; i++) 63 | { 64 | InstallEntry entry = new() 65 | { 66 | Name = stream.ReadCString(), 67 | MD5 = stream.Read(), 68 | Size = stream.ReadInt32BE() 69 | }; 70 | InstallData.Add(entry); 71 | 72 | entry.Tags = Tags.FindAll(tag => tag.Bits[i]); 73 | } 74 | } 75 | 76 | public InstallEntry GetEntry(string name) 77 | { 78 | return InstallData.Where(i => i.Name.ToLower() == name.ToLower()).FirstOrDefault(); 79 | } 80 | 81 | public IEnumerable GetEntriesByName(string name) 82 | { 83 | return InstallData.Where(i => i.Name.ToLower() == name.ToLower()); 84 | } 85 | 86 | public IEnumerable GetEntriesByTag(string tag) 87 | { 88 | foreach (var entry in InstallData) 89 | if (entry.Tags.Any(t => t.Name == tag)) 90 | yield return entry; 91 | } 92 | 93 | public IEnumerable GetEntries(ulong hash) 94 | { 95 | foreach (var entry in InstallData) 96 | if (Hasher.ComputeHash(entry.Name) == hash) 97 | yield return entry; 98 | } 99 | 100 | public IEnumerable GetEntries() 101 | { 102 | foreach (var entry in InstallData) 103 | yield return entry; 104 | } 105 | 106 | public void Clear() 107 | { 108 | InstallData.Clear(); 109 | InstallData = null; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/Jenkins96.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace DataExtractor.CASCLib 6 | { 7 | // Implementation of Bob Jenkins' hash function in C# (96 bit internal state) 8 | public class Jenkins96 : HashAlgorithm 9 | { 10 | private ulong hashValue; 11 | private static byte[] hashBytes = new byte[0]; 12 | 13 | private uint Rot(uint x, int k) 14 | { 15 | return (x << k) | (x >> (32 - k)); 16 | } 17 | 18 | public ulong ComputeHash(string str, bool fix = true) 19 | { 20 | var tempstr = fix ? str.Replace('/', '\\').ToUpperInvariant() : str; 21 | byte[] data = Encoding.ASCII.GetBytes(tempstr); 22 | ComputeHash(data); 23 | return hashValue; 24 | } 25 | 26 | public override void Initialize() 27 | { 28 | 29 | } 30 | 31 | protected override unsafe void HashCore(byte[] array, int ibStart, int cbSize) 32 | { 33 | uint length = (uint)array.Length; 34 | uint a = 0xdeadbeef + length; 35 | uint b = a; 36 | uint c = a; 37 | 38 | if (length == 0) 39 | { 40 | hashValue = ((ulong)c << 32) | b; 41 | return; 42 | } 43 | 44 | var newLen = (length + (12 - length % 12) % 12); 45 | 46 | if (length != newLen) 47 | { 48 | Array.Resize(ref array, (int)newLen); 49 | length = newLen; 50 | } 51 | 52 | fixed (byte* bb = array) 53 | { 54 | uint* u = (uint*)bb; 55 | 56 | for (var j = 0; j < length - 12; j += 12) 57 | { 58 | a += *(u + j / 4); 59 | b += *(u + j / 4 + 1); 60 | c += *(u + j / 4 + 2); 61 | 62 | a -= c; a ^= Rot(c, 4); c += b; 63 | b -= a; b ^= Rot(a, 6); a += c; 64 | c -= b; c ^= Rot(b, 8); b += a; 65 | a -= c; a ^= Rot(c, 16); c += b; 66 | b -= a; b ^= Rot(a, 19); a += c; 67 | c -= b; c ^= Rot(b, 4); b += a; 68 | } 69 | 70 | var i = length - 12; 71 | a += *(u + i / 4); 72 | b += *(u + i / 4 + 1); 73 | c += *(u + i / 4 + 2); 74 | 75 | c ^= b; c -= Rot(b, 14); 76 | a ^= c; a -= Rot(c, 11); 77 | b ^= a; b -= Rot(a, 25); 78 | c ^= b; c -= Rot(b, 16); 79 | a ^= c; a -= Rot(c, 4); 80 | b ^= a; b -= Rot(a, 14); 81 | c ^= b; c -= Rot(b, 24); 82 | 83 | hashValue = ((ulong)c << 32) | b; 84 | } 85 | } 86 | 87 | protected override byte[] HashFinal() 88 | { 89 | return hashBytes; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/LocalIndexHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace DataExtractor.CASCLib 7 | { 8 | public class LocalIndexHandler 9 | { 10 | private static readonly MD5HashComparer comparer = new(); 11 | private Dictionary LocalIndexData = new(comparer); 12 | 13 | public int Count 14 | { 15 | get { return LocalIndexData.Count; } 16 | } 17 | 18 | private LocalIndexHandler() 19 | { 20 | 21 | } 22 | 23 | public static LocalIndexHandler Initialize(CASCConfig config) 24 | { 25 | var handler = new LocalIndexHandler(); 26 | 27 | var idxFiles = GetIdxFiles(config); 28 | 29 | if (idxFiles.Count == 0) 30 | throw new FileNotFoundException("idx files missing!"); 31 | 32 | foreach (var idx in idxFiles) 33 | handler.ParseIndex(idx); 34 | 35 | return handler; 36 | } 37 | 38 | private unsafe void ParseIndex(string idx) 39 | { 40 | using var fs = new FileStream(idx, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 41 | using var br = new BinaryReader(fs); 42 | int h2Len = br.ReadInt32(); 43 | int h2Check = br.ReadInt32(); 44 | byte[] h2 = br.ReadBytes(h2Len); 45 | 46 | long padPos = (8 + h2Len + 0x0F) & 0xFFFFFFF0; 47 | fs.Position = padPos; 48 | 49 | int dataLen = br.ReadInt32(); 50 | int dataCheck = br.ReadInt32(); 51 | 52 | int numBlocks = dataLen / 18; 53 | 54 | //byte[] buf = new byte[8]; 55 | 56 | for (int i = 0; i < numBlocks; i++) 57 | { 58 | IndexEntry info = new(); 59 | byte[] keyBytes = br.ReadBytes(9); 60 | Array.Resize(ref keyBytes, 16); 61 | 62 | MD5Hash key; 63 | 64 | fixed (byte* ptr = keyBytes) 65 | key = *(MD5Hash*)ptr; 66 | 67 | byte indexHigh = br.ReadByte(); 68 | int indexLow = br.ReadInt32BE(); 69 | 70 | info.Index = (indexHigh << 2 | (byte)((indexLow & 0xC0000000) >> 30)); 71 | info.Offset = (indexLow & 0x3FFFFFFF); 72 | 73 | //for (int j = 3; j < 8; j++) 74 | // buf[7 - j] = br.ReadByte(); 75 | 76 | //long val = BitConverter.ToInt64(buf, 0); 77 | //info.Index = (int)(val / 0x40000000); 78 | //info.Offset = (int)(val % 0x40000000); 79 | 80 | info.Size = br.ReadInt32(); 81 | 82 | // duplicate keys wtf... 83 | //IndexData[key] = info; // use last key 84 | if (!LocalIndexData.ContainsKey(key)) // use first key 85 | LocalIndexData.Add(key, info); 86 | } 87 | 88 | padPos = (dataLen + 0x0FFF) & 0xFFFFF000; 89 | fs.Position = padPos; 90 | 91 | fs.Position += numBlocks * 18; 92 | //for (int i = 0; i < numBlocks; i++) 93 | //{ 94 | // var bytes = br.ReadBytes(18); // unknown data 95 | //} 96 | 97 | //if (fs.Position != fs.Length) 98 | // throw new Exception("idx file under read"); 99 | } 100 | 101 | private static List GetIdxFiles(CASCConfig config) 102 | { 103 | List latestIdx = new(); 104 | 105 | string dataFolder = CASCGame.GetDataFolder(config.GameType); 106 | string dataPath = Path.Combine(dataFolder, "data"); 107 | 108 | for (int i = 0; i < 0x10; ++i) 109 | { 110 | var files = Directory.EnumerateFiles(Path.Combine(config.BasePath, dataPath), string.Format("{0:x2}*.idx", i)); 111 | 112 | if (files.Count() > 0) 113 | latestIdx.Add(files.Last()); 114 | } 115 | 116 | return latestIdx; 117 | } 118 | 119 | public unsafe IndexEntry GetIndexInfo(MD5Hash key) 120 | { 121 | ulong* ptr = (ulong*)&key; 122 | ptr[1] &= 0xFF; 123 | 124 | return LocalIndexData.GetValueOrDefault(key); 125 | } 126 | 127 | public void Clear() 128 | { 129 | LocalIndexData.Clear(); 130 | LocalIndexData = null; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/MD5HashComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DataExtractor.CASCLib 4 | { 5 | public class MD5HashComparer : IEqualityComparer 6 | { 7 | const uint FnvPrime32 = 16777619; 8 | const uint FnvOffset32 = 2166136261; 9 | 10 | public unsafe bool Equals(MD5Hash x, MD5Hash y) 11 | { 12 | for (int i = 0; i < 16; ++i) 13 | if (x.Value[i] != y.Value[i]) 14 | return false; 15 | 16 | return true; 17 | } 18 | 19 | public int GetHashCode(MD5Hash obj) 20 | { 21 | return To32BitFnv1aHash(obj); 22 | } 23 | 24 | private unsafe int To32BitFnv1aHash(MD5Hash toHash) 25 | { 26 | uint hash = FnvOffset32; 27 | 28 | uint* ptr = (uint*)&toHash; 29 | 30 | for (int i = 0; i < 4; i++) 31 | { 32 | hash ^= ptr[i]; 33 | hash *= FnvPrime32; 34 | } 35 | 36 | return unchecked((int)hash); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/MultiDictionary.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DataExtractor.CASCLib 4 | { 5 | public class MultiDictionary : Dictionary> 6 | { 7 | public void Add(K key, V value) 8 | { 9 | if (TryGetValue(key, out List hset)) 10 | { 11 | hset.Add(value); 12 | } 13 | else 14 | { 15 | hset = new List 16 | { 17 | value 18 | }; 19 | base[key] = hset; 20 | } 21 | } 22 | 23 | public new void Clear() 24 | { 25 | foreach (var kv in this) 26 | { 27 | kv.Value.Clear(); 28 | } 29 | 30 | base.Clear(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/RibbitClient.cs: -------------------------------------------------------------------------------- 1 | using MimeKit; 2 | using System; 3 | using System.IO; 4 | using System.Net.Sockets; 5 | using System.Text; 6 | 7 | namespace DataExtractor.CASCLib 8 | { 9 | public class RibbitClient : IDisposable 10 | { 11 | private const string ribbitHost = ".version.battle.net"; 12 | 13 | private TcpClient client = new(); 14 | 15 | public RibbitClient(string region) 16 | { 17 | client = new TcpClient(region + ribbitHost, 1119); 18 | } 19 | 20 | public string Get(string request) 21 | { 22 | using NetworkStream stream = client.GetStream(); 23 | using StreamReader reader = new(stream); 24 | byte[] req = Encoding.ASCII.GetBytes(request + "\r\n"); 25 | 26 | stream.Write(req, 0, req.Length); 27 | 28 | var message = MimeMessage.Load(stream); 29 | 30 | return message.TextBody; 31 | } 32 | 33 | public Stream GetAsStream(string request) 34 | { 35 | using NetworkStream stream = client.GetStream(); 36 | using StreamReader reader = new(stream); 37 | byte[] req = Encoding.ASCII.GetBytes(request + "\r\n"); 38 | 39 | stream.Write(req, 0, req.Length); 40 | 41 | var message = MimeMessage.Load(stream); 42 | 43 | return new MemoryStream(Encoding.ASCII.GetBytes(message.TextBody)); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | client.Dispose(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/CASCLib/RootHandlers/RootHandlerBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace DataExtractor.CASCLib 5 | { 6 | public abstract class RootHandlerBase 7 | { 8 | protected readonly Jenkins96 Hasher = new(); 9 | protected CASCFolder Root; 10 | 11 | public virtual int Count { get; protected set; } 12 | public virtual int CountTotal { get; protected set; } 13 | public virtual int CountSelect { get; protected set; } 14 | public virtual int CountUnknown { get; protected set; } 15 | public virtual LocaleFlags Locale { get; protected set; } 16 | public bool OverrideArchive { get; protected set; } 17 | 18 | public abstract IEnumerable> GetAllEntries(); 19 | 20 | public abstract IEnumerable GetAllEntries(ulong hash); 21 | 22 | public abstract IEnumerable GetEntries(ulong hash); 23 | 24 | public abstract void LoadListFile(string path); 25 | 26 | public abstract void Clear(); 27 | 28 | protected abstract CASCFolder CreateStorageTree(); 29 | 30 | private static readonly char[] PathDelimiters = new char[] { '/', '\\' }; 31 | 32 | protected void CreateSubTree(CASCFolder root, ulong filehash, string file) 33 | { 34 | string[] parts = file.Split(PathDelimiters); 35 | 36 | CASCFolder folder = root; 37 | 38 | for (int i = 0; i < parts.Length; ++i) 39 | { 40 | bool isFile = (i == parts.Length - 1); 41 | 42 | string entryName = parts[i]; 43 | 44 | ICASCEntry entry = folder.GetEntry(entryName); 45 | 46 | if (entry == null) 47 | { 48 | if (isFile) 49 | { 50 | if (!CASCFile.Files.ContainsKey(filehash)) 51 | { 52 | entry = new CASCFile(filehash, file); 53 | CASCFile.Files[filehash] = (CASCFile)entry; 54 | } 55 | else 56 | entry = CASCFile.Files[filehash]; 57 | } 58 | else 59 | { 60 | entry = new CASCFolder(entryName); 61 | } 62 | 63 | folder.Entries[entryName] = entry; 64 | } 65 | 66 | folder = entry as CASCFolder; 67 | } 68 | } 69 | 70 | protected IEnumerable GetEntriesForSelectedLocale(ulong hash) 71 | { 72 | var rootInfos = GetAllEntries(hash); 73 | 74 | if (!rootInfos.Any()) 75 | yield break; 76 | 77 | var rootInfosLocale = rootInfos.Where(re => (re.LocaleFlags & Locale) != 0); 78 | 79 | foreach (var entry in rootInfosLocale) 80 | yield return entry; 81 | } 82 | 83 | public void MergeInstall(InstallHandler install) 84 | { 85 | if (install == null) 86 | return; 87 | 88 | foreach (var entry in install.GetEntries()) 89 | { 90 | CreateSubTree(Root, Hasher.ComputeHash(entry.Name), entry.Name); 91 | } 92 | } 93 | 94 | public CASCFolder SetFlags(LocaleFlags locale, bool overrideArchive = false, bool createTree = true) 95 | { 96 | Locale = locale; 97 | OverrideArchive = overrideArchive; 98 | 99 | if (createTree) 100 | Root = CreateStorageTree(); 101 | 102 | return Root; 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/ClientReader/BitReader.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System.Runtime.CompilerServices; 19 | using System.Text; 20 | 21 | namespace DataExtractor.Framework.ClientReader 22 | { 23 | public class BitReader 24 | { 25 | public int Position { get; set; } 26 | public int Offset { get; set; } 27 | public byte[] Data { get; set; } 28 | 29 | public BitReader(byte[] data) 30 | { 31 | Data = data; 32 | } 33 | 34 | public BitReader(byte[] data, int offset) 35 | { 36 | Data = data; 37 | Offset = offset; 38 | } 39 | 40 | public T Read(int numBits) where T : struct 41 | { 42 | ulong result = Unsafe.As(ref Data[Offset + (Position >> 3)]) << (64 - numBits - (Position & 7)) >> (64 - numBits); 43 | Position += numBits; 44 | return Unsafe.As(ref result); 45 | } 46 | 47 | public T ReadSigned(int numBits) where T : struct 48 | { 49 | ulong result = Unsafe.As(ref Data[Offset + (Position >> 3)]) << (64 - numBits - (Position & 7)) >> (64 - numBits); 50 | Position += numBits; 51 | ulong signedShift = (1UL << (numBits - 1)); 52 | result = (signedShift ^ result) - signedShift; 53 | return Unsafe.As(ref result); 54 | } 55 | 56 | public string ReadCString() 57 | { 58 | int start = Position; 59 | 60 | while (Data[Offset + (Position >> 3)] != 0) 61 | Position += 8; 62 | 63 | string result = Encoding.UTF8.GetString(Data, Offset + (start >> 3), (Position - start) >> 3); 64 | Position += 8; 65 | return result; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/ClientReader/Structs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | namespace DataExtractor.Framework.ClientReader 19 | { 20 | public sealed class CinematicCameraRecord 21 | { 22 | public uint Id; 23 | public float[] Origin = new float[3]; 24 | public uint SoundID; 25 | public float OriginFacing; 26 | public uint FileDataID; 27 | public int Unknown915; 28 | } 29 | 30 | public sealed class GameObjectDisplayInfoRecord 31 | { 32 | public uint Id; 33 | public float[] GeoBox = new float[6]; 34 | public uint FileDataID; 35 | public short ObjectEffectPackageID; 36 | public float OverrideLootEffectScale; 37 | public float OverrideNameScale; 38 | } 39 | 40 | public sealed class LiquidMaterialRecord 41 | { 42 | public uint Id; 43 | public sbyte Flags; 44 | public sbyte LVF; 45 | } 46 | 47 | public sealed class LiquidObjectRecord 48 | { 49 | public uint Id; 50 | public float FlowDirection; 51 | public float FlowSpeed; 52 | public short LiquidTypeID; 53 | public byte Fishable; 54 | public byte Reflection; 55 | } 56 | 57 | public sealed class LiquidTypeRecord 58 | { 59 | public uint Id; 60 | public string Name; 61 | public string[] Texture = new string[6]; 62 | public ushort Flags; 63 | public byte SoundBank; // used to be "type", maybe needs fixing (works well for now) 64 | public uint SoundID; 65 | public uint SpellID; 66 | public float MaxDarkenDepth; 67 | public float FogDarkenIntensity; 68 | public float AmbDarkenIntensity; 69 | public float DirDarkenIntensity; 70 | public ushort LightID; 71 | public float ParticleScale; 72 | public byte ParticleMovement; 73 | public byte ParticleTexSlots; 74 | public byte MaterialID; 75 | public int MinimapStaticCol; 76 | public byte[] FrameCountTexture = new byte[6]; 77 | public int[] Color = new int[2]; 78 | public float[] Float = new float[18]; 79 | public uint[] Int = new uint[4]; 80 | public float[] Coefficient = new float[4]; 81 | } 82 | 83 | public sealed class MapRecord 84 | { 85 | public uint Id; 86 | public string Directory; 87 | public string MapName; 88 | public string MapDescription0; // Horde 89 | public string MapDescription1; // Alliance 90 | public string PvpShortDescription; 91 | public string PvpLongDescription; 92 | public float[] Corpse = new float[2]; // entrance coordinates in ghost mode (in most cases = normal entrance) 93 | public byte MapType; 94 | public sbyte InstanceType; 95 | public byte ExpansionID; 96 | public ushort AreaTableID; 97 | public short LoadingScreenID; 98 | public short TimeOfDayOverride; 99 | public short ParentMapID; 100 | public short CosmeticParentMapID; 101 | public byte TimeOffset; 102 | public float MinimapIconScale; 103 | public short CorpseMapID; // map_id of entrance map in ghost mode (continent always and in most cases = normal entrance) 104 | public byte MaxPlayers; 105 | public short WindSettingsID; 106 | public int ZmpFileDataID; 107 | public int WdtFileDataID; 108 | public int[] Flags = new int[2]; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Collision/Callbacks.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.GameMath; 19 | using System.Collections.Generic; 20 | using System.Numerics; 21 | 22 | namespace DataExtractor.Framework.Collision 23 | { 24 | public class TriBoundFunc 25 | { 26 | public TriBoundFunc(List vert) 27 | { 28 | vertices = vert; 29 | } 30 | 31 | public void Invoke(MeshTriangle tri, out AxisAlignedBox value) 32 | { 33 | Vector3 lo = vertices[(int)tri.idx0]; 34 | Vector3 hi = lo; 35 | 36 | lo = Vector3.Min(Vector3.Min(lo, vertices[(int)tri.idx1]), vertices[(int)tri.idx2]); 37 | hi = Vector3.Max(Vector3.Max(hi, vertices[(int)tri.idx1]), vertices[(int)tri.idx2]); 38 | 39 | value = new AxisAlignedBox(lo, hi); 40 | } 41 | 42 | List vertices; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Collision/Management/VmapManager2.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | 21 | namespace DataExtractor.Framework.Collision 22 | { 23 | public enum VMAPLoadResult 24 | { 25 | Error, 26 | OK, 27 | Ignored 28 | } 29 | 30 | public class VMapManager2 31 | { 32 | public void Initialize(MultiMap mapData) 33 | { 34 | _childMapData = mapData; 35 | foreach (var pair in mapData) 36 | _parentMapData[pair.Value] = pair.Key; 37 | } 38 | 39 | public VMAPLoadResult LoadMap(string basePath, uint mapId, uint x, uint y) 40 | { 41 | var result = VMAPLoadResult.Ignored; 42 | if (LoadSingleMap(mapId, basePath, x, y)) 43 | { 44 | result = VMAPLoadResult.OK; 45 | var childMaps = _childMapData.LookupByKey(mapId); 46 | foreach (uint childMapId in childMaps) 47 | if (!LoadSingleMap(childMapId, basePath, x, y)) 48 | result = VMAPLoadResult.Error; 49 | } 50 | else 51 | result = VMAPLoadResult.Error; 52 | 53 | return result; 54 | } 55 | 56 | public bool LoadSingleMap(uint mapId, string basePath, uint tileX, uint tileY) 57 | { 58 | var instanceTree = _instanceMapTrees.LookupByKey(mapId); 59 | if (instanceTree == null) 60 | { 61 | string mapFileName = GetMapFileName(mapId); 62 | StaticMapTree newTree = new(mapId, basePath); 63 | if (!newTree.InitMap(mapFileName)) 64 | return false; 65 | 66 | _instanceMapTrees.Add(mapId, newTree); 67 | 68 | instanceTree = newTree; 69 | } 70 | 71 | return instanceTree.LoadMapTile(tileX, tileY, this); 72 | } 73 | 74 | public WorldModel AcquireModelInstance(string basepath, string filename) 75 | { 76 | filename = filename.TrimEnd('\0'); 77 | var model = _loadedModelFiles.LookupByKey(filename); 78 | if (model == null) 79 | { 80 | WorldModel worldmodel = new(); 81 | if (!worldmodel.readFile(basepath + filename)) 82 | { 83 | Console.WriteLine($"VMapManager: could not load '{filename}.vmo'"); 84 | return null; 85 | } 86 | 87 | model = new ManagedModel(); 88 | model.SetModel(worldmodel); 89 | 90 | _loadedModelFiles.Add(filename, model); 91 | } 92 | model.IncRefCount(); 93 | return model.GetModel(); 94 | } 95 | 96 | public void ReleaseModelInstance(string filename) 97 | { 98 | filename = filename.TrimEnd('\0'); 99 | var model = _loadedModelFiles.LookupByKey(filename); 100 | if (model == null) 101 | { 102 | Console.WriteLine($"VMapManager: trying to unload non-loaded file '{filename}'"); 103 | return; 104 | } 105 | if (model.DecRefCount() == 0) 106 | { 107 | //Console.WriteLine($"VMapManager: unloading file '{filename}'"); 108 | _loadedModelFiles.Remove(filename); 109 | } 110 | } 111 | 112 | public void GetInstanceMapTree(out Dictionary instanceMapTree) 113 | { 114 | instanceMapTree = _instanceMapTrees; 115 | } 116 | 117 | public static string GetMapFileName(uint mapId) 118 | { 119 | return string.Format("{0:D4}.vmtree", mapId); 120 | } 121 | 122 | public void UnloadMap(uint mapId, uint x, uint y) 123 | { 124 | var childMaps = _childMapData.LookupByKey(mapId); 125 | foreach (uint childMapId in childMaps) 126 | UnloadSingleMap(childMapId, x, y); 127 | 128 | UnloadSingleMap(mapId, x, y); 129 | } 130 | 131 | public void UnloadSingleMap(uint mapId, uint x, uint y) 132 | { 133 | var instanceTree = _instanceMapTrees.LookupByKey(mapId); 134 | if (instanceTree != null) 135 | { 136 | instanceTree.UnloadMapTile(x, y, this); 137 | if (instanceTree.NumLoadedTiles() == 0) 138 | { 139 | _instanceMapTrees.Remove(mapId); 140 | } 141 | } 142 | } 143 | 144 | public int GetParentMapId(uint mapId) 145 | { 146 | if (_parentMapData.ContainsKey(mapId)) 147 | return (int)_parentMapData[mapId]; 148 | 149 | return -1; 150 | } 151 | 152 | Dictionary _loadedModelFiles = new(); 153 | Dictionary _instanceMapTrees = new(); 154 | MultiMap _childMapData = new(); 155 | Dictionary _parentMapData = new(); 156 | } 157 | 158 | public class ManagedModel 159 | { 160 | public ManagedModel() 161 | { 162 | _model = null; 163 | _refCount = 0; 164 | } 165 | 166 | public void SetModel(WorldModel model) { _model = model; } 167 | public WorldModel GetModel() { return _model; } 168 | public void IncRefCount() { ++_refCount; } 169 | public int DecRefCount() { return --_refCount; } 170 | 171 | WorldModel _model; 172 | int _refCount; 173 | } 174 | } 175 | 176 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Collision/Maps/MapTree.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.Constants; 19 | using System; 20 | using System.Collections.Generic; 21 | using System.IO; 22 | 23 | namespace DataExtractor.Framework.Collision 24 | { 25 | public class StaticMapTree 26 | { 27 | public StaticMapTree(uint mapID, string basePath) 28 | { 29 | _mapId = mapID; 30 | _basePath = basePath; 31 | 32 | if (_basePath.Length > 0 && _basePath[_basePath.Length - 1] != '/' && _basePath[_basePath.Length - 1] != '\\') 33 | _basePath += '/'; 34 | } 35 | 36 | static string GetTileFileName(uint mapID, uint tileX, uint tileY) 37 | { 38 | return $"{mapID:D4}_{tileY:D2}_{tileX:D2}.vmtile"; 39 | } 40 | 41 | public bool InitMap(string fname) 42 | { 43 | bool success = false; 44 | string fullname = _basePath + fname; 45 | if (!File.Exists(fullname)) 46 | return false; 47 | 48 | using (BinaryReader binaryReader = new(File.Open(fullname, FileMode.Open, FileAccess.Read, FileShare.Read))) 49 | { 50 | if (binaryReader.ReadStringFromChars(8) == SharedConst.VMAP_MAGIC) 51 | { 52 | if (binaryReader.ReadStringFromChars(4) == "NODE" && _tree.readFromFile(binaryReader)) 53 | { 54 | _nTreeValues = _tree.primCount(); 55 | _treeValues = new ModelInstance[_nTreeValues]; 56 | success = true; 57 | } 58 | } 59 | 60 | if (success) 61 | { 62 | success = binaryReader.ReadStringFromChars(4) == "SIDX"; 63 | if (success) 64 | { 65 | uint spawnIndicesSize = binaryReader.ReadUInt32(); 66 | for (uint i = 0; i < spawnIndicesSize; ++i) 67 | { 68 | uint spawnId = binaryReader.ReadUInt32(); 69 | uint spawnIndex = binaryReader.ReadUInt32(); 70 | _spawnIndices[spawnId] = spawnIndex; 71 | } 72 | } 73 | } 74 | 75 | } 76 | return success; 77 | } 78 | 79 | public bool LoadMapTile(uint tileX, uint tileY, VMapManager2 vm) 80 | { 81 | if (_treeValues == null) 82 | { 83 | Console.WriteLine("StaticMapTree.LoadMapTile() : tree has not been initialized [{tileX}, {tileY}]"); 84 | return false; 85 | } 86 | 87 | string tilefile = _basePath + GetTileFileName(_mapId, tileX, tileY); 88 | if (File.Exists(tilefile)) 89 | { 90 | using BinaryReader binaryReader = new(File.Open(tilefile, FileMode.Open, FileAccess.Read, FileShare.Read)); 91 | if (binaryReader.ReadStringFromChars(8) != SharedConst.VMAP_MAGIC) 92 | return false; 93 | 94 | uint numSpawns = binaryReader.ReadUInt32(); 95 | for (uint i = 0; i < numSpawns; ++i) 96 | { 97 | // read model spawns 98 | var result = ModelSpawn.ReadFromFile(binaryReader, out ModelSpawn spawn); 99 | if (result) 100 | { 101 | // acquire model instance 102 | WorldModel model = vm.AcquireModelInstance(_basePath, spawn.name); 103 | if (model == null) 104 | Console.WriteLine($"StaticMapTree.LoadMapTile() : could not acquire WorldModel pointer [{tileX}, {tileY}]"); 105 | 106 | // update tree 107 | if (_spawnIndices.ContainsKey(spawn.ID)) 108 | { 109 | uint referencedVal = _spawnIndices[spawn.ID]; 110 | if (!_loadedSpawns.ContainsKey(referencedVal)) 111 | { 112 | if (referencedVal > _nTreeValues) 113 | { 114 | Console.WriteLine($"StaticMapTree.LoadMapTile() : invalid tree element ({referencedVal}/{_nTreeValues}) referenced in tile {tilefile}"); 115 | continue; 116 | } 117 | 118 | _treeValues[referencedVal] = new ModelInstance(spawn, model); 119 | _loadedSpawns[referencedVal] = 1; 120 | } 121 | else 122 | ++_loadedSpawns[referencedVal]; 123 | } 124 | } 125 | } 126 | _loadedTiles[PackTileID(tileX, tileY)] = true; 127 | } 128 | else 129 | _loadedTiles[PackTileID(tileX, tileY)] = false; 130 | 131 | //TC_METRIC_EVENT("map_events", "LoadMapTile", "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY)); 132 | return true; 133 | } 134 | 135 | public void UnloadMapTile(uint tileX, uint tileY, VMapManager2 vm) 136 | { 137 | uint tileID = PackTileID(tileX, tileY); 138 | if (!_loadedTiles.ContainsKey(tileID)) 139 | { 140 | Console.WriteLine("StaticMapTree.UnloadMapTile() : trying to unload non-loaded tile - Map:{iMapID} X:{tileX} Y:{tileY}"); 141 | return; 142 | } 143 | 144 | var tile = _loadedTiles.LookupByKey(tileID); 145 | if (tile) // file associated with tile 146 | { 147 | string tilefile = _basePath + GetTileFileName(_mapId, tileX, tileY); 148 | if (File.Exists(tilefile)) 149 | { 150 | using BinaryReader binaryReader = new(File.Open(tilefile, FileMode.Open, FileAccess.Read, FileShare.Read)); 151 | bool result = true; 152 | if (binaryReader.ReadStringFromChars(8) != SharedConst.VMAP_MAGIC) 153 | result = false; 154 | 155 | uint numSpawns = binaryReader.ReadUInt32(); 156 | for (uint i = 0; i < numSpawns && result; ++i) 157 | { 158 | // read model spawns 159 | result = ModelSpawn.ReadFromFile(binaryReader, out ModelSpawn spawn); 160 | if (result) 161 | { 162 | // release model instance 163 | vm.ReleaseModelInstance(spawn.name); 164 | 165 | // update tree 166 | if (!_spawnIndices.ContainsKey(spawn.ID)) 167 | result = false; 168 | else 169 | { 170 | uint referencedVal = _spawnIndices[spawn.ID]; 171 | if (!_loadedSpawns.ContainsKey(referencedVal)) 172 | Console.WriteLine($"StaticMapTree.UnloadMapTile() : trying to unload non-referenced model '{spawn.name}' (ID:{spawn.ID})"); 173 | else if (--_loadedSpawns[referencedVal] == 0) 174 | { 175 | _treeValues[referencedVal].SetUnloaded(); 176 | _loadedSpawns.Remove(referencedVal); 177 | } 178 | } 179 | } 180 | } 181 | } 182 | } 183 | _loadedTiles.Remove(tileID); 184 | //TC_METRIC_EVENT("map_events", "UnloadMapTile", "Map: " + std::to_string(iMapID) + " TileX: " + std::to_string(tileX) + " TileY: " + std::to_string(tileY)); 185 | } 186 | 187 | public void GetModelInstances(out ModelInstance[] models, out uint count) 188 | { 189 | models = _treeValues; 190 | count = _nTreeValues; 191 | } 192 | 193 | public int NumLoadedTiles() { return _loadedTiles.Count; } 194 | 195 | public static uint PackTileID(uint tileX, uint tileY) { return tileX << 16 | tileY; } 196 | public static void UnpackTileID(uint ID, out uint tileX, out uint tileY) { tileX = ID >> 16; tileY = ID & 0xFF; } 197 | 198 | uint _mapId; 199 | BIH _tree = new(); 200 | ModelInstance[] _treeValues; // the tree entries 201 | uint _nTreeValues; 202 | 203 | Dictionary _spawnIndices = new(); 204 | 205 | // Store all the map tile idents that are loaded for that map 206 | // some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed 207 | // empty tiles have no tile file, hence map with bool instead of just a set (consistency check) 208 | Dictionary _loadedTiles = new(); 209 | // stores to invalidate tree values, unload map, and to be able to report errors 210 | Dictionary _loadedSpawns = new(); 211 | string _basePath; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Collision/Models/Model.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.Constants; 19 | using DataExtractor.Framework.GameMath; 20 | using System; 21 | using System.IO; 22 | using System.Numerics; 23 | 24 | namespace DataExtractor.Framework.Collision 25 | { 26 | public class ModelSpawn 27 | { 28 | public ModelSpawn() { } 29 | public ModelSpawn(ModelSpawn spawn) 30 | { 31 | flags = spawn.flags; 32 | adtId = spawn.adtId; 33 | ID = spawn.ID; 34 | iPos = spawn.iPos; 35 | iRot = spawn.iRot; 36 | iScale = spawn.iScale; 37 | iBound = spawn.iBound; 38 | name = spawn.name; 39 | } 40 | 41 | public AxisAlignedBox GetBounds() { return iBound; } 42 | 43 | public static bool ReadFromFile(BinaryReader reader, out ModelSpawn spawn) 44 | { 45 | spawn = new ModelSpawn(); 46 | spawn.flags = reader.ReadUInt32(); 47 | spawn.adtId = reader.ReadUInt16(); 48 | spawn.ID = reader.ReadUInt32(); 49 | spawn.iPos = reader.ReadVector3(); 50 | spawn.iRot = reader.ReadVector3(); 51 | spawn.iScale = reader.ReadSingle(); 52 | 53 | if ((spawn.flags & ModelFlags.HasBound) != 0) // only WMOs have bound in MPQ, only available after computation 54 | { 55 | Vector3 bLow = reader.ReadVector3(); 56 | Vector3 bHigh = reader.ReadVector3(); 57 | spawn.iBound = new AxisAlignedBox(bLow, bHigh); 58 | } 59 | 60 | int nameLen = reader.ReadInt32(); 61 | spawn.name = reader.ReadString(nameLen); 62 | return true; 63 | } 64 | 65 | public static void WriteToFile(BinaryWriter writer, ModelSpawn spawn) 66 | { 67 | writer.Write(spawn.flags); 68 | writer.Write(spawn.adtId); 69 | writer.Write(spawn.ID); 70 | writer.WriteVector3(spawn.iPos); 71 | writer.WriteVector3(spawn.iRot); 72 | writer.Write(spawn.iScale); 73 | 74 | if ((spawn.flags & ModelFlags.HasBound) != 0) // only WMOs have bound in MPQ, only available after computation 75 | { 76 | writer.WriteVector3(spawn.iBound.Lo); 77 | writer.WriteVector3(spawn.iBound.Hi); 78 | } 79 | 80 | writer.Write(spawn.name.GetByteCount()); 81 | writer.WriteString(spawn.name); 82 | } 83 | 84 | //mapID, tileX, tileY, Flags, ID, Pos, Rot, Scale, Bound_lo, Bound_hi, name 85 | public uint flags; 86 | public ushort adtId; 87 | public uint ID; 88 | public Vector3 iPos; 89 | public Vector3 iRot; 90 | public float iScale; 91 | public AxisAlignedBox iBound = AxisAlignedBox.NaN; 92 | public string name; 93 | } 94 | 95 | public class ModelInstance : ModelSpawn 96 | { 97 | public ModelInstance(ModelSpawn spawn, WorldModel model) : base(spawn) 98 | { 99 | iModel = model; 100 | iInvRot = Matrix3.fromEulerAnglesZYX(MathF.PI * iRot.Y / 180.0f, MathF.PI * iRot.X / 180.0f, MathF.PI * iRot.Z / 180.0f).inverse(); 101 | iInvScale = 1.0f / iScale; 102 | } 103 | 104 | public void SetUnloaded() { iModel = null; } 105 | public WorldModel GetWorldModel() { return iModel; } 106 | 107 | Matrix3 iInvRot; 108 | float iInvScale; 109 | WorldModel iModel; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Constants/SharedConst.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System; 19 | using DataExtractor.CASCLib; 20 | 21 | namespace DataExtractor.Framework.Constants 22 | { 23 | class SharedConst 24 | { 25 | public const uint MAP_MAGIC = 0x5350414D; //"MAPS"; 26 | public const uint MAP_VERSION_MAGIC = 0x392E3176; // v1.9 27 | 28 | public const uint MAP_AREA_MAGIC = 0x41455241; //"AREA"; 29 | public const uint MAP_HEIGHT_MAGIC = 0x5447484D; //"MHGT"; 30 | public const uint MAP_LIQUID_MAGIC = 0x51494C4D; //"MLIQ"; 31 | 32 | public const string VMAP_MAGIC = "VMAP_4.9"; 33 | public const string RAW_VMAP_MAGIC = "VMAP049"; 34 | 35 | public const uint MMAP_MAGIC = 0x4D4D4150; // 'MMAP' 36 | public const uint MMAP_VERSION = 9; 37 | 38 | public const float LIQUID_TILE_SIZE = 533.333f / 128.0f; 39 | 40 | public static LocaleFlags[] WowLocaleToCascLocaleFlags = 41 | { 42 | LocaleFlags.enUS | LocaleFlags.enGB, 43 | LocaleFlags.koKR, 44 | LocaleFlags.frFR, 45 | LocaleFlags.deDE, 46 | LocaleFlags.zhCN, 47 | LocaleFlags.zhTW, 48 | LocaleFlags.esES, 49 | LocaleFlags.esMX, 50 | LocaleFlags.ruRU, 51 | 0, 52 | LocaleFlags.ptBR | LocaleFlags.ptPT, 53 | LocaleFlags.itIT 54 | }; 55 | 56 | public const int DT_NAVMESH_VERSION = 7; 57 | public const int DT_VERTS_PER_POLYGON = 6; 58 | public const int RC_WALKABLE_AREA = 63; 59 | public const int DT_POLY_BITS = 31; 60 | 61 | public const int V9_SIZE = 129; 62 | public const int V9_SIZE_SQ = V9_SIZE * V9_SIZE; 63 | public const int V8_SIZE = 128; 64 | public const int V8_SIZE_SQ = V8_SIZE * V8_SIZE; 65 | public const float GRID_SIZE = 533.3333f; 66 | public const float GRID_PART_SIZE = GRID_SIZE / V8_SIZE; 67 | 68 | // see contrib/extractor/system.cpp, CONF_use_minHeight 69 | public const float INVALID_MAP_LIQ_HEIGHT = -2000.0f; 70 | public const float INVALID_MAP_LIQ_HEIGHT_MAX = 5000.0f; 71 | 72 | public static int ADT_CELLS_PER_GRID = 16; 73 | public static int ADT_CELL_SIZE = 8; 74 | public static int ADT_GRID_SIZE = (ADT_CELLS_PER_GRID * ADT_CELL_SIZE); 75 | 76 | public static int MCVT_HEIGHT_MAP_SIZE = (ADT_CELL_SIZE + 1) * (ADT_CELL_SIZE + 1) + ADT_CELL_SIZE * ADT_CELL_SIZE; 77 | } 78 | 79 | [Flags] 80 | public enum AreaHeaderFlags : ushort 81 | { 82 | None = 0x00, 83 | NoArea = 0x01 84 | } 85 | 86 | [Flags] 87 | public enum HeightHeaderFlags : byte 88 | { 89 | None = 0x00, 90 | NoHeight = 0x01, 91 | AsInt16 = 0x02, 92 | AsInt8 = 0x04, 93 | HasFlightBounds = 0x08 94 | } 95 | 96 | [Flags] 97 | public enum LiquidHeaderFlags : byte 98 | { 99 | None = 0x00, 100 | NoType = 0x01, 101 | NoHeight = 0x02 102 | } 103 | 104 | [Flags] 105 | public enum LiquidHeaderTypeFlags : byte 106 | { 107 | NoWater = 0x00, 108 | Water = 0x01, 109 | Ocean = 0x02, 110 | Magma = 0x04, 111 | Slime = 0x08, 112 | 113 | DarkWater = 0x10, 114 | 115 | AllLiquids = Water | Ocean | Magma | Slime 116 | } 117 | 118 | public enum LiquidType 119 | { 120 | Water = 0, 121 | Ocean = 1, 122 | Magma = 2, 123 | Slime = 3 124 | } 125 | 126 | [Flags] 127 | public enum MopyFlags 128 | { 129 | Unk01 = 0x01, 130 | NoCamCollide = 0x02, 131 | Detail = 0x04, 132 | Collision = 0x08, 133 | Hint = 0x10, 134 | Render = 0x20, 135 | WallSurface = 0x40, // Guessed 136 | CollideHit = 0x80 137 | } 138 | 139 | [Flags] 140 | public enum MCNKFlags : uint 141 | { 142 | HasMCSH = 0x00001, 143 | Impass = 0x00002, 144 | LiquidRiver = 0x00004, 145 | LiquidOcean = 0x00008, 146 | LiquidMagma = 0x00010, 147 | LiquidSlime = 0x00020, 148 | HasMCCV = 0x00040, 149 | DoNotFixAlphaMap = 0x08000, 150 | HighResHoles = 0x10000, 151 | } 152 | 153 | public enum LiquidVertexFormatType : short 154 | { 155 | HeightDepth = 0, 156 | HeightTextureCoord = 1, 157 | Depth = 2, 158 | HeightDepthTextureCoord = 3, 159 | Unk4 = 4, 160 | Unk5 = 5 161 | } 162 | 163 | public struct ModelFlags 164 | { 165 | public const uint M2 = 1; 166 | public const uint HasBound = 1 << 1; 167 | public const uint ParentSpawn = 1 << 2; 168 | } 169 | 170 | public enum Spot 171 | { 172 | Top = 1, 173 | Right = 2, 174 | Left = 3, 175 | Bottom = 4, 176 | Entire = 5 177 | } 178 | 179 | public enum Grid 180 | { 181 | V8, 182 | V9 183 | } 184 | 185 | public enum NavArea 186 | { 187 | Empty = 0, 188 | // areas 1-60 will be used for destructible areas (currently skipped in vmaps, WMO with flag 1) 189 | // ground is the highest value to make recast choose ground over water when merging surfaces very close to each other (shallow water would be walkable) 190 | MagmaSlime = 61, // don't need to differentiate between them 191 | Water = 62, 192 | Ground = 63, 193 | } 194 | 195 | public enum NavTerrainFlag 196 | { 197 | Empty = 0x00, 198 | Ground = 1 << (63 - NavArea.Ground), 199 | Water = 1 << (63 - NavArea.Water), 200 | MagmaSlime = 1 << (63 - NavArea.MagmaSlime) 201 | } 202 | 203 | [Flags] 204 | public enum MODFFlags : ushort 205 | { 206 | Destroyable = 0x01, 207 | UseLod = 0x02, 208 | UnkHasScale = 0x04, 209 | EntryIsFileID = 0x08, 210 | seSetsFromMWDS = 0x80, 211 | } 212 | 213 | [Flags] 214 | public enum MDDFFlags : ushort 215 | { 216 | Biodome = 0x001, 217 | Shrubbery = 0x002, 218 | Unk0x4 = 0x004, 219 | Unk0x8 = 0x008, 220 | LiquidKnown = 0x020, 221 | EntryIsFileID = 0x040, 222 | Unk0x100 = 0x100, 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Detour/DetourNode.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using dtNodeIndex = System.UInt16; 3 | using dtPolyRef = System.UInt64; 4 | 5 | public partial class Detour 6 | { 7 | // From Thomas Wang, https://gist.github.com/badboy/6267743 8 | public static uint dtHashRef(dtPolyRef a) 9 | { 10 | a = (~a) + (a << 18); // a = (a << 18) - a - 1; 11 | a = a ^ (a >> 31); 12 | a = a * 21; // a = (a + (a << 2)) + (a << 4); 13 | a = a ^ (a >> 11); 14 | a = a + (a << 6); 15 | a = a ^ (a >> 22); 16 | return (uint)a; 17 | } 18 | } 19 | 20 | public partial class Detour 21 | { 22 | public enum dtNodeFlags 23 | { 24 | DT_NODE_OPEN = 0x01, 25 | DT_NODE_CLOSED = 0x02, 26 | DT_NODE_PARENT_DETACHED = 0x04, // parent of the node is not adjacent. Found using raycast. 27 | }; 28 | 29 | public const dtNodeIndex DT_NULL_IDX = dtNodeIndex.MaxValue; //(dtNodeIndex)~0; 30 | 31 | public class dtNode 32 | { 33 | public float[] pos = new float[3]; //< Position of the node. 34 | public float cost; //< Cost from previous node to current node. 35 | public float total; //< Cost up to the node. 36 | public uint pidx;// : 24; //< Index to parent node. 37 | public uint state;// : 2; ///< extra state information. A polyRef can have multiple nodes with different extra info. see DT_MAX_STATES_PER_NODE 38 | public byte flags;// : 3; //< Node flags 0/open/closed. 39 | public dtPolyRef id; //< Polygon ref the node corresponds to. 40 | /// 41 | public static int getSizeOf() 42 | { 43 | //C# can't guess the sizeof of the float array, let's pretend 44 | return sizeof(float) * (3 + 1 + 1) 45 | + sizeof(uint) 46 | + sizeof(byte) 47 | + sizeof(dtPolyRef); 48 | } 49 | public void dtcsClearFlag(dtNodeFlags flag) 50 | { 51 | unchecked 52 | { 53 | flags &= (byte)(~flag); 54 | } 55 | } 56 | public void dtcsSetFlag(dtNodeFlags flag) 57 | { 58 | flags |= (byte)flag; 59 | } 60 | public bool dtcsTestFlag(dtNodeFlags flag) 61 | { 62 | return (flags & (byte)flag) != 0; 63 | } 64 | }; 65 | 66 | 67 | public class dtNodePool 68 | { 69 | private dtNode[] m_nodes; 70 | private dtNodeIndex[] m_first; 71 | private dtNodeIndex[] m_next; 72 | private int m_maxNodes; 73 | private int m_hashSize; 74 | private int m_nodeCount; 75 | 76 | ////////////////////////////////////////////////////////////////////////////////////////// 77 | public dtNodePool(int maxNodes, int hashSize) 78 | { 79 | m_maxNodes = maxNodes; 80 | m_hashSize = hashSize; 81 | 82 | Debug.Assert(dtNextPow2((uint)m_hashSize) == (uint)m_hashSize); 83 | Debug.Assert(m_maxNodes > 0); 84 | 85 | m_nodes = new dtNode[m_maxNodes]; 86 | dtcsArrayItemsCreate(m_nodes); 87 | m_next = new dtNodeIndex[m_maxNodes]; 88 | m_first = new dtNodeIndex[hashSize]; 89 | 90 | Debug.Assert(m_nodes != null); 91 | Debug.Assert(m_next != null); 92 | Debug.Assert(m_first != null); 93 | 94 | for (int i = 0; i < hashSize; ++i) 95 | { 96 | m_first[i] = DT_NULL_IDX; 97 | } 98 | for (int i = 0; i < m_maxNodes; ++i) 99 | { 100 | m_next[i] = DT_NULL_IDX; 101 | } 102 | } 103 | 104 | public void clear() 105 | { 106 | for (int i = 0; i < m_hashSize; ++i) 107 | { 108 | m_first[i] = DT_NULL_IDX; 109 | } 110 | m_nodeCount = 0; 111 | } 112 | 113 | public uint getNodeIdx(dtNode node) 114 | { 115 | if (node == null) 116 | return 0; 117 | 118 | return (uint)(System.Array.IndexOf(m_nodes, node)) + 1; 119 | } 120 | 121 | public dtNode getNodeAtIdx(uint idx) 122 | { 123 | if (idx == 0) 124 | return null; 125 | return m_nodes[idx - 1]; 126 | } 127 | 128 | public int getMemUsed() 129 | { 130 | return 131 | sizeof(int) * 3 + 132 | dtNode.getSizeOf() * m_maxNodes + 133 | sizeof(dtNodeIndex) * m_maxNodes + 134 | sizeof(dtNodeIndex) * m_hashSize; 135 | } 136 | 137 | public int getMaxNodes() 138 | { 139 | return m_maxNodes; 140 | } 141 | 142 | public int getHashSize() 143 | { 144 | return m_hashSize; 145 | } 146 | public dtNodeIndex getFirst(int bucket) 147 | { 148 | return m_first[bucket]; 149 | } 150 | public dtNodeIndex getNext(int i) 151 | { 152 | return m_next[i]; 153 | } 154 | 155 | public dtNode findNode(dtPolyRef id) 156 | { 157 | uint bucket = (uint)(dtHashRef(id) & (m_hashSize - 1)); 158 | dtNodeIndex i = m_first[bucket]; 159 | while (i != DT_NULL_IDX) 160 | { 161 | if (m_nodes[i].id == id) 162 | return m_nodes[i]; 163 | i = m_next[i]; 164 | } 165 | return null; 166 | } 167 | 168 | public dtNode getNode(dtPolyRef id, byte state = 0) 169 | { 170 | uint bucket = (uint)(dtHashRef(id) & (m_hashSize - 1)); 171 | dtNodeIndex i = m_first[bucket]; 172 | dtNode node = null; 173 | while (i != DT_NULL_IDX) 174 | { 175 | if (m_nodes[i].id == id && m_nodes[i].state == state) 176 | return m_nodes[i]; 177 | i = m_next[i]; 178 | } 179 | 180 | if (m_nodeCount >= m_maxNodes) 181 | return null; 182 | 183 | i = (dtNodeIndex)m_nodeCount; 184 | m_nodeCount++; 185 | 186 | // Init node 187 | node = m_nodes[i]; 188 | node.pidx = 0; 189 | node.cost = 0; 190 | node.total = 0; 191 | node.id = id; 192 | node.state = state; 193 | node.flags = 0; 194 | 195 | m_next[i] = m_first[bucket]; 196 | m_first[bucket] = i; 197 | 198 | return node; 199 | } 200 | } 201 | 202 | 203 | ////////////////////////////////////////////////////////////////////////////////////////// 204 | public class dtNodeQueue 205 | { 206 | private dtNode[] m_heap; 207 | private int m_capacity; 208 | private int m_size; 209 | 210 | public dtNodeQueue(int n) 211 | { 212 | m_capacity = n; 213 | Debug.Assert(m_capacity > 0); 214 | 215 | m_heap = new dtNode[m_capacity + 1];//(dtNode**)dtAlloc(sizeof(dtNode*)*(m_capacity+1), DT_ALLOC_PERM); 216 | Debug.Assert(m_heap != null); 217 | } 218 | 219 | public void clear() 220 | { 221 | m_size = 0; 222 | } 223 | 224 | public dtNode top() 225 | { 226 | return m_heap[0]; 227 | } 228 | 229 | public dtNode pop() 230 | { 231 | dtNode result = m_heap[0]; 232 | m_size--; 233 | trickleDown(0, m_heap[m_size]); 234 | return result; 235 | } 236 | 237 | public void push(dtNode node) 238 | { 239 | m_size++; 240 | bubbleUp(m_size - 1, node); 241 | } 242 | 243 | public void modify(dtNode node) 244 | { 245 | for (int i = 0; i < m_size; ++i) 246 | { 247 | if (m_heap[i] == node) 248 | { 249 | bubbleUp(i, node); 250 | return; 251 | } 252 | } 253 | } 254 | 255 | public bool empty() 256 | { 257 | return m_size == 0; 258 | } 259 | 260 | public int getMemUsed() 261 | { 262 | return sizeof(int) * 2 + 263 | dtNode.getSizeOf() * (m_capacity + 1); 264 | } 265 | 266 | public int getCapacity() 267 | { 268 | return m_capacity; 269 | } 270 | 271 | 272 | public void bubbleUp(int i, dtNode node) 273 | { 274 | int parent = (i - 1) / 2; 275 | // note: (index > 0) means there is a parent 276 | while ((i > 0) && (m_heap[parent].total > node.total)) 277 | { 278 | m_heap[i] = m_heap[parent]; 279 | i = parent; 280 | parent = (i - 1) / 2; 281 | } 282 | m_heap[i] = node; 283 | } 284 | 285 | public void trickleDown(int i, dtNode node) 286 | { 287 | int child = (i * 2) + 1; 288 | while (child < m_size) 289 | { 290 | if (((child + 1) < m_size) && 291 | (m_heap[child].total > m_heap[child + 1].total)) 292 | { 293 | child++; 294 | } 295 | m_heap[i] = m_heap[child]; 296 | i = child; 297 | child = (i * 2) + 1; 298 | } 299 | bubbleUp(i, node); 300 | } 301 | } 302 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Detour/DetourStatus.cs: -------------------------------------------------------------------------------- 1 | using dtStatus = System.UInt32; 2 | 3 | public static partial class Detour 4 | { 5 | // High level status. 6 | public const uint DT_FAILURE = 1u << 31; // Operation failed. 7 | public const uint DT_SUCCESS = 1u << 30; // Operation succeed. 8 | public const uint DT_IN_PROGRESS = 1u << 29; // Operation still in progress. 9 | 10 | // Detail information for status. 11 | public const uint DT_STATUS_DETAIL_MASK = 0x0ffffff; 12 | public const uint DT_WRONG_MAGIC = 1 << 0; // Input data is not recognized. 13 | public const uint DT_WRONG_VERSION = 1 << 1; // Input data is in wrong version. 14 | public const uint DT_OUT_OF_MEMORY = 1 << 2; // Operation ran out of memory. 15 | public const uint DT_INVALID_PARAM = 1 << 3; // An input parameter was invalid. 16 | public const uint DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the query was too small to store all results. 17 | public const uint DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search. 18 | public const uint DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess. 19 | 20 | 21 | // Returns true of status is success. 22 | public static bool dtStatusSucceed(dtStatus status) 23 | { 24 | return (status & DT_SUCCESS) != 0; 25 | } 26 | 27 | // Returns true of status is failure. 28 | public static bool dtStatusFailed(dtStatus status) 29 | { 30 | return (status & DT_FAILURE) != 0; 31 | } 32 | 33 | // Returns true of status is in progress. 34 | public static bool dtStatusInProgress(dtStatus status) 35 | { 36 | return (status & DT_IN_PROGRESS) != 0; 37 | } 38 | 39 | // Returns true if specific detail is set. 40 | public static bool dtStatusDetail(dtStatus status, uint detail) 41 | { 42 | return (status & detail) != 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Extensions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.GameMath; 19 | using System.Collections.Generic; 20 | using System.IO; 21 | using System.Numerics; 22 | using System.Runtime.CompilerServices; 23 | using System.Runtime.InteropServices; 24 | using System.Text; 25 | 26 | namespace System 27 | { 28 | public static class Extensions 29 | { 30 | public static bool Empty(this ICollection collection) 31 | { 32 | return collection.Count == 0; 33 | } 34 | 35 | public static bool Empty(this IDictionary dictionary) 36 | { 37 | return dictionary.Count == 0; 38 | } 39 | 40 | public static TValue LookupByKey(this IDictionary dict, object key) 41 | { 42 | TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); 43 | return dict.TryGetValue(newkey, out TValue val) ? val : default; 44 | } 45 | public static TValue LookupByKey(this IDictionary dict, TKey key) 46 | { 47 | return dict.TryGetValue(key, out TValue val) ? val : default; 48 | } 49 | 50 | public static bool HasAnyFlag(this T value, T flag) where T : struct 51 | { 52 | long lValue = Convert.ToInt64(value); 53 | long lFlag = Convert.ToInt64(flag); 54 | return (lValue & lFlag) != 0; 55 | } 56 | 57 | #region BinaryReader 58 | public static string ReadCString(this BinaryReader reader) 59 | { 60 | byte num; 61 | List temp = new(); 62 | 63 | while ((num = reader.ReadByte()) != 0) 64 | temp.Add(num); 65 | 66 | return Encoding.UTF8.GetString(temp.ToArray()); 67 | } 68 | public static string ReadString(this BinaryReader reader, int count) 69 | { 70 | return Encoding.UTF8.GetString(reader.ReadBytes(count)); 71 | } 72 | public static string ReadStringFromChars(this BinaryReader reader, int count, bool reverseString = false) 73 | { 74 | byte[] values = new byte[count]; 75 | if (reverseString) 76 | { 77 | for (var i = count - 1; i >= 0; --i) 78 | values[i] = reader.ReadByte(); 79 | } 80 | else 81 | { 82 | for (var i = 0; i < count; ++i) 83 | values[i] = reader.ReadByte(); 84 | } 85 | 86 | return Encoding.UTF8.GetString(values); 87 | } 88 | 89 | public static T[] ReadArray(this BinaryReader reader, uint size) where T : struct 90 | { 91 | int numBytes = Unsafe.SizeOf() * (int)size; 92 | 93 | byte[] source = reader.ReadBytes(numBytes); 94 | 95 | T[] result = new T[source.Length / Unsafe.SizeOf()]; 96 | 97 | if (source.Length > 0) 98 | { 99 | unsafe 100 | { 101 | Unsafe.CopyBlockUnaligned(Unsafe.AsPointer(ref result[0]), Unsafe.AsPointer(ref source[0]), (uint)source.Length); 102 | } 103 | } 104 | 105 | return result; 106 | } 107 | public static T ReadStruct(this BinaryReader reader, int size = 0) where T : struct 108 | { 109 | byte[] data = reader.ReadBytes(Marshal.SizeOf(typeof(T))); 110 | GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); 111 | T returnObject = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); 112 | 113 | handle.Free(); 114 | return returnObject; 115 | } 116 | public static T Read(this BinaryReader reader) where T : struct 117 | { 118 | byte[] result = reader.ReadBytes(Unsafe.SizeOf()); 119 | 120 | return Unsafe.ReadUnaligned(ref result[0]); 121 | } 122 | public static Vector3 ReadVector3(this BinaryReader reader) 123 | { 124 | return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); 125 | } 126 | #endregion 127 | 128 | #region BinaryWriter 129 | public static void WriteString(this BinaryWriter writer, string str) 130 | { 131 | writer.Write(Encoding.UTF8.GetBytes(str)); 132 | } 133 | public static void WriteCString(this BinaryWriter writer, string str) 134 | { 135 | writer.Write(Encoding.UTF8.GetBytes(str)); 136 | writer.Write((byte)0); 137 | } 138 | public static void WriteStruct(this BinaryWriter writer, T obj) where T : struct 139 | { 140 | int length = Marshal.SizeOf(obj); 141 | IntPtr ptr = Marshal.AllocHGlobal(length); 142 | byte[] myBuffer = new byte[length]; 143 | 144 | Marshal.StructureToPtr(obj, ptr, true); 145 | Marshal.Copy(ptr, myBuffer, 0, length); 146 | Marshal.FreeHGlobal(ptr); 147 | 148 | writer.Write(myBuffer); 149 | } 150 | public static void WriteVector3(this BinaryWriter writer, Vector3 vector) 151 | { 152 | writer.Write(vector.X); 153 | writer.Write(vector.Y); 154 | writer.Write(vector.Z); 155 | } 156 | #endregion 157 | 158 | #region Strings 159 | public static bool IsEmpty(this string str) 160 | { 161 | return string.IsNullOrEmpty(str); 162 | } 163 | public static string GetPlainName(this string fileName, int index = -1) 164 | { 165 | if (index == -1) 166 | index = fileName.LastIndexOf('\\'); 167 | 168 | if (index != -1) 169 | fileName = fileName.Substring(index + 1); 170 | 171 | if (fileName.Contains("FILE")) 172 | return fileName; 173 | 174 | fileName = fileName.FixNameCase(); 175 | fileName = fileName.Replace(' ', '_'); 176 | 177 | return fileName; 178 | } 179 | public static string FixNameCase(this string name) 180 | { 181 | char[] ptr = name.ToCharArray(); 182 | 183 | int i = name.Length - 1; 184 | //extension in lowercase 185 | for (; ptr[i] != '.'; --i) 186 | ptr[i] = char.ToLower(ptr[i]); 187 | 188 | for (; i >= 0; --i) 189 | { 190 | if (i > 0 && ptr[i] >= 'A' && ptr[i] <= 'Z' && char.IsLetter(ptr[i - 1])) 191 | ptr[i] = char.ToLower(ptr[i]); 192 | else if ((i == 0 || !Char.IsLetter(ptr[i - 1])) && ptr[i] >= 'a' && ptr[i] <= 'z') 193 | ptr[i] = char.ToUpper(ptr[i]); 194 | } 195 | 196 | return new string(ptr); 197 | } 198 | public static bool Compare(this byte[] b, byte[] b2) 199 | { 200 | for (int i = 0; i < b2.Length; i++) 201 | if (b[i] != b2[i]) 202 | return false; 203 | 204 | return true; 205 | } 206 | public static int GetByteCount(this string str) 207 | { 208 | if (str.IsEmpty()) 209 | return 0; 210 | 211 | return Encoding.UTF8.GetByteCount(str); 212 | } 213 | #endregion 214 | 215 | #region Math 216 | public static bool isFinite(this Vector3 vec) => 217 | float.IsInfinity(vec.X) && float.IsInfinity(vec.Y) && float.IsInfinity(vec.Z); 218 | 219 | public static bool isNaN(this Vector3 vec) => 220 | float.IsNaN(vec.X) && float.IsNaN(vec.Y) && float.IsNaN(vec.Z); 221 | 222 | public enum Axis { X = 0, Y = 1, Z = 2, Detect = -1 }; 223 | public static Axis PrimaryAxis(this Vector3 vec) 224 | { 225 | double nx = Math.Abs(vec.X); 226 | double ny = Math.Abs(vec.Y); 227 | double nz = Math.Abs(vec.Z); 228 | 229 | Axis a; 230 | if (nx > ny) 231 | a = (nx > nz) ? Axis.X : Axis.Z; 232 | else 233 | a = (ny > nz) ? Axis.Y : Axis.Z; 234 | 235 | return a; 236 | } 237 | 238 | public static float GetAt(this Vector3 vector, long index) 239 | { 240 | return index switch 241 | { 242 | 0 => vector.X, 243 | 1 => vector.Y, 244 | 2 => vector.Z, 245 | _ => throw new IndexOutOfRangeException(), 246 | }; 247 | } 248 | 249 | public static void SetAt(this ref Vector3 vector, float value, long index) 250 | { 251 | switch (index) 252 | { 253 | case 0: 254 | vector.X = value; 255 | break; 256 | case 1: 257 | vector.Y = value; 258 | break; 259 | case 2: 260 | vector.Z = value; 261 | break; 262 | default: 263 | throw new IndexOutOfRangeException(); 264 | } 265 | } 266 | 267 | public static Matrix3 ToRotationMatrix(this Quaternion x) => new(x); 268 | #endregion 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/GameMath/AxisAlignedBox.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System; 19 | using System.Numerics; 20 | using System.Runtime.Serialization; 21 | 22 | namespace DataExtractor.Framework.GameMath 23 | { 24 | /// 25 | /// Represents an axis aligned box in 3D space. 26 | /// 27 | /// 28 | /// An axis-aligned box is a box whose faces coincide with the standard basis axes. 29 | /// 30 | [Serializable] 31 | public struct AxisAlignedBox : ISerializable, ICloneable 32 | { 33 | #region Private Fields 34 | private Vector3 _lo; 35 | private Vector3 _hi; 36 | #endregion 37 | 38 | #region Constructors 39 | /// 40 | /// Initializes a new instance of the class using given minimum and maximum points. 41 | /// 42 | /// A instance representing the minimum point. 43 | /// A instance representing the maximum point. 44 | public AxisAlignedBox(Vector3 min, Vector3 max) 45 | { 46 | _lo = min; 47 | _hi = max; 48 | } 49 | /// 50 | /// Initializes a new instance of the class using given values from another box instance. 51 | /// 52 | /// A instance to take values from. 53 | public AxisAlignedBox(AxisAlignedBox box) 54 | { 55 | _lo = box.Lo; 56 | _hi = box.Hi; 57 | } 58 | /// 59 | /// Initializes a new instance of the class with serialized data. 60 | /// 61 | /// The object that holds the serialized object data. 62 | /// The contextual information about the source or destination. 63 | private AxisAlignedBox(SerializationInfo info, StreamingContext context) 64 | { 65 | _lo = (Vector3)info.GetValue("Min", typeof(Vector3)); 66 | _hi = (Vector3)info.GetValue("Max", typeof(Vector3)); 67 | } 68 | #endregion 69 | 70 | #region Public Properties 71 | /// 72 | /// Gets or sets the minimum point which is the box's minimum X and Y coordinates. 73 | /// 74 | public Vector3 Lo 75 | { 76 | get { return _lo; } 77 | set { _lo = value; } 78 | } 79 | /// 80 | /// Gets or sets the maximum point which is the box's maximum X and Y coordinates. 81 | /// 82 | public Vector3 Hi 83 | { 84 | get { return _hi; } 85 | set { _hi = value; } 86 | } 87 | #endregion 88 | 89 | #region ISerializable Members 90 | /// 91 | /// Populates a with the data needed to serialize the target object. 92 | /// 93 | /// The to populate with data. 94 | /// The destination (see ) for this serialization. 95 | public void GetObjectData(SerializationInfo info, StreamingContext context) 96 | { 97 | info.AddValue("Max", _hi, typeof(Vector3)); 98 | info.AddValue("Min", _lo, typeof(Vector3)); 99 | } 100 | #endregion 101 | 102 | #region ICloneable Members 103 | /// 104 | /// Creates an exact copy of this object. 105 | /// 106 | /// The object this method creates, cast as an object. 107 | object ICloneable.Clone() 108 | { 109 | return new AxisAlignedBox(this); 110 | } 111 | /// 112 | /// Creates an exact copy of this object. 113 | /// 114 | /// The object this method creates. 115 | public AxisAlignedBox Clone() 116 | { 117 | return new AxisAlignedBox(this); 118 | } 119 | #endregion 120 | 121 | #region Public Methods 122 | /// 123 | /// Computes the box vertices. 124 | /// 125 | /// An array of containing the box vertices. 126 | public Vector3[] ComputeVertices() 127 | { 128 | Vector3[] vertices = new Vector3[8]; 129 | 130 | vertices[0] = _lo; 131 | vertices[1] = new Vector3(_hi.X, _lo.Y, _lo.Z); 132 | vertices[2] = new Vector3(_hi.X, _hi.Y, _lo.Z); 133 | vertices[3] = new Vector3(_lo.X, _hi.Y, _lo.Z); 134 | 135 | vertices[4] = new Vector3(_lo.X, _lo.Y, _hi.Z); 136 | vertices[5] = new Vector3(_hi.X, _lo.Y, _hi.Z); 137 | vertices[6] = _hi; 138 | vertices[7] = new Vector3(_lo.X, _hi.Y, _hi.Z); 139 | 140 | return vertices; 141 | } 142 | 143 | #endregion 144 | 145 | #region Overrides 146 | /// 147 | /// Returns the hashcode for this instance. 148 | /// 149 | /// A 32-bit signed integer hash code. 150 | public override int GetHashCode() 151 | { 152 | return _lo.GetHashCode() ^ _hi.GetHashCode(); 153 | } 154 | /// 155 | /// Returns a value indicating whether this instance is equal to 156 | /// the specified object. 157 | /// 158 | /// An object to compare to this instance. 159 | /// True if is a and has the same values as this instance; otherwise, False. 160 | public override bool Equals(object obj) 161 | { 162 | if (obj is AxisAlignedBox) 163 | { 164 | AxisAlignedBox box = (AxisAlignedBox)obj; 165 | return (_lo == box.Lo) && (_hi == box.Hi); 166 | } 167 | return false; 168 | } 169 | 170 | /// 171 | /// Returns a string representation of this object. 172 | /// 173 | /// A string representation of this object. 174 | public override string ToString() 175 | { 176 | return string.Format("AxisAlignedBox(Min={0}, Max={1})", _lo, _hi); 177 | } 178 | #endregion 179 | 180 | #region Comparison Operators 181 | /// 182 | /// Checks if the two given boxes are equal. 183 | /// 184 | /// The first of two boxes to compare. 185 | /// The second of two boxes to compare. 186 | /// true if the boxes are equal; otherwise, false. 187 | public static bool operator ==(AxisAlignedBox a, AxisAlignedBox b) 188 | { 189 | if (Equals(a, null)) 190 | { 191 | return Equals(b, null); 192 | } 193 | 194 | if (Equals(b, null)) 195 | { 196 | return Equals(a, null); 197 | } 198 | 199 | return (a.Lo == b.Lo) && (a.Hi == b.Hi); 200 | } 201 | 202 | /// 203 | /// Checks if the two given boxes are not equal. 204 | /// 205 | /// The first of two boxes to compare. 206 | /// The second of two boxes to compare. 207 | /// true if the vectors are not equal; otherwise, false. 208 | public static bool operator !=(AxisAlignedBox a, AxisAlignedBox b) 209 | { 210 | if (Object.Equals(a, null) == true) 211 | { 212 | return !Object.Equals(b, null); 213 | } 214 | else if (Object.Equals(b, null) == true) 215 | { 216 | return !Object.Equals(a, null); 217 | } 218 | return !((a.Lo == b.Lo) && (a.Hi == b.Hi)); 219 | } 220 | #endregion 221 | 222 | public bool contains(Vector3 point) 223 | { 224 | return 225 | (point.X >= _lo.X) && 226 | (point.Y >= _lo.Y) && 227 | (point.Z >= _lo.Z) && 228 | (point.X <= _hi.X) && 229 | (point.Y <= _hi.Y) && 230 | (point.Z <= _hi.Z); 231 | } 232 | 233 | public void merge(AxisAlignedBox a) 234 | { 235 | if (isEmpty()) 236 | { 237 | _lo = a.Lo; 238 | _hi = a.Hi; 239 | } 240 | else if (!a.isEmpty()) 241 | { 242 | _lo = Vector3.Min(Lo, a.Lo); 243 | _hi = Vector3.Max(Hi, a.Hi); 244 | } 245 | } 246 | 247 | public void merge(Vector3 a) 248 | { 249 | if (isEmpty()) 250 | { 251 | _lo = _hi = a; 252 | } 253 | else 254 | { 255 | _lo = Vector3.Min(_lo, a); 256 | _hi = Vector3.Max(_hi, a); 257 | } 258 | } 259 | 260 | public static readonly AxisAlignedBox Zero = new(Vector3.Zero, Vector3.Zero); 261 | 262 | public static readonly AxisAlignedBox NaN = new(new Vector3(float.NaN, float.NaN, float.NaN), new Vector3(float.NaN, float.NaN, float.NaN)); 263 | 264 | public Vector3 corner(int index) 265 | { 266 | // default constructor inits all components to 0 267 | Vector3 v = new(); 268 | 269 | switch (index) 270 | { 271 | case 0: 272 | v.X = _lo.X; 273 | v.Y = _lo.Y; 274 | v.Z = _hi.Z; 275 | break; 276 | 277 | case 1: 278 | v.X = _hi.X; 279 | v.Y = _lo.Y; 280 | v.Z = _hi.Z; 281 | break; 282 | 283 | case 2: 284 | v.X = _hi.X; 285 | v.Y = _hi.Y; 286 | v.Z = _hi.Z; 287 | break; 288 | 289 | case 3: 290 | v.X = _lo.X; 291 | v.Y = _hi.Y; 292 | v.Z = _hi.Z; 293 | break; 294 | 295 | case 4: 296 | v.X = _lo.X; 297 | v.Y = _lo.Y; 298 | v.Z = _lo.Z; 299 | break; 300 | 301 | case 5: 302 | v.X = _hi.X; 303 | v.Y = _lo.Y; 304 | v.Z = _lo.Z; 305 | break; 306 | 307 | case 6: 308 | v.X = _hi.X; 309 | v.Y = _hi.Y; 310 | v.Z = _lo.Z; 311 | break; 312 | 313 | case 7: 314 | v.X = _lo.X; 315 | v.Y = _hi.Y; 316 | v.Z = _lo.Z; 317 | break; 318 | 319 | default: 320 | break; 321 | } 322 | 323 | return v; 324 | } 325 | 326 | public static AxisAlignedBox operator +(AxisAlignedBox box, Vector3 v) 327 | { 328 | AxisAlignedBox outt = new(); 329 | outt.Lo = box.Lo + v; 330 | outt.Hi = box.Hi + v; 331 | return outt; 332 | } 333 | 334 | public bool isFinite() 335 | { 336 | var b = isEmpty() || (Lo.isNaN() && Hi.isFinite()); 337 | return b; 338 | } 339 | 340 | public bool isEmpty() 341 | { 342 | return Lo.isNaN(); 343 | } 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/IO/FileWriter.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System.IO; 19 | 20 | public class FileWriter 21 | { 22 | public static void WriteFile(Stream data, string path, FileMode fileMode = FileMode.Create) 23 | { 24 | using MemoryStream ms = new(); 25 | using var fs = new FileStream(path, fileMode, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, true); 26 | data.CopyTo(ms); 27 | fs.Write(ms.ToArray(), 0, (int)data.Length); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/MathFunctions.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System; 19 | 20 | public static class MathFunctions 21 | { 22 | public const float E = 2.71828f; 23 | public const float Log10E = 0.434294f; 24 | public const float Log2E = 1.4427f; 25 | public const float PI = 3.14159f; 26 | public const float PiOver2 = 1.5708f; 27 | public const float PiOver4 = 0.785398f; 28 | public const float TwoPi = 6.28319f; 29 | public const float Epsilon = 4.76837158203125E-7f; 30 | 31 | public static float wrap(float t, float lo, float hi) 32 | { 33 | if ((t >= lo) && (t < hi)) 34 | { 35 | return t; 36 | } 37 | 38 | float interval = hi - lo; 39 | return (float)(t - interval * Math.Floor((t - lo) / interval)); 40 | } 41 | 42 | public static void Swap(ref T lhs, ref T rhs) 43 | { 44 | T temp = lhs; 45 | lhs = rhs; 46 | rhs = temp; 47 | } 48 | 49 | #region Clamp 50 | /// 51 | /// Clamp a to if it is withon the range. 52 | /// 53 | /// The value to clamp. 54 | /// The clamped value. 55 | /// The tolerance value. 56 | /// 57 | /// Returns the clamped value. 58 | /// result = (tolerance > Abs(value-calmpedValue)) ? calmpedValue : value; 59 | /// 60 | public static float Clamp(float value, float calmpedValue, float tolerance) 61 | { 62 | return (tolerance > Math.Abs(value - calmpedValue)) ? calmpedValue : value; 63 | } 64 | /// 65 | /// Clamp a to using the default tolerance value. 66 | /// 67 | /// The value to clamp. 68 | /// The clamped value. 69 | /// 70 | /// Returns the clamped value. 71 | /// result = (EpsilonF > Abs(value-calmpedValue)) ? calmpedValue : value; 72 | /// 73 | /// is used for tolerance. 74 | public static float Clamp(float value, float calmpedValue) 75 | { 76 | return (Epsilon > Math.Abs(value - calmpedValue)) ? calmpedValue : value; 77 | } 78 | #endregion 79 | 80 | static double eps(float a, float b) 81 | { 82 | float aa = Math.Abs(a) + 1.0f; 83 | if (aa == float.PositiveInfinity) 84 | return 0.00001f; 85 | else 86 | return 0.00001f * aa; 87 | } 88 | 89 | public static float lerp(float a, float b, float f) 90 | { 91 | return a + (b - a) * f; 92 | } 93 | 94 | public static float DegToRad(float degrees) 95 | { 96 | return degrees * (2.0f * PI / 360.0f); 97 | } 98 | 99 | public static float toRadians(float deg) 100 | { 101 | return deg * PI / 180.0f; 102 | } 103 | 104 | public static float toDegrees(float rad) 105 | { 106 | return rad * 180.0f / PI; 107 | } 108 | 109 | #region Fuzzy 110 | public static bool fuzzyEq(float a, float b) 111 | { 112 | return (a == b) || (Math.Abs(a - b) <= eps(a, b)); 113 | } 114 | public static bool fuzzyGt(float a, float b) 115 | { 116 | return a > b + eps(a, b); 117 | } 118 | public static bool fuzzyLt(float a, float b) 119 | { 120 | return a < b - eps(a, b); 121 | } 122 | public static bool fuzzyNe(float a, float b) 123 | { 124 | return !fuzzyEq(a, b); 125 | } 126 | public static bool fuzzyLe(float a, float b) 127 | { 128 | return a < b + eps(a, b); 129 | } 130 | public static bool fuzzyGe(float a, float b) 131 | { 132 | return a > b - eps(a, b); 133 | } 134 | #endregion 135 | 136 | public static int ApplyPct(ref int Base, float pct) 137 | { 138 | return Base = CalculatePct(Base, pct); 139 | } 140 | public static uint ApplyPct(ref uint Base, float pct) 141 | { 142 | return Base = CalculatePct(Base, pct); 143 | } 144 | public static float ApplyPct(ref float Base, float pct) 145 | { 146 | return Base = CalculatePct(Base, pct); 147 | } 148 | 149 | public static long AddPct(ref long value, float pct) 150 | { 151 | return value += (long)CalculatePct(value, pct); 152 | } 153 | public static int AddPct(ref int value, float pct) 154 | { 155 | return value += CalculatePct(value, pct); 156 | } 157 | public static uint AddPct(ref uint value, float pct) 158 | { 159 | return value += CalculatePct(value, pct); 160 | } 161 | public static float AddPct(ref float value, float pct) 162 | { 163 | return value += CalculatePct(value, pct); 164 | } 165 | 166 | public static int CalculatePct(int value, float pct) 167 | { 168 | return (int)(value * Convert.ToSingle(pct) / 100.0f); 169 | } 170 | public static uint CalculatePct(uint value, float pct) 171 | { 172 | return (uint)(value * Convert.ToSingle(pct) / 100.0f); 173 | } 174 | public static float CalculatePct(float value, float pct) 175 | { 176 | return value * pct / 100.0f; 177 | } 178 | public static ulong CalculatePct(ulong value, float pct) 179 | { 180 | return (ulong)(value * pct / 100.0f); 181 | } 182 | 183 | public static int RoundToInterval(ref int num, dynamic floor, dynamic ceil) 184 | { 185 | return num = (int)Math.Min(Math.Max(num, floor), ceil); 186 | } 187 | public static uint RoundToInterval(ref uint num, dynamic floor, dynamic ceil) 188 | { 189 | return num = Math.Min(Math.Max(num, floor), ceil); 190 | } 191 | public static float RoundToInterval(ref float num, dynamic floor, dynamic ceil) 192 | { 193 | return num = Math.Min(Math.Max(num, floor), ceil); 194 | } 195 | 196 | public static void ApplyPercentModFloatVar(ref float value, float val, bool apply) 197 | { 198 | if (val == -100.0f) // prevent set var to zero 199 | val = -99.99f; 200 | value *= (apply ? (100.0f + val) / 100.0f : 100.0f / (100.0f + val)); 201 | } 202 | 203 | public static ulong MakePair64(uint l, uint h) 204 | { 205 | return (ulong)l | ((ulong)h << 32); 206 | } 207 | public static uint Pair64_HiPart(ulong x) 208 | { 209 | return (uint)((x >> 32) & 0x00000000FFFFFFFF); 210 | } 211 | public static uint Pair64_LoPart(ulong x) 212 | { 213 | return (uint)(x & 0x00000000FFFFFFFF); 214 | } 215 | public static ushort Pair32_HiPart(uint x) 216 | { 217 | return (ushort)((x >> 16) & 0x0000FFFF); 218 | } 219 | public static ushort Pair32_LoPart(uint x) 220 | { 221 | return (ushort)(x & 0x0000FFFF); 222 | } 223 | public static uint MakePair32(uint l, uint h) 224 | { 225 | return (ushort)l | (h << 16); 226 | } 227 | public static ushort MakePair16(uint l, uint h) 228 | { 229 | return (ushort)((byte)l | (ushort)h << 8); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/MultiMap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System.Linq; 19 | 20 | namespace System.Collections.Generic 21 | { 22 | public sealed class MultiMap : IMultiMap, IDictionary 23 | { 24 | public MultiMap() { } 25 | 26 | public MultiMap(IEnumerable> initialData) 27 | { 28 | foreach (var item in initialData) 29 | { 30 | Add(item); 31 | } 32 | } 33 | 34 | public void Add(TKey key, TValue value) 35 | { 36 | if (!_interalStorage.ContainsKey(key)) 37 | _interalStorage.Add(key, new List()); 38 | 39 | _interalStorage[key].Add(value); 40 | } 41 | 42 | public void AddRange(TKey key, IEnumerable valueList) 43 | { 44 | if (!_interalStorage.ContainsKey(key)) 45 | { 46 | _interalStorage.Add(key, new List()); 47 | } 48 | foreach (TValue value in valueList) 49 | { 50 | _interalStorage[key].Add(value); 51 | } 52 | } 53 | 54 | public void Add(KeyValuePair item) 55 | { 56 | if (!_interalStorage.ContainsKey(item.Key)) 57 | { 58 | _interalStorage.Add(item.Key, new List()); 59 | } 60 | _interalStorage[item.Key].Add(item.Value); 61 | } 62 | 63 | public bool Remove(TKey key) 64 | { 65 | return _interalStorage.Remove(key); 66 | } 67 | 68 | public bool Remove(KeyValuePair item) 69 | { 70 | if (!ContainsKey(item.Key)) 71 | return false; 72 | 73 | bool val = _interalStorage[item.Key].Remove(item.Value); 74 | 75 | if (!val) 76 | return false; 77 | 78 | if (_interalStorage[item.Key].Empty()) 79 | _interalStorage.Remove(item.Key); 80 | 81 | return true; 82 | } 83 | 84 | public bool Remove(TKey key, TValue value) 85 | { 86 | if (!ContainsKey(key)) 87 | return false; 88 | 89 | bool val = _interalStorage[key].Remove(value); 90 | 91 | if (!val) 92 | return false; 93 | 94 | if (_interalStorage[key].Empty()) 95 | _interalStorage.Remove(key); 96 | 97 | return true; 98 | } 99 | 100 | public bool ContainsKey(TKey key) 101 | { 102 | return _interalStorage.ContainsKey(key); 103 | } 104 | 105 | public bool Contains(KeyValuePair item) 106 | { 107 | if (_interalStorage.TryGetValue(item.Key, out List valueList)) 108 | return valueList.Contains(item.Value); 109 | return false; 110 | } 111 | 112 | public bool Contains(TKey key, TValue item) 113 | { 114 | if (!_interalStorage.ContainsKey(key)) return false; 115 | return _interalStorage[key].Contains(item); 116 | } 117 | 118 | public List LookupByKey(TKey key) 119 | { 120 | if (_interalStorage.ContainsKey(key)) 121 | return _interalStorage[key].ToList(); 122 | 123 | return new List(); 124 | } 125 | 126 | public List LookupByKey(object key) 127 | { 128 | TKey newkey = (TKey)Convert.ChangeType(key, typeof(TKey)); 129 | if (_interalStorage.ContainsKey(newkey)) 130 | return _interalStorage[newkey].ToList(); 131 | 132 | return new List(); 133 | } 134 | 135 | bool IDictionary.TryGetValue(TKey key, out TValue value) 136 | { 137 | if (!_interalStorage.ContainsKey(key)) 138 | { 139 | value = default; 140 | return false; 141 | } 142 | value = _interalStorage[key].Last(); 143 | return true; 144 | } 145 | 146 | TValue IDictionary.this[TKey key] 147 | { 148 | get 149 | { 150 | return _interalStorage[key].LastOrDefault(); 151 | } 152 | set 153 | { 154 | Add(key, value); 155 | } 156 | } 157 | 158 | public List this[TKey key] 159 | { 160 | get 161 | { 162 | if (!_interalStorage.ContainsKey(key)) 163 | return new List(); 164 | return _interalStorage[key]; 165 | } 166 | set 167 | { 168 | if (!_interalStorage.ContainsKey(key)) 169 | _interalStorage.Add(key, value); 170 | else 171 | _interalStorage[key] = value; 172 | } 173 | } 174 | 175 | public ICollection Keys 176 | { 177 | get { return _interalStorage.Keys; } 178 | } 179 | 180 | public ICollection Values 181 | { 182 | get 183 | { 184 | List retVal = new(); 185 | foreach (var item in _interalStorage) 186 | { 187 | retVal.AddRange(item.Value); 188 | } 189 | return retVal; 190 | } 191 | } 192 | 193 | public List> KeyValueList 194 | { 195 | get 196 | { 197 | List> retVal = new(); 198 | foreach (var pair in _interalStorage) 199 | { 200 | foreach (var value in pair.Value) 201 | retVal.Add(new KeyValuePair(pair.Key, value)); 202 | } 203 | return retVal; 204 | } 205 | } 206 | 207 | public void Clear() 208 | { 209 | _interalStorage.Clear(); 210 | } 211 | 212 | public void CopyTo(KeyValuePair[] array, int arrayIndex) 213 | { 214 | if (array == null) 215 | throw new ArgumentNullException("array"); 216 | 217 | if (arrayIndex < 0) 218 | throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "argument 'arrayIndex' cannot be negative"); 219 | 220 | if (arrayIndex >= array.Length || Count > array.Length - arrayIndex) 221 | array = new KeyValuePair[Count]; 222 | 223 | int index = arrayIndex; 224 | foreach (KeyValuePair pair in this) 225 | array[index++] = new KeyValuePair(pair.Key, pair.Value); 226 | 227 | } 228 | 229 | public int Count 230 | { 231 | get 232 | { 233 | int count = 0; 234 | foreach (var item in _interalStorage) 235 | { 236 | count += item.Value.Count; 237 | } 238 | return count; 239 | } 240 | } 241 | 242 | int ICollection>.Count 243 | { 244 | get { return _interalStorage.Count; } 245 | } 246 | 247 | bool ICollection>.IsReadOnly 248 | { 249 | get { return false; } 250 | } 251 | 252 | public IEnumerator> GetEnumerator() 253 | { 254 | return new MultiMapEnumerator(this); 255 | } 256 | 257 | IEnumerator IEnumerable.GetEnumerator() 258 | { 259 | return new MultiMapEnumerator(this); 260 | } 261 | 262 | private Dictionary> _interalStorage = new(); 263 | } 264 | 265 | public class MultiMapEnumerator : IEnumerator> 266 | { 267 | MultiMap _map; 268 | IEnumerator _keyEnumerator; 269 | IEnumerator _valueEnumerator; 270 | 271 | public MultiMapEnumerator(MultiMap map) 272 | { 273 | _map = map; 274 | Reset(); 275 | } 276 | 277 | object IEnumerator.Current 278 | { 279 | get 280 | { 281 | return Current; 282 | } 283 | } 284 | 285 | public KeyValuePair Current 286 | { 287 | get 288 | { 289 | return new KeyValuePair(_keyEnumerator.Current, _valueEnumerator.Current); 290 | } 291 | } 292 | 293 | public void Dispose() 294 | { 295 | _keyEnumerator = null; 296 | _valueEnumerator = null; 297 | _map = null; 298 | } 299 | 300 | public bool MoveNext() 301 | { 302 | if (!_valueEnumerator.MoveNext()) 303 | { 304 | if (!_keyEnumerator.MoveNext()) 305 | return false; 306 | _valueEnumerator = _map[_keyEnumerator.Current].GetEnumerator(); 307 | _valueEnumerator.MoveNext(); 308 | return true; 309 | } 310 | return true; 311 | } 312 | 313 | public void Reset() 314 | { 315 | _keyEnumerator = _map.Keys.GetEnumerator(); 316 | _valueEnumerator = new List().GetEnumerator(); 317 | } 318 | } 319 | 320 | public interface IMultiMap 321 | { 322 | void AddRange(TKey key, IEnumerable valueList); 323 | List this[TKey key] { get; set; } 324 | bool Remove(TKey key, TValue value); 325 | void Add(TKey key, TValue value); 326 | bool ContainsKey(TKey key); 327 | 328 | ICollection Keys { get; } 329 | bool Remove(TKey key); 330 | ICollection Values { get; } 331 | 332 | void Add(KeyValuePair item); 333 | void Clear(); 334 | bool Contains(TKey key, TValue item); 335 | void CopyTo(KeyValuePair[] array, int arrayIndex); 336 | int Count { get; } 337 | bool Remove(KeyValuePair item); 338 | 339 | List LookupByKey(TKey key); 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Recast/RecastFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | public static partial class Recast{ 5 | /// @par 6 | /// 7 | /// Allows the formation of walkable regions that will flow over low lying 8 | /// objects such as curbs, and up structures such as stairways. 9 | /// 10 | /// Two neighboring spans are walkable if: rcAbs(currentSpan.smax - neighborSpan.smax) < waklableClimb 11 | /// 12 | /// @warning Will override the effect of #rcFilterLedgeSpans. So if both filters are used, call 13 | /// #rcFilterLedgeSpans after calling this filter. 14 | /// 15 | /// @see rcHeightfield, rcConfig 16 | public static void rcFilterLowHangingWalkableObstacles(rcContext ctx, int walkableClimb, rcHeightfield solid) 17 | { 18 | Debug.Assert(ctx != null, "rcContext is null"); 19 | 20 | ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES); 21 | 22 | int w = solid.width; 23 | int h = solid.height; 24 | 25 | for (int y = 0; y < h; ++y) 26 | { 27 | for (int x = 0; x < w; ++x) 28 | { 29 | rcSpan ps = null; 30 | bool previousWalkable = false; 31 | byte previousArea = RC_NULL_AREA; 32 | 33 | for (rcSpan s = solid.spans[x + y*w]; s != null; ps = s, s = s.next) 34 | { 35 | bool walkable = s.area != RC_NULL_AREA; 36 | // If current span is not walkable, but there is walkable 37 | // span just below it, mark the span above it walkable too. 38 | if (!walkable && previousWalkable) 39 | { 40 | if (Math.Abs((int)s.smax - (int)ps.smax) <= walkableClimb){ 41 | s.area = previousArea; 42 | } 43 | } 44 | // Copy walkable flag so that it cannot propagate 45 | // past multiple non-walkable objects. 46 | previousWalkable = walkable; 47 | previousArea = s.area; 48 | } 49 | } 50 | } 51 | 52 | ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_LOW_OBSTACLES); 53 | } 54 | 55 | /// @par 56 | /// 57 | /// A ledge is a span with one or more neighbors whose maximum is further away than @p walkableClimb 58 | /// from the current span's maximum. 59 | /// This method removes the impact of the overestimation of conservative voxelization 60 | /// so the resulting mesh will not have regions hanging in the air over ledges. 61 | /// 62 | /// A span is a ledge if: rcAbs(currentSpan.smax - neighborSpan.smax) > walkableClimb 63 | /// 64 | /// @see rcHeightfield, rcConfig 65 | public static void rcFilterLedgeSpans(rcContext ctx, int walkableHeight, int walkableClimb, 66 | rcHeightfield solid) 67 | { 68 | Debug.Assert(ctx != null, "rcContext is null"); 69 | 70 | ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_BORDER); 71 | 72 | int w = solid.width; 73 | int h = solid.height; 74 | int MAX_HEIGHT = 0xffff; 75 | 76 | // Mark border spans. 77 | for (int y = 0; y < h; ++y) 78 | { 79 | for (int x = 0; x < w; ++x) 80 | { 81 | for (rcSpan s = solid.spans[x + y*w]; s != null; s = s.next) 82 | { 83 | // Skip non walkable spans. 84 | if (s.area == RC_NULL_AREA){ 85 | continue; 86 | } 87 | 88 | int bot = (int)(s.smax); 89 | int top = s.next != null ? (int)(s.next.smin) : MAX_HEIGHT; 90 | 91 | // Find neighbours minimum height. 92 | int minh = MAX_HEIGHT; 93 | 94 | // Min and max height of accessible neighbours. 95 | int asmin = s.smax; 96 | int asmax = s.smax; 97 | 98 | for (int dir = 0; dir < 4; ++dir) 99 | { 100 | int dx = x + rcGetDirOffsetX(dir); 101 | int dy = y + rcGetDirOffsetY(dir); 102 | // Skip neighbours which are out of bounds. 103 | if (dx < 0 || dy < 0 || dx >= w || dy >= h) 104 | { 105 | minh = Math.Min(minh, -walkableClimb - bot); 106 | continue; 107 | } 108 | 109 | // From minus infinity to the first span. 110 | rcSpan ns = solid.spans[dx + dy*w]; 111 | int nbot = -walkableClimb; 112 | int ntop = ns != null ? (int)ns.smin : MAX_HEIGHT; 113 | // Skip neightbour if the gap between the spans is too small. 114 | if (Math.Min(top,ntop) - Math.Max(bot,nbot) > walkableHeight) 115 | minh = Math.Min(minh, nbot - bot); 116 | 117 | // Rest of the spans. 118 | for (ns = solid.spans[dx + dy*w]; ns != null; ns = ns.next) 119 | { 120 | nbot = (int)ns.smax; 121 | ntop = ns.next != null ? (int)ns.next.smin : MAX_HEIGHT; 122 | // Skip neightbour if the gap between the spans is too small. 123 | if (Math.Min(top,ntop) - Math.Max(bot,nbot) > walkableHeight) 124 | { 125 | minh = Math.Min(minh, nbot - bot); 126 | 127 | // Find min/max accessible neighbour height. 128 | if (Math.Abs(nbot - bot) <= walkableClimb) 129 | { 130 | if (nbot < asmin) asmin = nbot; 131 | if (nbot > asmax) asmax = nbot; 132 | } 133 | 134 | } 135 | } 136 | } 137 | 138 | // The current span is close to a ledge if the drop to any 139 | // neighbour span is less than the walkableClimb. 140 | if (minh < -walkableClimb){ 141 | s.area = RC_NULL_AREA; 142 | } 143 | 144 | // If the difference between all neighbours is too large, 145 | // we are at steep slope, mark the span as ledge. 146 | if ((asmax - asmin) > walkableClimb) 147 | { 148 | s.area = RC_NULL_AREA; 149 | } 150 | } 151 | } 152 | } 153 | 154 | ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_BORDER); 155 | } 156 | 157 | /// @par 158 | /// 159 | /// For this filter, the clearance above the span is the distance from the span's 160 | /// maximum to the next higher span's minimum. (Same grid column.) 161 | /// 162 | /// @see rcHeightfield, rcConfig 163 | public static void rcFilterWalkableLowHeightSpans(rcContext ctx, int walkableHeight, rcHeightfield solid) 164 | { 165 | Debug.Assert(ctx != null, "rcContext is null"); 166 | 167 | ctx.startTimer(rcTimerLabel.RC_TIMER_FILTER_WALKABLE); 168 | 169 | int w = solid.width; 170 | int h = solid.height; 171 | int MAX_HEIGHT = 0xffff; 172 | 173 | // Remove walkable flag from spans which do not have enough 174 | // space above them for the agent to stand there. 175 | for (int y = 0; y < h; ++y) 176 | { 177 | for (int x = 0; x < w; ++x) 178 | { 179 | for (rcSpan s = solid.spans[x + y*w]; s != null; s = s.next) 180 | { 181 | int bot = (int)(s.smax); 182 | int top = s.next != null ? (int)(s.next.smin) : MAX_HEIGHT; 183 | if ((top - bot) <= walkableHeight) { 184 | s.area = RC_NULL_AREA; 185 | } 186 | } 187 | } 188 | } 189 | 190 | ctx.stopTimer(rcTimerLabel.RC_TIMER_FILTER_WALKABLE); 191 | } 192 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Framework/Threading/ProducerConsumerQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading; 3 | 4 | namespace DataExtractor 5 | { 6 | public class ProducerConsumerQueue 7 | { 8 | object _queueLock = new(); 9 | Queue _queue = new(); 10 | volatile bool _shutdown; 11 | 12 | public ProducerConsumerQueue() 13 | { 14 | _shutdown = false; 15 | } 16 | 17 | public void Push(T value) 18 | { 19 | lock (_queueLock) 20 | { 21 | _queue.Enqueue(value); 22 | Monitor.PulseAll(_queueLock); 23 | } 24 | } 25 | 26 | public bool Empty() 27 | { 28 | lock (_queueLock) 29 | return _queue.Count == 0; 30 | } 31 | 32 | public bool Pop(out T value) 33 | { 34 | value = default(T); 35 | lock (_queueLock) 36 | { 37 | if (_queue.Count == 0 || _shutdown) 38 | return false; 39 | 40 | value = _queue.Dequeue(); 41 | return true; 42 | } 43 | } 44 | 45 | public void WaitAndPop(out T value) 46 | { 47 | value = default(T); 48 | lock (_queueLock) 49 | { 50 | while (_queue.Count == 0 && !_shutdown) 51 | Monitor.Wait(_queueLock); 52 | 53 | if (_queue.Count == 0 || _shutdown) 54 | return; 55 | 56 | value = _queue.Dequeue(); 57 | } 58 | } 59 | 60 | public void Cancel() 61 | { 62 | lock (_queueLock) 63 | { 64 | while (_queue.Count != 0) 65 | { 66 | _queue.Dequeue(); 67 | } 68 | 69 | _shutdown = true; 70 | Monitor.PulseAll(_queueLock); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Source/DataExtractor/Map/ChunkedFile.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | //using DataExtractor.Framework.CASC.Handlers; 19 | using DataExtractor.CASCLib; 20 | using DataExtractor.Map; 21 | using System; 22 | using System.Collections.Generic; 23 | using System.IO; 24 | using System.Text; 25 | 26 | namespace DataExtractor 27 | { 28 | class ChunkedFile 29 | { 30 | public bool LoadFile(CASCHandler cascHandler, string fileName) 31 | { 32 | var file = cascHandler.OpenFile(fileName); 33 | if (file == null) 34 | return false; 35 | 36 | var fileSize = file.Length; 37 | if (fileSize == 0xFFFFFFFF) 38 | return false; 39 | 40 | dataSize = (uint)fileSize; 41 | data = new BinaryReader(file).ReadBytes((int)dataSize); 42 | 43 | ParseChunks(); 44 | if (PrepareLoadedData()) 45 | return true; 46 | 47 | Console.WriteLine($"Error loading {fileName}\n"); 48 | 49 | return false; 50 | } 51 | 52 | public bool LoadFile(CASCHandler cascHandler, uint fileDataId, string description) 53 | { 54 | var file = cascHandler.OpenFile((int)fileDataId); 55 | if (file == null) 56 | return false; 57 | 58 | var fileSize = file.Length; 59 | if (fileSize == 0xFFFFFFFF) 60 | return false; 61 | 62 | dataSize = (uint)fileSize; 63 | data = new BinaryReader(file).ReadBytes((int)dataSize ); 64 | 65 | ParseChunks(); 66 | if (PrepareLoadedData()) 67 | return true; 68 | 69 | Console.WriteLine($"Error loading {description}\n"); 70 | 71 | return false; 72 | } 73 | 74 | bool PrepareLoadedData() 75 | { 76 | FileChunk chunk = GetChunk("MVER"); 77 | if (chunk == null) 78 | return false; 79 | 80 | // Check version 81 | MVER version = chunk.As(); 82 | if (version.Version != 18) 83 | return false; 84 | 85 | return true; 86 | } 87 | 88 | public static Dictionary InterestingChunks = new() 89 | { 90 | {"REVM", "MVER"}, 91 | {"NIAM", "MAIN"}, 92 | {"O2HM", "MH2O"}, 93 | {"KNCM", "MCNK"}, 94 | {"TVCM", "MCVT"}, 95 | {"QLCM", "MCLQ"}, 96 | {"OBFM", "MFBO"}, 97 | {"DHPM", "MPHD"}, 98 | {"DIAM", "MAID"}, 99 | {"OMWM", "MWMO"}, 100 | {"XDMM", "MMDX"}, 101 | {"FDDM", "MDDF"}, 102 | {"FDOM", "MODF"}, 103 | }; 104 | 105 | public static bool IsInterestingChunk(string fcc) => InterestingChunks.ContainsKey(fcc); 106 | 107 | void ParseChunks() 108 | { 109 | using (BinaryReader reader = new(new MemoryStream(data))) 110 | { 111 | while (reader.BaseStream.Position < reader.BaseStream.Length) 112 | { 113 | string header = new(reader.ReadChars(4)); 114 | int size = reader.ReadInt32(); 115 | 116 | if (!IsInterestingChunk(header)) 117 | reader.BaseStream.Position += size; 118 | else if (size <= dataSize) 119 | { 120 | header = InterestingChunks[header]; 121 | 122 | FileChunk chunk = new(reader.ReadBytes(size)); 123 | chunk.parseSubChunks(); 124 | chunks.Add(header, chunk); 125 | } 126 | } 127 | } 128 | } 129 | 130 | public FileChunk GetChunk(string name) 131 | { 132 | var range = chunks.LookupByKey(name); 133 | if (range != null && range.Count == 1) 134 | return range[0]; 135 | 136 | return null; 137 | } 138 | 139 | byte[] data; 140 | uint dataSize; 141 | 142 | public MultiMap chunks = new(); 143 | } 144 | 145 | class FileChunk 146 | { 147 | public FileChunk(byte[] data) 148 | { 149 | this.data = data; 150 | } 151 | 152 | public void parseSubChunks() 153 | { 154 | using (BinaryReader reader = new(new MemoryStream(data))) 155 | { 156 | while (reader.BaseStream.Position + 4 < reader.BaseStream.Length) 157 | { 158 | string header = reader.ReadStringFromChars(4); 159 | if (ChunkedFile.IsInterestingChunk(header)) 160 | { 161 | int subsize = reader.ReadInt32(); 162 | if (subsize < data.Length) 163 | { 164 | header = ChunkedFile.InterestingChunks[header]; 165 | FileChunk chunk = new FileChunk(reader.ReadBytes(subsize)); 166 | chunk.parseSubChunks(); 167 | if (!subchunks.ContainsKey(header)) 168 | subchunks[header] = new List(); 169 | 170 | subchunks[header].Add(chunk); 171 | } 172 | } 173 | } 174 | } 175 | } 176 | 177 | public FileChunk GetSubChunk(string name) 178 | { 179 | var range = subchunks.LookupByKey(name); 180 | if (range != null) 181 | return range[0]; 182 | 183 | return null; 184 | } 185 | 186 | public T As() where T : IMapStruct, new() 187 | { 188 | T obj = new(); 189 | obj.Read(data); 190 | return obj; 191 | } 192 | 193 | uint size; 194 | byte[] data; 195 | 196 | Dictionary> subchunks = new(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Source/DataExtractor/Map/WDTStructures.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using System; 19 | using System.IO; 20 | 21 | namespace DataExtractor.Map 22 | { 23 | public class MPHD : IMapStruct 24 | { 25 | public void Read(byte[] data) 26 | { 27 | using BinaryReader reader = new(new MemoryStream(data)); 28 | Flags = reader.ReadUInt32(); 29 | LgtFileDataID = reader.ReadUInt32(); 30 | OccFileDataID = reader.ReadUInt32(); 31 | FogsFileDataID = reader.ReadUInt32(); 32 | MpvFileDataID = reader.ReadUInt32(); 33 | TexFileDataID = reader.ReadUInt32(); 34 | WdlFileDataID = reader.ReadUInt32(); 35 | Pd4FileDataID = reader.ReadUInt32(); 36 | } 37 | 38 | public uint Flags { get; set; } 39 | public uint LgtFileDataID { get; set; } 40 | public uint OccFileDataID { get; set; } 41 | public uint FogsFileDataID { get; set; } 42 | public uint MpvFileDataID { get; set; } 43 | public uint TexFileDataID { get; set; } 44 | public uint WdlFileDataID { get; set; } 45 | public uint Pd4FileDataID { get; set; } 46 | } 47 | 48 | public class MAIN : IMapStruct 49 | { 50 | public void Read(byte[] data) 51 | { 52 | using BinaryReader reader = new(new MemoryStream(data)); 53 | for (var x = 0; x < 64; ++x) 54 | { 55 | MapAreaInfo[x] = new SMAearInfo[64]; 56 | 57 | for (var y = 0; y < 64; ++y) 58 | MapAreaInfo[x][y] = reader.Read(); 59 | } 60 | } 61 | 62 | public SMAearInfo[][] MapAreaInfo = new SMAearInfo[64][]; 63 | 64 | public struct SMAearInfo 65 | { 66 | public uint Flag; 67 | public uint AsyncID; 68 | } 69 | } 70 | 71 | public class MAID : IMapStruct 72 | { 73 | public void Read(byte[] data) 74 | { 75 | using BinaryReader reader = new(new MemoryStream(data)); 76 | for (var x = 0; x < 64; ++x) 77 | { 78 | MapFileDataIDs[x] = new MapFileData[64]; 79 | for (var y = 0; y < 64; ++y) 80 | MapFileDataIDs[x][y] = reader.Read(); 81 | } 82 | } 83 | 84 | public MapFileData[][] MapFileDataIDs = new MapFileData[64][]; 85 | 86 | public struct MapFileData 87 | { 88 | public uint RootADT; // FileDataID of mapname_xx_yy.adt 89 | public uint Obj0ADT; // FileDataID of mapname_xx_yy_obj0.adt 90 | public uint Obj1ADT; // FileDataID of mapname_xx_yy_obj1.adt 91 | public uint Tex0ADT; // FileDataID of mapname_xx_yy_tex0.adt 92 | public uint LodADT; // FileDataID of mapname_xx_yy_lod.adt 93 | public uint MapTexture; // FileDataID of mapname_xx_yy.blp 94 | public uint MapTextureN; // FileDataID of mapname_xx_yy_n.blp 95 | public uint MinimapTexture; // FileDataID of mapxx_yy.blp 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Source/DataExtractor/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DataExtractor": { 4 | "commandName": "Project", 5 | "workingDirectory": "D:\\wowLegion" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Vmap/ADTFile.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.Constants; 19 | using DataExtractor.Map; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.IO; 23 | 24 | namespace DataExtractor.Vmap 25 | { 26 | class ADTFile : ChunkedFile 27 | { 28 | public ADTFile(bool cache) 29 | { 30 | cacheable = cache; 31 | dirFileCache = null; 32 | } 33 | 34 | public bool Init(uint mapNum, uint originalMapId) 35 | { 36 | if (dirFileCache != null) 37 | return InitFromCache(mapNum, originalMapId); 38 | 39 | if (cacheable) 40 | dirFileCache = new List(); 41 | 42 | FilenameChunk mmdx = GetChunk("MMDX")?.As(); 43 | if (mmdx != null && mmdx.Filenames.Count > 0) 44 | { 45 | foreach (var filename in mmdx.Filenames) 46 | { 47 | modelInstanceNames.Add(filename); 48 | VmapFile.ExtractSingleModel(filename); 49 | } 50 | } 51 | 52 | FilenameChunk mwmo = GetChunk("MWMO")?.As(); 53 | if (mwmo != null && mwmo.Filenames.Count > 0) 54 | { 55 | foreach (var filename in mwmo.Filenames) 56 | { 57 | wmoInstanceNames.Add(filename); 58 | VmapFile.ExtractSingleWmo(filename); 59 | } 60 | } 61 | 62 | MDDF doodadChunk = GetChunk("MDDF")?.As(); 63 | if (doodadChunk != null && doodadChunk.DoodadDefs.Length > 0) 64 | { 65 | foreach (var doodad in doodadChunk.DoodadDefs) 66 | { 67 | if (doodad.Flags.HasAnyFlag(MDDFFlags.EntryIsFileID)) 68 | { 69 | string fileName = $"FILE{doodad.Id:X8}.xxx"; 70 | VmapFile.ExtractSingleModel(fileName); 71 | Model.Extract(doodad, fileName, mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 72 | } 73 | else 74 | Model.Extract(doodad, modelInstanceNames[(int)doodad.Id], mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 75 | } 76 | 77 | modelInstanceNames.Clear(); 78 | } 79 | 80 | MODF wmoChunk = GetChunk("MODF")?.As(); 81 | if (wmoChunk != null && wmoChunk.MapObjDefs.Length > 0) 82 | { 83 | foreach (var wmo in wmoChunk.MapObjDefs) 84 | { 85 | if (wmo.Flags.HasAnyFlag(MODFFlags.EntryIsFileID)) 86 | { 87 | string fileName = $"FILE{wmo.Id:X8}.xxx"; 88 | VmapFile.ExtractSingleWmo(wmo.Id); 89 | WMORoot.Extract(wmo, fileName, false, mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 90 | 91 | if (VmapFile.WmoDoodads.ContainsKey(fileName)) 92 | Model.ExtractSet(VmapFile.WmoDoodads[fileName], wmo, false, mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 93 | } 94 | else 95 | { 96 | WMORoot.Extract(wmo, wmoInstanceNames[(int)wmo.Id], false, mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 97 | if (VmapFile.WmoDoodads.ContainsKey(wmoInstanceNames[(int)wmo.Id])) 98 | Model.ExtractSet(VmapFile.WmoDoodads[wmoInstanceNames[(int)wmo.Id]], wmo, false, mapNum, originalMapId, Program.DirBinWriter, dirFileCache); 99 | } 100 | } 101 | 102 | wmoInstanceNames.Clear(); 103 | } 104 | 105 | return true; 106 | } 107 | 108 | bool InitFromCache(uint MapNum, uint originalMapId) 109 | { 110 | if (dirFileCache.Empty()) 111 | return true; 112 | 113 | string dirname = Program.BuildingsDirectory + "dir_bin"; 114 | using (BinaryWriter binaryWriter = new(File.Open(dirname, FileMode.Append, FileAccess.Write))) 115 | { 116 | foreach (ADTOutputCache cached in dirFileCache) 117 | { 118 | binaryWriter.Write(MapNum); 119 | uint flags = cached.Flags; 120 | if (MapNum != originalMapId) 121 | flags |= ModelFlags.ParentSpawn; 122 | binaryWriter.Write(flags); 123 | binaryWriter.Write(cached.Data); 124 | } 125 | } 126 | 127 | return true; 128 | } 129 | 130 | bool cacheable; 131 | List dirFileCache = new(); 132 | List wmoInstanceNames = new(); 133 | List modelInstanceNames = new(); 134 | } 135 | 136 | public struct ADTOutputCache 137 | { 138 | public uint Flags { get; set; } 139 | public byte[] Data { get; set; } 140 | } 141 | } -------------------------------------------------------------------------------- /Source/DataExtractor/Vmap/WDTFile.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2019 CypherCore 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | using DataExtractor.Framework.Constants; 19 | using DataExtractor.Map; 20 | using System; 21 | using System.Collections.Generic; 22 | using System.IO; 23 | 24 | namespace DataExtractor.Vmap 25 | { 26 | class WDTFile : ChunkedFile 27 | { 28 | public bool Init(uint mapId) 29 | { 30 | FilenameChunk mwmo = GetChunk("MWMO")?.As(); 31 | if (mwmo != null && mwmo.Filenames.Count > 0) 32 | { 33 | foreach (var filename in mwmo.Filenames) 34 | { 35 | wmoInstanceNames.Add(filename); 36 | VmapFile.ExtractSingleWmo(filename); 37 | } 38 | } 39 | 40 | MODF wmoChunk = GetChunk("MODF")?.As(); 41 | if (wmoChunk != null && wmoChunk.MapObjDefs.Length > 0) 42 | { 43 | foreach (var wmo in wmoChunk.MapObjDefs) 44 | { 45 | if (wmo.Flags.HasAnyFlag(MODFFlags.EntryIsFileID)) 46 | { 47 | string fileName = $"FILE{wmo.Id:X8}.xxx"; 48 | VmapFile.ExtractSingleWmo(fileName); 49 | WMORoot.Extract(wmo, fileName, false, mapId, mapId, Program.DirBinWriter, null); 50 | 51 | if (VmapFile.WmoDoodads.ContainsKey(fileName)) 52 | Model.ExtractSet(VmapFile.WmoDoodads[fileName], wmo, false, mapId, mapId, Program.DirBinWriter, null); 53 | } 54 | else 55 | { 56 | WMORoot.Extract(wmo, wmoInstanceNames[(int)wmo.Id], false, mapId, mapId, Program.DirBinWriter, null); 57 | if (VmapFile.WmoDoodads.ContainsKey(wmoInstanceNames[(int)wmo.Id])) 58 | Model.ExtractSet(VmapFile.WmoDoodads[wmoInstanceNames[(int)wmo.Id]], wmo, false, mapId, mapId, Program.DirBinWriter, null); 59 | } 60 | } 61 | 62 | wmoInstanceNames.Clear(); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | List wmoInstanceNames = new(); 69 | } 70 | } -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- 1 | = CypherCore -- Thanks/credits file = 2 | 3 | Special thanks goes out to Arctium And TrinityCore. We have gained base code and help from 4 | them many times in the creation of this project. Keep up the good work guys. 5 | 6 | Arctium (https://arctium.org/) 7 | TrinityCore (http://www.trinitycore.org) -------------------------------------------------------------------------------- /Tools.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29609.76 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataExtractor", "Source\DataExtractor\DataExtractor.csproj", "{B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|linux-x64 = Debug|linux-x64 11 | Debug|mac-x64 = Debug|mac-x64 12 | Debug|win-x64 = Debug|win-x64 13 | Release|linux-x64 = Release|linux-x64 14 | Release|mac-x64 = Release|mac-x64 15 | Release|win-x64 = Release|win-x64 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|linux-x64.ActiveCfg = Debug|linux-x64 19 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|linux-x64.Build.0 = Debug|linux-x64 20 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|mac-x64.ActiveCfg = Debug|mac-x64 21 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|mac-x64.Build.0 = Debug|mac-x64 22 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|win-x64.ActiveCfg = Debug|win-x64 23 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Debug|win-x64.Build.0 = Debug|win-x64 24 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|linux-x64.ActiveCfg = Release|linux-x64 25 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|linux-x64.Build.0 = Release|linux-x64 26 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|mac-x64.ActiveCfg = Release|mac-x64 27 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|mac-x64.Build.0 = Release|mac-x64 28 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|win-x64.ActiveCfg = Release|win-x64 29 | {B2AFFE2B-8A61-4CBF-B200-844AD2A69D7F}.Release|win-x64.Build.0 = Release|win-x64 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0448CEA1-624C-4157-986A-9320EDF1DC72} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | 5 | image: Visual Studio 2019 6 | 7 | clone_script: 8 | - cmd: git clone -q --branch=master --recursive https://github.com/CypherCore/Tools.git C:\projects\Tools-master 9 | 10 | build_script: 11 | - cmd: >- 12 | cd "C:/projects/Tools-master" 13 | 14 | dotnet restore 15 | 16 | dotnet build /p:Platform=win-x64 17 | -------------------------------------------------------------------------------- /default.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ../../Build/$(Configuration)/$(Platform) 5 | True 6 | False 7 | False 8 | win-x64;linux-x64;mac-x64 9 | win10-x64;linux-x64;osx-x64 10 | 11 | 12 | 13 | x64 14 | win10-x64 15 | 16 | false 17 | 18 | 19 | 20 | x64 21 | linux-x64 22 | 23 | false 24 | 25 | 26 | 27 | x64 28 | osx-x64 29 | 30 | false 31 | 32 | 33 | 34 | true 35 | true 36 | true 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------