Commit local workspace.

- Split minidump (upload) and exception handling code
- Smaller minidumps for faster uploads
- Several new build flags (accessible from premake5 with --params)
- BitMessage abstractions
- Some other things I can't remember.
This commit is contained in:
/dev/urandom 2016-08-28 19:38:16 +02:00
parent 1d5927f093
commit ebba2819d6
No known key found for this signature in database
GPG Key ID: 41322B973E0F295E
16 changed files with 665 additions and 175 deletions

View File

@ -43,6 +43,21 @@ newoption {
description = "Always compile unit tests."
}
newoption {
trigger = "force-exception-handler",
description = "Install custom unhandled exception handler even for Debug builds."
}
newoption {
trigger = "force-minidump-upload",
description = "Upload minidumps even for Debug builds."
}
newoption {
trigger = "disable-bitmessage",
description = "Disable use of BitMessage completely."
}
newaction {
trigger = "version",
description = "Returns the version string for the current commit of the source code.",
@ -242,6 +257,9 @@ workspace "iw4x"
resincludedirs {
"$(ProjectDir)src" -- fix for VS IDE
}
removefiles {
"./src/Components/Modules/Tor.*",
}
-- Debug flags
if _OPTIONS["ac-debug-detections"] then
@ -253,6 +271,18 @@ workspace "iw4x"
if _OPTIONS["force-unit-tests"] then
defines { "FORCE_UNIT_TESTS" }
end
if _OPTIONS["force-minidump-upload"] then
defines { "FORCE_MINIDUMP_UPLOAD" }
end
if _OPTIONS["force-exception-handler"] then
defines { "FORCE_EXCEPTION_HANDLER" }
end
if _OPTIONS["disable-bitmessage"] then
defines { "DISABLE_BITMESSAGE" }
removefiles {
"./src/Components/Modules/BitMessage.*",
}
end
-- Pre-compiled header
pchheader "STDInclude.hpp" -- must be exactly same as used in #include directives
@ -260,7 +290,9 @@ workspace "iw4x"
buildoptions { "/Zm200" }
-- Dependency libraries
bitmrc.import()
if not _OPTIONS["disable-bitmessage"] then
bitmrc.import()
end
fmt.import()
json11.import()
libtomcrypt.import()
@ -360,16 +392,18 @@ workspace "iw4x"
}
group "External dependencies"
bitmrc.project()
if not _OPTIONS["disable-bitmessage"] then
bitmrc.project()
libcryptopp.project()
sqlite3.project()
end
fmt.project()
json11.project()
libcryptopp.project()
libtomcrypt.project()
libtommath.project()
mongoose.project()
pdcurses.project()
protobuf.project()
sqlite3.project()
winksignals.project()
zlib.project()

View File

@ -45,6 +45,7 @@ namespace Components
Loader::Register(new Dedicated());
Loader::Register(new Discovery());
Loader::Register(new Exception());
Loader::Register(new MinidumpUpload());
Loader::Register(new FastFiles());
Loader::Register(new Materials());
Loader::Register(new FileSystem());

View File

@ -62,6 +62,7 @@ namespace Components
#include "Modules\Dedicated.hpp"
#include "Modules\Discovery.hpp"
#include "Modules\Exception.hpp"
#include "Modules\MinidumpUpload.hpp"
#include "Modules\FastFiles.hpp"
#include "Modules\Materials.hpp"
#include "Modules\Singleton.hpp"

View File

@ -8,14 +8,13 @@ using namespace Utils;
namespace Components
{
BitMessage* BitMessage::Singleton = nullptr;
BitMessage* BitMessage::Singleton = NULL;
BitMessage::BitMessage()
{
if (Singleton != nullptr)
{
throw new std::runtime_error("Only 1 BitMessage instance allowed at the same time.");
}
if (Singleton != NULL)
throw new std::runtime_error("Only one instance of BitMessage allowed at the same time.");
Singleton = this;
Logger::Print("Initializing BitMessage...\n");
@ -160,6 +159,92 @@ namespace Components
});
}
BitMessage::~BitMessage()
{
Singleton = NULL;
}
void BitMessage::SetDefaultTTL(time_t ttl)
{
this->BMClient->defaultTTL = ttl;
}
bool BitMessage::RequestPublicKey(std::string targetAddress)
{
// Convert to ustring
ustring targetAddressU;
targetAddressU.fromString(targetAddress);
// Convert to PubAddr
PubAddr pubAddr;
if (!pubAddr.loadAddr(targetAddressU))
{
return false;
}
// Request public key!
this->BMClient->getPubKey(pubAddr);
return true;
}
bool BitMessage::Subscribe(std::string targetAddress)
{
// Convert to ustring
ustring targetAddressU;
targetAddressU.fromString(targetAddress);
// Convert to PubAddr
PubAddr pubAddr;
if (!pubAddr.loadAddr(targetAddressU))
{
return false;
}
// Subscribe!
this->BMClient->addSubscription(pubAddr);
return true;
}
bool BitMessage::SendMsg(std::string targetAddress, std::string message, time_t ttl)
{
// Convert target address to ustring
ustring targetAddressU;
targetAddressU.fromString(targetAddress);
// Convert target address to PubAddr
PubAddr pubAddr;
if (!pubAddr.loadAddr(targetAddressU))
{
return false;
}
// Convert message to ustring
ustring messageU;
messageU.fromString(message);
// Send the message
// TODO - Set mutex on priv when accessing first private address
if (ttl > 0)
this->BMClient->sendMessage(messageU, pubAddr, this->BMClient->PrivAddresses[0], ttl);
else
this->BMClient->sendMessage(messageU, pubAddr, this->BMClient->PrivAddresses[0]);
return true;
}
bool BitMessage::SendBroadcast(std::string message, time_t ttl)
{
// Convert message to ustring
ustring messageU;
messageU.fromString(message);
// TODO - Set mutex on priv when accessing first private address
if (ttl > 0)
this->BMClient->sendBroadcast(messageU, this->BMClient->PrivAddresses[0], ttl);
else
this->BMClient->sendBroadcast(messageU, this->BMClient->PrivAddresses[0]);
return true;
}
bool BitMessage::InitAddr()
{
Logger::Print("Generating BM address...\n");

View File

@ -2,40 +2,27 @@
#ifndef DISABLE_BITMESSAGE
#define BITMESSAGE_KEYS_FILENAME std::string("players/bmkey.dat")
#define BITMESSAGE_KEYS_FILENAME std::string("players/bmkeys.dat")
#define BITMESSAGE_OBJECT_STORAGE_FILENAME std::string("players/bmstore.dat")
static const std::map<std::string, unsigned short> bitmessageKnownNodes = {
// https://github.com/Bitmessage/PyBitmessage/blob/4622d952e47a7dbb3a90aa79f4d20163aa14b041/src/defaultKnownNodes.py#L15-L23
// Stream 1
//{ "2604:2000:1380:9f:82e:148b:2746:d0c7", 8080 },
{ "5.45.99.75", 8444 },
{ "75.167.159.54", 8444 },
{ "95.165.168.168", 8444 },
{ "85.180.139.241", 8444 },
{ "158.222.211.81", 8080 },
{ "178.62.12.187", 8448 },
{ "24.188.198.204", 8111 },
{ "109.147.204.113", 1195 },
{ "178.11.46.221", 8444 },
// Stream 2 has none yet
// Stream 3 has none yet
};
namespace Components
{
class BitMessage : public Component
{
public:
BitMessage();
~BitMessage();
#ifdef DEBUG
const char* GetName() { return "BitMessage"; };
#endif
void SetDefaultTTL(time_t ttl);
bool RequestPublicKey(std::string targetAddress);
bool Subscribe(std::string targetAddress);
bool SendMsg(std::string targetAddress, std::string message, time_t ttl = 0);
bool SendBroadcast(std::string message, time_t ttl = 0);
static BitMessage* Singleton;
BitMRC* BMClient;

View File

@ -9,145 +9,53 @@
namespace Components
{
// Fileupload to Webhost
bool Exception::UploadMinidump(std::string filename)
{
Utils::WebIO webio("Firefucks", UPLOAD_URL);
if (Utils::IO::FileExists(filename))
{
std::string buffer = Utils::IO::ReadFile(filename);
std::string result = webio.PostFile(buffer);
std::string errors;
json11::Json object = json11::Json::parse(result, errors);
if (!object.is_object()) return false;
json11::Json success = object["success"];
if (!success.is_bool() || !success.bool_value()) return false;
json11::Json files = object["files"];
if (!files.is_array()) return false;
for (auto file : files.array_items())
{
json11::Json url = file["url"];
json11::Json hash = file["hash"];
if (hash.is_string() && url.is_string())
{
if (Utils::String::ToLower(Utils::Cryptography::SHA1::Compute(buffer, true)) == Utils::String::ToLower(hash.string_value()))
{
MessageBoxA(0, url.string_value().data(), 0, 0);
return true;
}
}
}
}
return false;
}
// Fileupload to Bitmessage
bool Exception::UploadMinidump2BM(std::string filename)
{
if (Components::BitMessage::Singleton == nullptr)
{
//throw new std::runtime_error("BitMessage was already stopped.");
Logger::Print("Bitmessage was already stopped.\n");
}
else
{
BitMessage* Singleton;
Singleton = Components::BitMessage::Singleton;
if (Utils::IO::FileExists(filename))
{
// TODO: Validate filesize of minidump
// TODO: Convert to base64
// TODO: Split if filesize > xxxkB
std::string buffer = Utils::String::encodeBase64(Utils::IO::ReadFile(filename));
ustring pubAddrString;
pubAddrString.fromString(BITMESSAGE_UPLOAD_IDENTITY);
PubAddr pubAddr;
if (pubAddr.loadAddr(pubAddrString))
{
int g;
ustring msg;
Logger::Print("Uploading Minidump (this may take a while)...\n");
for (size_t i = 0; i < buffer.size(); i=i+BITMESSAGE_SIZE_LIMIT)
{
if (buffer.size() > i + BITMESSAGE_SIZE_LIMIT)
g = buffer.size();
else
g = i + BITMESSAGE_SIZE_LIMIT;
std::string substring = buffer.substr(i, g);
msg.fromString(substring);
Singleton->BMClient->sendMessage(msg, pubAddr, Singleton->BMClient->PrivAddresses[0]);
}
Logger::Print("Minidump uploaded.\n");
}
else
{
Logger::Print("Address not correct!\n");
}
}
}
return false;
}
LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo)
{
char filename[MAX_PATH];
__time64_t time;
tm ltime;
_time64(&time);
_localtime64_s(&ltime, &time);
strftime(filename, sizeof(filename) - 1, "iw4x-" VERSION_STR "-%Y%m%d%H%M%S.dmp", &ltime);
HANDLE hFile = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile && hFile != INVALID_HANDLE_VALUE)
// Pass on harmless errors
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_INTEGER_OVERFLOW ||
ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_FLOAT_OVERFLOW)
{
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE };
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &ex, NULL, NULL);
CloseHandle(hFile);
return EXCEPTION_CONTINUE_EXECUTION;
}
//Exception::UploadMinidump(filename);
Exception::UploadMinidump2BM(filename);
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
auto minidump = MinidumpUpload::Singleton->CreateQueuedMinidump(ExceptionInfo);
if (minidump == NULL)
{
OutputDebugStringA("Failed to create new minidump!");
Utils::OutputDebugLastError();
}
printf("Trying to print an error message from ExceptionFilter...");
switch (ExceptionInfo->ExceptionRecord->ExceptionCode)
{
case EXCEPTION_STACK_OVERFLOW:
Logger::Error("Termination because of a stack overflow.\n");
TerminateProcess(GetCurrentProcess(), EXCEPTION_STACK_OVERFLOW);
}
else
{
break;
default:
Logger::Error("Fatal error (0x%08X) at 0x%08X.", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
break;
}
//TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH;
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI Exception::SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER)
{
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
return lpTopLevelExceptionFilter;
return SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
}
LPTOP_LEVEL_EXCEPTION_FILTER Exception::Hook()
{
return SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
}
Exception::Exception()
{
#ifdef DEBUG
// Display DEBUG branding, so we know we're on a debug build
Renderer::OnFrame([] ()
Renderer::OnFrame([]()
{
Game::Font* font = Game::R_RegisterFont("fonts/normalFont");
float color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
@ -162,18 +70,19 @@ namespace Components
Game::R_AddCmdDrawText("DEBUG-BUILD", 0x7FFFFFFF, font, 15.0f, 10.0f + Game::R_TextHeight(font), 1.0f, 1.0f, 0.0f, color, Game::ITEM_TEXTSTYLE_SHADOWED);
});
#else
#endif
#if !defined(DEBUG) || defined(FORCE_EXCEPTION_HANDLER)
Utils::Hook::Set(0x6D70AC, Exception::SetUnhandledExceptionFilterStub);
SetUnhandledExceptionFilter(&Exception::ExceptionFilter);
#endif
Command::Add("mapTest", [] (Command::Params params)
Command::Add("mapTest", [](Command::Params params)
{
std::string command;
int max = (params.Length() >= 2 ? atoi(params[1]) : 16), current = 0;
for (int i =0;;)
for (int i = 0;;)
{
char* mapname = Game::mapnames[i];
if (!*mapname)
@ -182,7 +91,7 @@ namespace Components
continue;
}
if(!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect
if (!(i % 2)) command.append(fmt::sprintf("wait 250;disconnect;wait 750;", mapname)); // Test a disconnect
else command.append(fmt::sprintf("wait 500;", mapname)); // Test direct map switch
command.append(fmt::sprintf("map %s;", mapname));
@ -193,6 +102,12 @@ namespace Components
Command::Execute(command, false);
});
Command::Add("debug_exceptionhandler", [](Command::Params)
{
Logger::Print("Rerunning SetUnhandledExceptionHandler...\n");
auto oldHandler = Exception::Hook();
Logger::Print("Old exception handler was 0x%010X.\n", oldHandler);
});
#pragma warning(push)
#pragma warning(disable:4740) // flow in or out of inline asm code suppresses global optimization
Command::Add("debug_minidump", [](Command::Params)

View File

@ -1,6 +1,4 @@
#define UPLOAD_URL "https://reich.io/upload.php"
#define BITMESSAGE_UPLOAD_IDENTITY "BM-NBhqCHraxGyUT38cfhRQYWx29gJ7QePa"
#define BITMESSAGE_SIZE_LIMIT 230000
namespace Components
{
@ -12,12 +10,10 @@ namespace Components
#ifdef DEBUG
const char* GetName() { return "Exception"; };
#endif
static LPTOP_LEVEL_EXCEPTION_FILTER Hook();
private:
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);
static bool UploadMinidump(std::string filename);
static bool UploadMinidump2BM(std::string filename);
};
}

View File

@ -0,0 +1,366 @@
#include "STDInclude.hpp"
#include "Shlwapi.h"
const int MiniDumpTiny = MiniDumpFilterMemory | MiniDumpWithoutAuxiliaryState | MiniDumpWithoutOptionalData | MiniDumpFilterModulePaths | MiniDumpIgnoreInaccessibleMemory;
namespace Components
{
#pragma region Minidump class implementation
inline Minidump::Minidump() {}
Minidump::~Minidump()
{
if (this->mapFileHandle != NULL && this->mapFileHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(this->mapFileHandle);
this->mapFileHandle = INVALID_HANDLE_VALUE;
}
if (this->fileHandle != NULL && this->fileHandle != INVALID_HANDLE_VALUE)
{
CloseHandle(this->fileHandle);
this->fileHandle = INVALID_HANDLE_VALUE;
}
}
std::string Minidump::ToString()
{
if (!Minidump::EnsureFileMapping()) return false;
auto pBuf = MapViewOfFile(this->mapFileHandle, FILE_MAP_READ, 0, 0, 0);
if (pBuf == NULL)
{
Utils::OutputDebugLastError();
throw new std::runtime_error("Could not read minidump.");
}
size_t fileSize;
DWORD fileSizeHi;
fileSize = GetFileSize(this->fileHandle, &fileSizeHi);
#ifdef _WIN64
fileSize |= ((size_t)fileSizeHi << 32);
#endif
std::string retval = std::string((const char*)pBuf, fileSize * sizeof(const char));
UnmapViewOfFile(pBuf);
return retval;
}
bool Minidump::GetStream(MINIDUMP_STREAM_TYPE type, PMINIDUMP_DIRECTORY* directoryPtr, PVOID* streamBeginningPtr, ULONG* streamSizePtr)
{
if (!Minidump::EnsureFileMapping()) return false;
auto pBuf = MapViewOfFile(this->mapFileHandle, FILE_MAP_READ, 0, 0, 0);
if (pBuf == NULL)
{
Utils::OutputDebugLastError();
throw new std::runtime_error("Could not read minidump.");
}
BOOL success = MiniDumpReadDumpStream(pBuf, type, directoryPtr, streamBeginningPtr, streamSizePtr);
UnmapViewOfFile(pBuf);
if (success != TRUE)
return false;
return true;
}
inline bool Minidump::Check()
{
/*PMINIDUMP_DIRECTORY directory;
PVOID stream;
ULONG streamSize;
return Minidump::GetStream(ExceptionStream, &directory, &stream, &streamSize);*/
return Minidump::GetStream(ExceptionStream, NULL, NULL, NULL);
}
Minidump* Minidump::Create(std::string path, LPEXCEPTION_POINTERS exceptionInfo, int type)
{
Minidump* minidump = Minidump::Initialize(path);
if (minidump == NULL) return minidump;
// Do the dump generation
MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), exceptionInfo, FALSE };
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), minidump->fileHandle, (MINIDUMP_TYPE)type, &ex, NULL, NULL))
{
Utils::OutputDebugLastError();
delete minidump;
return NULL;
}
if (SetFilePointer(minidump->fileHandle, 0, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
{
Utils::OutputDebugLastError();
delete minidump;
DeleteFileA(path.c_str());
return NULL;
}
return minidump;
}
Minidump* Minidump::Open(std::string path)
{
Minidump* minidump = Minidump::Initialize(path, FILE_SHARE_READ);
return minidump;
}
bool Minidump::EnsureFileMapping()
{
if (this->mapFileHandle == NULL || this->mapFileHandle == INVALID_HANDLE_VALUE)
{
this->mapFileHandle = CreateFileMappingA(this->fileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
if (this->mapFileHandle == NULL || this->mapFileHandle == INVALID_HANDLE_VALUE)
{
Utils::OutputDebugLastError();
return false;
}
}
return true;
}
Minidump* Minidump::Initialize(std::string path, DWORD fileShare)
{
Minidump* minidump = new Minidump();
minidump->fileHandle = CreateFileA(path.c_str(),
GENERIC_WRITE | GENERIC_READ, fileShare,
NULL, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, NULL);
if (minidump->fileHandle == NULL || minidump->fileHandle == INVALID_HANDLE_VALUE)
{
Utils::OutputDebugLastError();
delete minidump;
return NULL;
}
return minidump;
}
#pragma endregion
#pragma region Minidump uploader class implementation
MinidumpUpload* MinidumpUpload::Singleton = NULL;
const std::string MinidumpUpload::queuedMinidumpsFolder = "minidumps\\";
#ifdef DISABLE_BITMESSAGE
const std::vector<std::string> MinidumpUpload::targetUrls = {
"https://reich.io/upload.php",
"https://hitlers.kz/upload.php"
};
#else
const std::string MinidumpUpload::targetAddress = "BM-2cSksR7gyyFcNK7MaFoxGCjRJWxtoGckdj";
const unsigned int MinidumpUpload::maxSegmentSize = 200 * 1024; // 200 kB
#endif
MinidumpUpload::MinidumpUpload()
{
if (Singleton != NULL)
throw new std::runtime_error("Can only create one instance at a time.");
Singleton = this;
#if !defined(DEBUG) || defined(FORCE_MINIDUMP_UPLOAD)
this->uploadThread = std::thread([&]() { this->UploadQueuedMinidumps(); });
#endif
}
MinidumpUpload::~MinidumpUpload()
{
Singleton = NULL;
if (this->uploadThread.joinable())
{
this->uploadThread.join();
}
}
bool MinidumpUpload::EnsureQueuedMinidumpsFolderExists()
{
BOOL success = CreateDirectoryA(queuedMinidumpsFolder.c_str(), NULL);
if (success != TRUE) success = GetLastError() == ERROR_ALREADY_EXISTS;
return success == TRUE;
}
Minidump* MinidumpUpload::CreateQueuedMinidump(LPEXCEPTION_POINTERS exceptionInfo, int minidumpType)
{
// Note that most of the Path* functions are DEPRECATED and they have been replaced by Cch variants that only work on Windows 8+.
// If you plan to drop support for Windows 7, please upgrade these calls to prevent accidental buffer overflows!
if (!EnsureQueuedMinidumpsFolderExists()) return NULL;
// Current executable name
char exeFileName[MAX_PATH];
GetModuleFileNameA(NULL, exeFileName, MAX_PATH);
PathStripPathA(exeFileName);
PathRemoveExtensionA(exeFileName);
// Generate filename
char filenameFriendlyTime[MAX_PATH];
__time64_t time;
tm ltime;
_time64(&time);
_localtime64_s(&ltime, &time);
strftime(filenameFriendlyTime, sizeof(filenameFriendlyTime) - 1, "%Y%m%d%H%M%S", &ltime);
// Combine with queuedMinidumpsFolder
char filename[MAX_PATH];
PathCombineA(filename, queuedMinidumpsFolder.c_str(), Utils::String::VA("%s-" VERSION_STR "-%s.dmp", exeFileName, filenameFriendlyTime));
// Generate the dump
return Minidump::Create(filename, exceptionInfo, minidumpType);
}
bool MinidumpUpload::UploadQueuedMinidumps()
{
#ifndef DISABLE_BITMESSAGE
// Preload public key for our target that will receive minidumps
if (!BitMessage::Singleton->RequestPublicKey(MinidumpUpload::targetAddress))
{
Logger::Error("Failed to request public key for minidump collection address.\n");
}
#endif
// Check if folder exists
if (!PathIsDirectoryA(queuedMinidumpsFolder.c_str()))
{
// Nothing to upload
return PathFileExistsA(queuedMinidumpsFolder.c_str()) == FALSE;
}
// Walk through directory and search for valid minidumps
WIN32_FIND_DATAA ffd;
HANDLE hFind = FindFirstFileA(Utils::String::VA("%s\\*.dmp", queuedMinidumpsFolder), &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) > 0)
continue; // ignore directory
char fullPath[MAX_PATH_SIZE];
PathCombineA(fullPath, queuedMinidumpsFolder.c_str(), ffd.cFileName);
// Try to open this minidump
auto minidump = Minidump::Open(fullPath);
if (minidump == NULL)
continue; // file can't be opened
// Upload!
if (!MinidumpUpload::Upload(minidump))
continue; // couldn't upload that minidump, keep for another attempt
delete minidump;
#ifndef KEEP_MINIDUMPS_AFTER_UPLOAD
// Delete minidump if possible
DeleteFileA(fullPath);
#endif
} while (FindNextFileA(hFind, &ffd) != 0);
}
return true;
}
bool MinidumpUpload::Upload(Minidump* minidump)
{
// Checking if we can extract any information from minidump first, just to be sure that this is valid.
if (!minidump->Check())
{
Utils::OutputDebugLastError();
return false;
}
std::map<std::string, std::string> extraHeaders = {
{"Hash-SHA512", Utils::Cryptography::SHA512::Compute(minidump->ToString(), true)},
{"Compression", "deflate"},
{"ID", Utils::String::GenerateUUIDString()},
};
Logger::Print("Compressing minidump...\n");
std::string compressedMinidump = Utils::Compression::ZLib::Compress(minidump->ToString());
Logger::Print("Uploading minidump...\n");
#ifdef DISABLE_BITMESSAGE
for (auto& targetUrl : targetUrls)
{
Utils::WebIO webio("Firefucks", targetUrl);
std::string buffer = MinidumpUpload::Encode(compressedMinidump, extraHeaders);
std::string result = webio.PostFile(buffer, "files[]", "minidump.dmpx");
std::string errors;
json11::Json object = json11::Json::parse(result, errors);
if (!object.is_object()) continue;
json11::Json success = object["success"];
if (!success.is_bool() || !success.bool_value()) return false;
json11::Json files = object["files"];
if (!files.is_array()) continue;
for (auto file : files.array_items())
{
json11::Json url = file["url"];
json11::Json hash = file["hash"];
if (hash.is_string() && url.is_string())
{
if (Utils::String::ToLower(Utils::Cryptography::SHA1::Compute(buffer, true)) == Utils::String::ToLower(hash.string_value()))
{
MessageBoxA(0, url.string_value().data(), 0, 0);
return true;
}
}
}
}
return false;
#else
// BitMessage has a max msg size that is somewhere around 220 KB, split it up as necessary!
auto totalParts = compressedMinidump.size() / this->maxSegmentSize + 1;
extraHeaders.insert({ "Parts", Utils::String::VA("%d",totalParts) });
for (size_t offset = 0; offset < compressedMinidump.size(); offset += this->maxSegmentSize)
{
auto extraPartHeaders = extraHeaders;
auto part = compressedMinidump.substr(offset, std::min(this->maxSegmentSize, compressedMinidump.size() - offset));
auto partNum = offset / this->maxSegmentSize + 1;
extraPartHeaders.insert({ "Part", Utils::String::VA("%d", partNum) });
Logger::Print("Uploading part %d out of %d (%d bytes)...\n", partNum, totalParts, part.size());
BitMessage::Singleton->SendMsg(MinidumpUpload::targetAddress, MinidumpUpload::Encode(part, extraPartHeaders));
}
return true;
#endif
}
std::string MinidumpUpload::Encode(std::string data, std::map<std::string, std::string> extraHeaders)
{
std::string marker = "MINIDUMP";
std::stringstream output;
extraHeaders["Encoding"] = "raw";
extraHeaders["Version"] = VERSION_STR;
output << "-----BEGIN " << marker << "-----\n";
// Insert extra headers
for (auto& header : extraHeaders)
{
output << header.first << ": " << header.second << "\n";
}
output << "\n"
<< data << "\n"
<< "-----END " << marker << "-----\n";
return output.str();
}
#pragma endregion
}

View File

@ -0,0 +1,77 @@
#pragma once
#pragma warning(push)
#pragma warning(disable: 4091)
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
#pragma warning(pop)
extern const int MiniDumpTiny;
namespace Components
{
// This class holds a Minidump and allows easy access to its aspects.
class Minidump
{
public:
~Minidump();
static Minidump* Create(std::string path, LPEXCEPTION_POINTERS exceptionInfo, int type = MiniDumpTiny);
static Minidump* Open(std::string path);
bool GetStream(MINIDUMP_STREAM_TYPE type, PMINIDUMP_DIRECTORY* directoryPtr, PVOID* streamPtr, ULONG* streamSizePtr);
bool Check();
std::string ToString();
private:
Minidump();
bool EnsureFileMapping();
static Minidump* Initialize(std::string path, DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE);
HANDLE fileHandle;
HANDLE mapFileHandle;
};
class MinidumpUpload : public Component
{
public:
#ifdef DEBUG
const char* GetName() { return "MinidumpUpload"; };
#endif
MinidumpUpload();
~MinidumpUpload();
// Uploads the given minidump.
bool Upload(Minidump* minidump);
// Generates a new minidump and saves it to the folder for queued minidumps.
Minidump* CreateQueuedMinidump(LPEXCEPTION_POINTERS exceptionInfo, int minidumpType = MiniDumpTiny);
// Browses the folder for queued minidumps and uploads each queued minidump.
// On Release builds this will also delete every successfully uploaded minidump.
bool UploadQueuedMinidumps();
static MinidumpUpload* Singleton;
private:
std::thread uploadThread;
// Encodes the given minidump so that it can be uploaded in a proper format.
// Internally, this will compress the minidump and decorate it with proper markers and first-look headers.
static std::string Encode(std::string data, std::map<std::string, std::string> extraHeaders = {});
// Ensures the queued minidumps folder exists. Will return false if the directory can't be created and does not exist.
static bool EnsureQueuedMinidumpsFolderExists();
// Contains the path to the minidumps folder.
static const std::string queuedMinidumpsFolder;
#ifdef DISABLE_BITMESSAGE
static const std::vector<std::string> targetUrls;
#else
static const std::string targetAddress;
static const unsigned int maxSegmentSize;
#endif
};
}

View File

@ -61,8 +61,9 @@
#include <json11.hpp>
#include <tomcrypt.h>
#include <wink/signal.hpp>
// Bitmessage
#ifndef DISABLE_BITMESSAGE
#include <BitMRC.h>
#endif
#ifdef max
#undef max
@ -112,6 +113,7 @@
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "Urlmon.lib")
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "rpcrt4.lib")
// Enable additional literals
using namespace std::literals;

View File

@ -131,14 +131,16 @@ namespace Utils
return EncodeBase64(input.c_str(), input.size());
}
// Decodes a given string from Base64
std::string DecodeBase64(const std::string& input) {
unsigned char* out = new unsigned char[input.size()];
unsigned long outlen = input.size();
base64_decode((unsigned char*)input.c_str(), input.size(), out, &outlen);
std::string ret((char*)out, outlen);
delete[] out;
return ret;
// Generates a UUID and returns the string representation of it
std::string GenerateUUIDString() {
// Generate UUID data
UUID uuid;
UuidCreate(&uuid);
// Convert to string representation
char* strdata = NULL;
UuidToStringA(&uuid, (RPC_CSTR*)&strdata);
return std::string(strdata);
}
}
}

View File

@ -42,6 +42,7 @@ namespace Utils
std::string EncodeBase64(const char* input, const unsigned long inputSize);
std::string EncodeBase64(const std::string& input);
std::string DecodeBase64(const std::string& input);
std::string GenerateUUIDString();
}
}

View File

@ -22,4 +22,17 @@ namespace Utils
if (pos == std::string::npos) return data;
return data.substr(0, pos).data();
}
void OutputDebugLastError()
{
DWORD errorMessageID = ::GetLastError();
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
std::string message(messageBuffer, size);
OutputDebugStringA(Utils::String::VA("Last error code: 0x%08X (%s)\n", errorMessageID, message));
LocalFree(messageBuffer);
}
}

View File

@ -2,6 +2,7 @@ namespace Utils
{
std::string GetMimeType(std::string url);
std::string ParseChallenge(std::string data);
void OutputDebugLastError();
template <typename T> void Merge(std::vector<T>* target, T* source, size_t length)
{

View File

@ -134,21 +134,30 @@ namespace Utils
return body;
}
std::string WebIO::PostFile(std::string url, std::string data)
std::string WebIO::PostFile(std::string url, std::string data, std::string fieldName, std::string fileName)
{
WebIO::SetURL(url);
return WebIO::PostFile(data);
return WebIO::PostFile(data, fieldName, fileName);
}
std::string WebIO::PostFile(std::string data)
std::string WebIO::PostFile(std::string data, std::string fieldName, std::string fileName)
{
WebIO::Params headers;
std::string boundary = "----WebKitFormBoundaryHoLVocRsBxs71fU6";
headers["Content-Type"] = "multipart/form-data, boundary=" + boundary;
Utils::String::Replace(fieldName, "\"", "\\\"");
Utils::String::Replace(fieldName, "\\", "\\\\");
Utils::String::Replace(fileName, "\"", "\\\"");
Utils::String::Replace(fileName, "\\", "\\\\");
std::string body = "--" + boundary + "\r\n";
body += "Content-Disposition: form-data; name=\"files[]\"; filename=\"minidump.dmp\"\r\n";
body += "Content-Disposition: form-data; name=\"";
body += fieldName;
body += "\"; filename=\"";
body += fileName;
body += "\"\r\n";
body += "Content-Type: application/octet-stream\r\n\r\n";
body += data + "\r\n";
body += "--" + boundary + "--\r\n";

View File

@ -26,8 +26,8 @@ namespace Utils
void SetURL(std::string url);
void SetCredentials(std::string username, std::string password);
std::string PostFile(std::string url, std::string data);
std::string PostFile(std::string data);
std::string PostFile(std::string url, std::string data, std::string fieldName, std::string fileName);
std::string PostFile(std::string data, std::string fieldName, std::string fileName);
std::string Post(std::string url, WebIO::Params params);
std::string Post(std::string url, std::string body);