/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*/
#include "CascHandles.h"
#include "IoContext.h"
#include "Resolver.h"
#include
#include
#include
#include
#include
#include
#include
#include
char const* CASC::HumanReadableCASCError(uint32 error)
{
switch (error)
{
case ERROR_SUCCESS: return "SUCCESS";
case ERROR_FILE_CORRUPT: return "FILE_CORRUPT";
case ERROR_CAN_NOT_COMPLETE: return "CAN_NOT_COMPLETE";
case ERROR_HANDLE_EOF: return "HANDLE_EOF";
case ERROR_NO_MORE_FILES: return "NO_MORE_FILES";
case ERROR_BAD_FORMAT: return "BAD_FORMAT";
case ERROR_INSUFFICIENT_BUFFER: return "INSUFFICIENT_BUFFER";
case ERROR_ALREADY_EXISTS: return "ALREADY_EXISTS";
case ERROR_DISK_FULL: return "DISK_FULL";
case ERROR_INVALID_PARAMETER: return "INVALID_PARAMETER";
case ERROR_NOT_SUPPORTED: return "NOT_SUPPORTED";
case ERROR_NOT_ENOUGH_MEMORY: return "NOT_ENOUGH_MEMORY";
case ERROR_INVALID_HANDLE: return "INVALID_HANDLE";
case ERROR_ACCESS_DENIED: return "ACCESS_DENIED";
case ERROR_FILE_NOT_FOUND: return "FILE_NOT_FOUND";
case ERROR_FILE_ENCRYPTED: return "FILE_ENCRYPTED";
case ERROR_FILE_OFFLINE: return "FILE_OFFLINE";
default: return "UNKNOWN";
}
}
namespace
{
Optional DownloadFile(std::string const& serverName, int16 port, std::string const& getCommand)
{
boost::system::error_code error;
Trinity::Asio::IoContext ioContext;
boost::asio::ssl::context sslContext(boost::asio::ssl::context::sslv23);
sslContext.set_options(boost::asio::ssl::context::no_sslv2, error);
sslContext.set_options(boost::asio::ssl::context::no_sslv3, error);
sslContext.set_options(boost::asio::ssl::context::no_tlsv1, error);
sslContext.set_options(boost::asio::ssl::context::no_tlsv1_1, error);
sslContext.set_default_verify_paths(error);
Trinity::Net::Resolver resolver(ioContext);
Optional endpoint = resolver.Resolve(boost::asio::ip::tcp::v4(), serverName, std::to_string(port));
if (!endpoint)
return {};
boost::asio::ssl::stream socket(ioContext, sslContext);
socket.set_verify_mode(boost::asio::ssl::verify_none, error);
if (error)
return {};
socket.lowest_layer().connect(*endpoint, error);
if (error)
return {};
if (!SSL_set_tlsext_host_name(socket.native_handle(), serverName.c_str()))
return {};
socket.handshake(boost::asio::ssl::stream_base::client, error);
if (error)
return {};
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "GET " << getCommand << " HTTP/1.0\r\n";
request_stream << "Host: " << serverName << "\r\n";
request_stream << "Connection: close\r\n\r\n";
// Send the request.
boost::asio::write(socket, request);
// Read the response status line.
boost::asio::streambuf response;
boost::asio::read_until(socket, response, "\r\n", error);
if (error)
{
printf("Downloading tact key list failed to read HTTP response status %s", error.message().c_str());
return {};
}
// Check that response is OK.
std::string http_version;
uint32 status_code;
std::string status_message;
std::istream response_stream(&response);
response_stream >> http_version;
response_stream >> status_code;
std::getline(response_stream, status_message);
if (status_code != 200)
{
printf("Downloading tact key list failed with server response %u %s", status_code, status_message.c_str());
return {};
}
// Read the response headers, which are terminated by a blank line.
boost::asio::read_until(socket, response, "\r\n\r\n");
if (error)
{
printf("Downloading tact key list failed to read HTTP response headers %s", error.message().c_str());
return {};
}
// Process the response headers.
std::string header;
while (std::getline(response_stream, header) && header != "\r")
{
}
std::stringstream rawBody;
// Write whatever content we already have to output.
if (response.size() > 0)
rawBody << &response;
// Read until EOF, writing data to output as we go.
while (boost::asio::read(socket, response, boost::asio::transfer_at_least(1), error))
rawBody << &response;
return rawBody.str();
}
template
bool GetStorageInfo(HANDLE storage, CASC_STORAGE_INFO_CLASS storageInfoClass, T* value)
{
size_t infoDataSizeNeeded = 0;
return ::CascGetStorageInfo(storage, storageInfoClass, value, sizeof(T), &infoDataSizeNeeded);
}
}
namespace CASC
{
using CASCCharType = std::remove_const_t>;
using CASCStringType = std::basic_string;
Storage::Storage(HANDLE handle) : _handle(handle)
{
}
bool Storage::LoadOnlineTactKeys()
{
// attempt to download only once, not every storage opening
static Optional const tactKeys = DownloadFile("raw.githubusercontent.com", 443, "/wowdev/TACTKeys/master/WoW.txt");
return tactKeys && CascImportKeysFromString(_handle, tactKeys->c_str());
}
Storage::~Storage()
{
::CascCloseStorage(_handle);
}
Storage* Storage::Open(boost::filesystem::path const& path, uint32 localeMask, char const* product)
{
CASCStringType strPath = path.template string();
CASCStringType strProduct(product, product + strlen(product)); // dumb conversion from char to wchar, always ascii
CASC_OPEN_STORAGE_ARGS args = {};
args.Size = sizeof(CASC_OPEN_STORAGE_ARGS);
args.szLocalPath = strPath.c_str();
args.szCodeName = strProduct.c_str();
args.dwLocaleMask = localeMask;
HANDLE handle = nullptr;
if (!CascOpenStorageEx(nullptr, &args, false, &handle))
{
DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
printf("Error opening casc storage '%s': %s\n", path.string().c_str(), HumanReadableCASCError(lastError));
CascCloseStorage(handle);
SetCascError(lastError);
return nullptr;
}
printf("Opened casc storage '%s'\n", path.string().c_str());
Storage* storage = new Storage(handle);
if (!storage->LoadOnlineTactKeys())
printf("Failed to load additional online encryption keys, some files might not be extracted.\n");
return storage;
}
Storage* Storage::OpenRemote(boost::filesystem::path const& path, uint32 localeMask, char const* product, char const* region)
{
CASCStringType strPath = path.template string();
CASCStringType strProduct(product, product + strlen(product)); // dumb conversion from char to wchar, always ascii
CASCStringType strRegion(region, region + strlen(region)); // dumb conversion from char to wchar, always ascii
CASC_OPEN_STORAGE_ARGS args = {};
args.Size = sizeof(CASC_OPEN_STORAGE_ARGS);
args.szLocalPath = strPath.c_str();
args.szCodeName = strProduct.c_str();
args.szRegion = strRegion.c_str();
args.dwLocaleMask = localeMask;
HANDLE handle = nullptr;
if (!::CascOpenStorageEx(nullptr, &args, true, &handle))
{
DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
printf("Error opening remote casc storage: %s\n", HumanReadableCASCError(lastError));
CascCloseStorage(handle);
SetCascError(lastError);
return nullptr;
}
DWORD features = 0;
if (!GetStorageInfo(handle, CascStorageFeatures, &features) || !(features & CASC_FEATURE_ONLINE))
{
printf("Local casc storage detected in cache path \"%s\" (or its parent directory). Remote storage not opened!\n", path.string().c_str());
CascCloseStorage(handle);
SetCascError(ERROR_FILE_OFFLINE);
return nullptr;
}
printf("Opened remote casc storage '%s'\n", path.string().c_str());
Storage* storage = new Storage(handle);
if (!storage->LoadOnlineTactKeys())
printf("Failed to load additional online encryption keys, some files might not be extracted.\n");
return storage;
}
uint32 Storage::GetBuildNumber() const
{
CASC_STORAGE_PRODUCT product;
if (GetStorageInfo(_handle, CascStorageProduct, &product))
return product.BuildNumber;
return 0;
}
uint32 Storage::GetInstalledLocalesMask() const
{
DWORD locales;
if (GetStorageInfo(_handle, CascStorageInstalledLocales, &locales))
return locales;
return 0;
}
bool Storage::HasTactKey(uint64 keyLookup) const
{
return CascFindEncryptionKey(_handle, keyLookup) != nullptr;
}
File* Storage::OpenFile(char const* fileName, uint32 localeMask, bool printErrors /*= false*/, bool zerofillEncryptedParts /*= false*/) const
{
DWORD openFlags = CASC_OPEN_BY_NAME;
if (zerofillEncryptedParts)
openFlags |= CASC_OVERCOME_ENCRYPTED;
HANDLE handle = nullptr;
if (!::CascOpenFile(_handle, fileName, localeMask, openFlags, &handle))
{
DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
if (printErrors)
fprintf(stderr, "Failed to open '%s' in CASC storage: %s\n", fileName, HumanReadableCASCError(lastError));
CascCloseFile(handle);
SetCascError(lastError);
return nullptr;
}
return new File(handle);
}
File* Storage::OpenFile(uint32 fileDataId, uint32 localeMask, bool printErrors /*= false*/, bool zerofillEncryptedParts /*= false*/) const
{
DWORD openFlags = CASC_OPEN_BY_FILEID;
if (zerofillEncryptedParts)
openFlags |= CASC_OVERCOME_ENCRYPTED;
HANDLE handle = nullptr;
if (!::CascOpenFile(_handle, CASC_FILE_DATA_ID(fileDataId), localeMask, openFlags, &handle))
{
DWORD lastError = GetCascError(); // support checking error set by *Open* call, not the next *Close*
if (printErrors)
fprintf(stderr, "Failed to open 'FileDataId %u' in CASC storage: %s\n", fileDataId, HumanReadableCASCError(lastError));
CascCloseFile(handle);
SetCascError(lastError);
return nullptr;
}
return new File(handle);
}
File::File(HANDLE handle) : _handle(handle)
{
}
File::~File()
{
::CascCloseFile(_handle);
}
uint32 File::GetId() const
{
CASC_FILE_FULL_INFO info;
if (!::CascGetFileInfo(_handle, CascFileFullInfo, &info, sizeof(info), nullptr))
return CASC_INVALID_ID;
return info.FileDataId;
}
int64 File::GetSize() const
{
ULONGLONG size;
if (!::CascGetFileSize64(_handle, &size))
return -1;
return int64(size);
}
int64 File::GetPointer() const
{
ULONGLONG position;
if (!::CascSetFilePointer64(_handle, 0, &position, FILE_CURRENT))
return -1;
return int64(position);
}
bool File::SetPointer(int64 position)
{
LONG parts[2];
memcpy(parts, &position, sizeof(parts));
return ::CascSetFilePointer64(_handle, position, nullptr, FILE_BEGIN);
}
bool File::ReadFile(void* buffer, uint32 bytes, uint32* bytesRead)
{
DWORD bytesReadDWORD;
if (!::CascReadFile(_handle, buffer, bytes, &bytesReadDWORD))
return false;
if (bytesRead)
*bytesRead = bytesReadDWORD;
return true;
}
}