// SPDX-License-Identifier: GPL-3.0-or-later #include "common.h" #include #include #include #include #include #include #include namespace start { fs::path Path(std::string_view utf8) { return fs::path(Wide(utf8)); } std::string Utf8(const fs::path& path) { return Utf8(std::wstring_view(path.native())); } std::string Utf8(std::wstring_view text) { if (text.empty()) return {}; const int n = WideCharToMultiByte(CP_UTF8, 0, text.data(), int(text.size()), nullptr, 0, nullptr, nullptr); std::string out(std::size_t(n), '\0'); WideCharToMultiByte(CP_UTF8, 0, text.data(), int(text.size()), out.data(), n, nullptr, nullptr); return out; } std::wstring Wide(std::string_view utf8) { if (utf8.empty()) return {}; const int n = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), int(utf8.size()), nullptr, 0); std::wstring out(std::size_t(n), L'\0'); MultiByteToWideChar(CP_UTF8, 0, utf8.data(), int(utf8.size()), out.data(), n); return out; } Bytes ReadFile(const fs::path& path) { std::ifstream in(path, std::ios::binary); if (!in) throw Error("cannot read " + Utf8(path)); in.seekg(0, std::ios::end); const auto size = std::streamoff(in.tellg()); in.seekg(0); Bytes data(std::size_t(std::max(size, 0))); if (!data.empty() && !in.read(reinterpret_cast(data.data()), std::streamsize(data.size()))) throw Error("cannot read " + Utf8(path)); return data; } std::string ReadText(const fs::path& path) { const Bytes data = ReadFile(path); std::string text(data.begin(), data.end()); if (StartsWith(text, "\xEF\xBB\xBF")) text.erase(0, 3); return text; } void WriteFile(const fs::path& path, const void* data, std::size_t size) { if (path.has_parent_path()) fs::create_directories(path.parent_path()); fs::path temporary = path; temporary += L".tmp" + std::to_wstring(GetCurrentProcessId()); { std::ofstream out(temporary, std::ios::binary | std::ios::trunc); if (!out || (size && !out.write(static_cast(data), std::streamsize(size)))) throw Error("cannot write " + Utf8(path)); } if (!MoveFileExW(temporary.c_str(), path.c_str(), MOVEFILE_REPLACE_EXISTING)) { DeleteFileW(temporary.c_str()); throw Error("cannot write " + Utf8(path)); } } namespace { Bytes Hash(LPCWSTR id, const void* data, std::size_t size) { BCRYPT_ALG_HANDLE algorithm = nullptr; BCRYPT_HASH_HANDLE hash = nullptr; DWORD length = 0, got = 0; if (BCryptOpenAlgorithmProvider(&algorithm, id, nullptr, 0) < 0) throw Error("no hash provider"); BCryptGetProperty(algorithm, BCRYPT_HASH_LENGTH, reinterpret_cast(&length), sizeof length, &got, 0); Bytes digest(length); BCryptCreateHash(algorithm, &hash, nullptr, 0, nullptr, 0, 0); const auto* p = static_cast(data); while (size) { const ULONG chunk = ULONG(std::min(size, 1u << 30)); BCryptHashData(hash, const_cast(p), chunk, 0); p += chunk; size -= chunk; } BCryptFinishHash(hash, digest.data(), length, 0); BCryptDestroyHash(hash); BCryptCloseAlgorithmProvider(algorithm, 0); return digest; } } std::string Sha256Hex(const void* data, std::size_t size) { const Bytes digest = Hash(BCRYPT_SHA256_ALGORITHM, data, size); return ToHex(digest.data(), digest.size()); } Bytes Sha1(const void* data, std::size_t size) { return Hash(BCRYPT_SHA1_ALGORITHM, data, size); } Sha256Stream::Sha256Stream() { BCryptOpenAlgorithmProvider(reinterpret_cast(&algorithm_), BCRYPT_SHA256_ALGORITHM, nullptr, 0); BCryptCreateHash(algorithm_, reinterpret_cast(&hash_), nullptr, 0, nullptr, 0, 0); } Sha256Stream::~Sha256Stream() { if (hash_) BCryptDestroyHash(hash_); if (algorithm_) BCryptCloseAlgorithmProvider(algorithm_, 0); } void Sha256Stream::Update(const void* data, std::size_t size) { const auto* p = static_cast(data); while (size) { const ULONG chunk = ULONG(std::min(size, 1u << 30)); BCryptHashData(hash_, const_cast(p), chunk, 0); p += chunk; size -= chunk; } } std::string Sha256Stream::HexDigest() { std::uint8_t digest[32]; BCryptFinishHash(hash_, digest, sizeof digest, 0); return ToHex(digest, sizeof digest); } std::uint32_t Crc32(const void* data, std::size_t size) { return std::uint32_t(crc32(0, static_cast(data), uInt(size))); } std::optional Env(const char* name) { const std::wstring key = Wide(name); const DWORD n = GetEnvironmentVariableW(key.c_str(), nullptr, 0); if (!n) return std::nullopt; std::wstring value(n, L'\0'); const DWORD got = GetEnvironmentVariableW(key.c_str(), value.data(), n); value.resize(got); return Utf8(value); } std::string Env(const char* name, std::string_view fallback) { const auto value = Env(name); return value ? *value : std::string(fallback); } std::uint64_t CommitLimitMb() { MEMORYSTATUSEX status{}; status.dwLength = sizeof(status); return GlobalMemoryStatusEx(&status) ? status.ullTotalPageFile >> 20 : 0; } namespace { struct PoolAnswer { unsigned long error = 0; HANDLE section = nullptr; std::uint64_t bytes = 0; }; std::map& PoolAnswers() { static std::map answers; return answers; } } unsigned long PoolRefused(std::uint64_t mib) { auto& answers = PoolAnswers(); if (const auto it = answers.find(mib); it != answers.end()) return it->second.error; const std::uint64_t size = (mib + 1024) << 20; SECURITY_ATTRIBUTES inherit{sizeof inherit, nullptr, TRUE}; const bool lazy = Env("BB_POOL_LAZY", "") == "1"; HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, &inherit, PAGE_EXECUTE_READWRITE | (lazy ? SEC_RESERVE : SEC_COMMIT), DWORD(size >> 32), DWORD(size), nullptr); PoolAnswer& answer = answers[mib]; answer.error = section ? 0 : GetLastError(); answer.section = section; answer.bytes = size; return answer.error; } void PoolHandOff(std::uint64_t mib) { UnsetEnv("BB_POOL_SECTION"); for (auto& [size, answer] : PoolAnswers()) { if (!answer.section) continue; if (size == mib) { char text[64]; std::snprintf(text, sizeof text, "%llu:%llu", (unsigned long long)(std::uintptr_t)answer.section, (unsigned long long)answer.bytes); SetEnv("BB_POOL_SECTION", text); } else { CloseHandle(answer.section); answer.section = nullptr; } } } void SetEnv(const char* name, std::string_view value) { SetEnvironmentVariableW(Wide(name).c_str(), Wide(value).c_str()); } void UnsetEnv(const char* name) { SetEnvironmentVariableW(Wide(name).c_str(), nullptr); } std::map ReadSettings(const fs::path& path) { std::map settings; std::error_code error; if (!fs::is_regular_file(path, error)) return settings; for (const std::string& line : Split(ReadText(path), '\n')) { const std::string text = Trim(line); if (text.empty() || text[0] == '#' || text[0] == ';') continue; const auto equals = text.find('='); if (equals == std::string::npos) continue; settings[Trim(text.substr(0, equals))] = Trim(text.substr(equals + 1)); } return settings; } std::optional Setting(const std::map& settings, const std::string& key) { const auto it = settings.find(key); return it == settings.end() ? std::nullopt : std::optional(it->second); } namespace { HANDLE g_log = nullptr; } void Log(const char* format, ...) { char stack[2048]; va_list args; va_start(args, format); const int n = std::vsnprintf(stack, sizeof stack, format, args); va_end(args); std::string text; if (n >= int(sizeof stack)) { text.resize(std::size_t(n) + 1); va_start(args, format); std::vsnprintf(text.data(), text.size(), format, args); va_end(args); text.resize(std::size_t(n)); } else if (n > 0) { text.assign(stack, std::size_t(n)); } text += '\n'; if (g_log) { DWORD written = 0; ::WriteFile(g_log, text.data(), DWORD(text.size()), &written, nullptr); } else { std::fwrite(text.data(), 1, text.size(), stdout); std::fflush(stdout); } } void InitLog() { const auto path = Env("BB_LOG_FILE"); if (!path || path->empty()) return; const fs::path file = Path(*path); std::error_code error; fs::create_directories(file.parent_path(), error); SECURITY_ATTRIBUTES inherit{sizeof inherit, nullptr, TRUE}; g_log = CreateFileW(file.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &inherit, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (g_log == INVALID_HANDLE_VALUE) { g_log = nullptr; return; } SYSTEMTIME now; GetLocalTime(&now); Log("%s", std::string(80, '=').c_str()); Log("Bloodborne PC session log started: %04u-%02u-%02uT%02u:%02u:%02u", now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond); Log("Log file: %s", Utf8(fs::absolute(file, error)).c_str()); Log("%s", std::string(80, '=').c_str()); } void* LogHandle() { return g_log; } std::string Hex(std::uint64_t value) { char text[24]; std::snprintf(text, sizeof text, "%#llx", static_cast(value)); return value ? text : "0x0"; } std::string Lower(std::string text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return char(std::tolower(c)); }); return text; } std::string Trim(std::string_view text) { const auto space = [](char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\v' || c == '\f'; }; while (!text.empty() && space(text.front())) text.remove_prefix(1); while (!text.empty() && space(text.back())) text.remove_suffix(1); return std::string(text); } std::vector Split(std::string_view text, char separator) { std::vector parts; std::size_t start = 0; while (true) { const auto at = text.find(separator, start); parts.emplace_back(text.substr(start, at == std::string_view::npos ? std::string_view::npos : at - start)); if (at == std::string_view::npos) break; start = at + 1; } return parts; } bool StartsWith(std::string_view text, std::string_view prefix) { return text.substr(0, prefix.size()) == prefix; } Bytes FromHex(std::string_view hex) { Bytes out; int high = -1; for (char c : hex) { int v; if (c >= '0' && c <= '9') v = c - '0'; else if (c >= 'a' && c <= 'f') v = c - 'a' + 10; else if (c >= 'A' && c <= 'F') v = c - 'A' + 10; else if (c == ' ') continue; else throw Error("bad hex digit"); if (high < 0) { high = v; } else { out.push_back(std::uint8_t(high << 4 | v)); high = -1; } } if (high >= 0) throw Error("odd hex length"); return out; } std::string ToHex(const std::uint8_t* data, std::size_t size) { static const char digits[] = "0123456789abcdef"; std::string out(size * 2, '0'); for (std::size_t i = 0; i < size; ++i) { out[2 * i] = digits[data[i] >> 4]; out[2 * i + 1] = digits[data[i] & 15]; } return out; } }