TrinityCore
Loading...
Searching...
No Matches
CascHandles.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "CascHandles.h"
19#include "IoContext.h"
20#include "Resolver.h"
21#include "Socket.h"
22#include <CascLib.h>
23#include <boost/asio/streambuf.hpp>
24#include <boost/asio/read.hpp>
25#include <boost/asio/read_until.hpp>
26#include <boost/asio/write.hpp>
27#include <boost/asio/ssl/stream.hpp>
28#include <boost/filesystem/operations.hpp>
29#include <string>
30
32{
33 switch (error)
34 {
35 case ERROR_SUCCESS: return "SUCCESS";
36 case ERROR_FILE_CORRUPT: return "FILE_CORRUPT";
37 case ERROR_CAN_NOT_COMPLETE: return "CAN_NOT_COMPLETE";
38 case ERROR_HANDLE_EOF: return "HANDLE_EOF";
39 case ERROR_NO_MORE_FILES: return "NO_MORE_FILES";
40 case ERROR_BAD_FORMAT: return "BAD_FORMAT";
41 case ERROR_INSUFFICIENT_BUFFER: return "INSUFFICIENT_BUFFER";
42 case ERROR_ALREADY_EXISTS: return "ALREADY_EXISTS";
43 case ERROR_DISK_FULL: return "DISK_FULL";
44 case ERROR_INVALID_PARAMETER: return "INVALID_PARAMETER";
45 case ERROR_NOT_SUPPORTED: return "NOT_SUPPORTED";
46 case ERROR_NOT_ENOUGH_MEMORY: return "NOT_ENOUGH_MEMORY";
47 case ERROR_INVALID_HANDLE: return "INVALID_HANDLE";
48 case ERROR_ACCESS_DENIED: return "ACCESS_DENIED";
49 case ERROR_FILE_NOT_FOUND: return "FILE_NOT_FOUND";
50 case ERROR_FILE_ENCRYPTED: return "FILE_ENCRYPTED";
51 case ERROR_FILE_OFFLINE: return "FILE_OFFLINE";
52 default: return "UNKNOWN";
53 }
54}
55
56namespace
57{
58 Optional<std::string> DownloadFile(std::string const& serverName, int16 port, std::string const& getCommand)
59 {
60 boost::system::error_code error;
62 boost::asio::ssl::context sslContext(boost::asio::ssl::context::sslv23);
63 sslContext.set_options(boost::asio::ssl::context::no_sslv2, error);
64 sslContext.set_options(boost::asio::ssl::context::no_sslv3, error);
65 sslContext.set_options(boost::asio::ssl::context::no_tlsv1, error);
66 sslContext.set_options(boost::asio::ssl::context::no_tlsv1_1, error);
67 sslContext.set_default_verify_paths(error);
68
69 Trinity::Net::Resolver resolver(ioContext);
70
71 Optional<boost::asio::ip::tcp::endpoint> endpoint = resolver.Resolve(boost::asio::ip::tcp::v4(), serverName, std::to_string(port));
72 if (!endpoint)
73 return {};
74
75 boost::asio::ssl::stream<Trinity::Net::IoContextTcpSocket> socket(ioContext, sslContext);
76 socket.set_verify_mode(boost::asio::ssl::verify_none, error);
77 if (error)
78 return {};
79
80 socket.lowest_layer().connect(*endpoint, error);
81 if (error)
82 return {};
83
84 if (!SSL_set_tlsext_host_name(socket.native_handle(), serverName.c_str()))
85 return {};
86
87 socket.handshake(boost::asio::ssl::stream_base::client, error);
88 if (error)
89 return {};
90
91 boost::asio::streambuf request;
92 std::ostream request_stream(&request);
93
94 request_stream << "GET " << getCommand << " HTTP/1.0\r\n";
95 request_stream << "Host: " << serverName << "\r\n";
96 request_stream << "Connection: close\r\n\r\n";
97
98 // Send the request.
99 boost::asio::write(socket, request);
100
101 // Read the response status line.
102 boost::asio::streambuf response;
103 boost::asio::read_until(socket, response, "\r\n", error);
104 if (error)
105 {
106 printf("Downloading tact key list failed to read HTTP response status %s", error.message().c_str());
107 return {};
108 }
109
110 // Check that response is OK.
111 std::string http_version;
112 uint32 status_code;
113 std::string status_message;
114 std::istream response_stream(&response);
115
116 response_stream >> http_version;
117 response_stream >> status_code;
118 std::getline(response_stream, status_message);
119
120 if (status_code != 200)
121 {
122 printf("Downloading tact key list failed with server response %u %s", status_code, status_message.c_str());
123 return {};
124 }
125
126 // Read the response headers, which are terminated by a blank line.
127 boost::asio::read_until(socket, response, "\r\n\r\n");
128 if (error)
129 {
130 printf("Downloading tact key list failed to read HTTP response headers %s", error.message().c_str());
131 return {};
132 }
133
134 // Process the response headers.
135 std::string header;
136 while (std::getline(response_stream, header) && header != "\r")
137 {
138 }
139
140 std::stringstream rawBody;
141
142 // Write whatever content we already have to output.
143 if (response.size() > 0)
144 rawBody << &response;
145
146 // Read until EOF, writing data to output as we go.
147 while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error))
148 rawBody << &response;
149
150 return rawBody.str();
151 }
152
153 template<typename T>
154 bool GetStorageInfo(HANDLE storage, CASC_STORAGE_INFO_CLASS storageInfoClass, T* value)
155 {
156 size_t infoDataSizeNeeded = 0;
157 return ::CascGetStorageInfo(storage, storageInfoClass, value, sizeof(T), &infoDataSizeNeeded);
158 }
159}
160
161namespace CASC
162{
163using CASCCharType = std::remove_const_t<std::remove_pointer_t<decltype(CASC_OPEN_STORAGE_ARGS::szLocalPath)>>;
164using CASCStringType = std::basic_string<CASCCharType>;
165
166Storage::Storage(HANDLE handle) : _handle(handle)
167{
168}
169
171{
172 // attempt to download only once, not every storage opening
173 static Optional<std::string> const tactKeys = DownloadFile("raw.githubusercontent.com", 443, "/wowdev/TACTKeys/master/WoW.txt");
174
175 return tactKeys && CascImportKeysFromString(_handle, tactKeys->c_str());
176}
177
179{
180 ::CascCloseStorage(_handle);
181}
182
183Storage* Storage::Open(boost::filesystem::path const& path, uint32 localeMask, char const* product)
184{
185 CASCStringType strPath = path.template string<CASCStringType>();
186 CASCStringType strProduct(product, product + strlen(product)); // dumb conversion from char to wchar, always ascii
187 CASC_OPEN_STORAGE_ARGS args = {};
188 args.Size = sizeof(CASC_OPEN_STORAGE_ARGS);
189 args.szLocalPath = strPath.c_str();
190 args.szCodeName = strProduct.c_str();
191 args.dwLocaleMask = localeMask;
192 HANDLE handle = nullptr;
193 if (!CascOpenStorageEx(nullptr, &args, false, &handle))
194 {
195 DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
196 printf("Error opening casc storage '%s': %s\n", path.string().c_str(), HumanReadableCASCError(lastError));
197 CascCloseStorage(handle);
198 SetCascError(lastError);
199 return nullptr;
200 }
201
202 printf("Opened casc storage '%s'\n", path.string().c_str());
203 Storage* storage = new Storage(handle);
204
205 if (!storage->LoadOnlineTactKeys())
206 printf("Failed to load additional online encryption keys, some files might not be extracted.\n");
207
208 return storage;
209}
210
211Storage* Storage::OpenRemote(boost::filesystem::path const& path, uint32 localeMask, char const* product, char const* region)
212{
213 CASCStringType strPath = path.template string<CASCStringType>();
214 CASCStringType strProduct(product, product + strlen(product)); // dumb conversion from char to wchar, always ascii
215 CASCStringType strRegion(region, region + strlen(region)); // dumb conversion from char to wchar, always ascii
216 CASC_OPEN_STORAGE_ARGS args = {};
217 args.Size = sizeof(CASC_OPEN_STORAGE_ARGS);
218 args.szLocalPath = strPath.c_str();
219 args.szCodeName = strProduct.c_str();
220 args.szRegion = strRegion.c_str();
221 args.dwLocaleMask = localeMask;
222
223 HANDLE handle = nullptr;
224 if (!::CascOpenStorageEx(nullptr, &args, true, &handle))
225 {
226 DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
227 printf("Error opening remote casc storage: %s\n", HumanReadableCASCError(lastError));
228 CascCloseStorage(handle);
229 SetCascError(lastError);
230 return nullptr;
231 }
232
233 DWORD features = 0;
234 if (!GetStorageInfo(handle, CascStorageFeatures, &features) || !(features & CASC_FEATURE_ONLINE))
235 {
236 printf("Local casc storage detected in cache path \"%s\" (or its parent directory). Remote storage not opened!\n", path.string().c_str());
237 CascCloseStorage(handle);
238 SetCascError(ERROR_FILE_OFFLINE);
239 return nullptr;
240 }
241
242 printf("Opened remote casc storage '%s'\n", path.string().c_str());
243 Storage* storage = new Storage(handle);
244
245 if (!storage->LoadOnlineTactKeys())
246 printf("Failed to load additional online encryption keys, some files might not be extracted.\n");
247
248 return storage;
249}
250
252{
253 CASC_STORAGE_PRODUCT product;
254 if (GetStorageInfo(_handle, CascStorageProduct, &product))
255 return product.BuildNumber;
256
257 return 0;
258}
259
261{
262 DWORD locales;
263 if (GetStorageInfo(_handle, CascStorageInstalledLocales, &locales))
264 return locales;
265
266 return 0;
267}
268
269bool Storage::HasTactKey(uint64 keyLookup) const
270{
271 return CascFindEncryptionKey(_handle, keyLookup) != nullptr;
272}
273
274File* Storage::OpenFile(char const* fileName, uint32 localeMask, bool printErrors /*= false*/, bool zerofillEncryptedParts /*= false*/) const
275{
276 DWORD openFlags = CASC_OPEN_BY_NAME;
277 if (zerofillEncryptedParts)
278 openFlags |= CASC_OVERCOME_ENCRYPTED;
279
280 HANDLE handle = nullptr;
281 if (!::CascOpenFile(_handle, fileName, localeMask, openFlags, &handle))
282 {
283 DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
284 if (printErrors)
285 fprintf(stderr, "Failed to open '%s' in CASC storage: %s\n", fileName, HumanReadableCASCError(lastError));
286
287 CascCloseFile(handle);
288 SetCascError(lastError);
289 return nullptr;
290 }
291
292 return new File(handle);
293}
294
295File* Storage::OpenFile(uint32 fileDataId, uint32 localeMask, bool printErrors /*= false*/, bool zerofillEncryptedParts /*= false*/) const
296{
297 DWORD openFlags = CASC_OPEN_BY_FILEID;
298 if (zerofillEncryptedParts)
299 openFlags |= CASC_OVERCOME_ENCRYPTED;
300
301 HANDLE handle = nullptr;
302 if (!::CascOpenFile(_handle, CASC_FILE_DATA_ID(fileDataId), localeMask, openFlags, &handle))
303 {
304 DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
305 if (printErrors)
306 fprintf(stderr, "Failed to open 'FileDataId %u' in CASC storage: %s\n", fileDataId, HumanReadableCASCError(lastError));
307
308 CascCloseFile(handle);
309 SetCascError(lastError);
310 return nullptr;
311 }
312
313 return new File(handle);
314}
315
316File::File(HANDLE handle) : _handle(handle)
317{
318}
319
321{
322 ::CascCloseFile(_handle);
323}
324
326{
327 CASC_FILE_FULL_INFO info;
328 if (!::CascGetFileInfo(_handle, CascFileFullInfo, &info, sizeof(info), nullptr))
329 return CASC_INVALID_ID;
330
331 return info.FileDataId;
332}
333
335{
336 ULONGLONG size;
337 if (!::CascGetFileSize64(_handle, &size))
338 return -1;
339
340 return int64(size);
341}
342
344{
345 ULONGLONG position;
346 if (!::CascSetFilePointer64(_handle, 0, &position, FILE_CURRENT))
347 return -1;
348
349 return int64(position);
350}
351
353{
354 LONG parts[2];
355 memcpy(parts, &position, sizeof(parts));
356 return ::CascSetFilePointer64(_handle, position, nullptr, FILE_BEGIN);
357}
358
359bool File::ReadFile(void* buffer, uint32 bytes, uint32* bytesRead)
360{
361 DWORD bytesReadDWORD;
362 if (!::CascReadFile(_handle, buffer, bytes, &bytesReadDWORD))
363 return false;
364
365 if (bytesRead)
366 *bytesRead = bytesReadDWORD;
367
368 return true;
369}
370}
int64_t int64
Definition Define.h:149
int16_t int16
Definition Define.h:151
uint64_t uint64
Definition Define.h:153
uint32_t uint32
Definition Define.h:154
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
uint32 GetId() const
int64 GetSize() const
bool SetPointer(int64 position)
int64 GetPointer() const
HANDLE _handle
Definition CascHandles.h:78
bool ReadFile(void *buffer, uint32 bytes, uint32 *bytesRead)
File(HANDLE handle)
static Storage * Open(boost::filesystem::path const &path, uint32 localeMask, char const *product)
static Storage * OpenRemote(boost::filesystem::path const &path, uint32 localeMask, char const *product, char const *region)
File * OpenFile(char const *fileName, uint32 localeMask, bool printErrors=false, bool zerofillEncryptedParts=false) const
bool HasTactKey(uint64 keyLookup) const
uint32 GetBuildNumber() const
Storage(HANDLE handle)
uint32 GetInstalledLocalesMask() const
bool LoadOnlineTactKeys()
std::remove_const_t< std::remove_pointer_t< decltype(CASC_OPEN_STORAGE_ARGS::szLocalPath)> > CASCCharType
char const * HumanReadableCASCError(uint32 error)
std::basic_string< CASCCharType > CASCStringType