├── .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 [](https://travis-ci.org/CypherCore/Tools) [](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 |
--------------------------------------------------------------------------------