Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 0 additions & 102 deletions loader/hash/hash.cpp

This file was deleted.

22 changes: 13 additions & 9 deletions loader/hash/hash.hpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
#pragma once

#include <Geode/utils/hash.hpp>
#include <Geode/utils/file.hpp>
#include <Geode/utils/StringBuffer.hpp>
#include <asp/iter.hpp>
#include <string>
#include <filesystem>
#include <span>

std::string calculateSHA256(std::filesystem::path const& path);
inline geode::Sha256 sha256Text(std::filesystem::path const& path) {
std::string input = geode::utils::file::readString(path).unwrapOrDefault();
geode::utils::StringBuffer<> buf;

std::string calculateSHA256Text(std::filesystem::path const& path);
// remove all newlines
for (auto line : asp::iter::lines(input)) {
buf.append(line);
}

/**
* Calculates the SHA256 hash of the given data,
* used for verifying mods.
*/
std::string calculateHash(std::span<const uint8_t> data);
std::string calculateHash(std::string_view data);
return geode::sha256(buf.view());
}
75 changes: 75 additions & 0 deletions loader/include/Geode/utils/hash.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#pragma once
#include <Geode/platform/platform.hpp>
#include <Geode/Result.hpp>
#include <utility>
#include <typeinfo>
#include <string_view>
#include <filesystem>
#include <string>
#include <span>
#include <array>

namespace geode {

Expand All @@ -18,4 +24,73 @@ inline size_t typenameHash() {
return hasher(typeid(T).name());
}

template <size_t N>
struct Hash {
std::array<uint8_t, N> data;

bool operator==(Hash const& other) const noexcept = default;
bool operator!=(Hash const& other) const noexcept = default;
bool operator<(Hash const& other) const noexcept = default;

/// Returns the hex-encoded string representation of the hash, e.g. "a3b4c5d6e7f8..."
std::string toString() const {
std::string str;
str.resize(N * 2);
this->hexEncode(str.data());
return str;
}

/// Writes hex-encoded hash into the given buffer, the buffer must be at least 2 * N bytes long
void hexEncode(void* buf) const {
uint8_t* dest = reinterpret_cast<uint8_t*>(buf);
constexpr char hexChars[] = "0123456789abcdef";

for (size_t i = 0; i < N; ++i) {
dest[2 * i] = hexChars[(data[i] >> 4) & 0x0F];
dest[2 * i + 1] = hexChars[data[i] & 0x0F];
}
}
};

using Sha256 = Hash<32>;

/// A class for incrementally calculating the SHA256 hash of some data.
/// Call `update` to advance the state of the hasher with new data, then call `finish` to get the final digest.
class GEODE_DLL Sha256Hasher {
public:
Sha256Hasher();
~Sha256Hasher();

Sha256Hasher(Sha256Hasher const&) = delete;
Sha256Hasher& operator=(Sha256Hasher const&) = delete;
Sha256Hasher(Sha256Hasher&&) noexcept;
Sha256Hasher& operator=(Sha256Hasher&&) noexcept;

void update(std::span<uint8_t const> data);
void update(void const* data, size_t bytes);
void update(std::string_view data);

Sha256 finish() const;

/// Resets the internal state of the hash. This allows you to treat the hasher like a new instance,
/// without having to actually create a new object and allocate memory.
void reset();

private:
void* m_state = nullptr;
};

inline auto format_as(Sha256 const& hash) -> std::string {
return hash.toString();
}

/// Reads the binary file at the given path and calculates the SHA256 digest of the contents.
/// This reads the file lazily and uses C++ iostreams. If the performance of that is unacceptable, use `Sha256Hasher` directly.
Result<Sha256> sha256File(std::filesystem::path const& path);

/// Calculates the SHA256 digest of the given data.
Sha256 sha256(std::span<uint8_t const> data);
/// Calculates the SHA256 digest of the given data.
Sha256 sha256(std::string_view data);

}
2 changes: 1 addition & 1 deletion loader/src/loader/updater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ bool updater::verifyLoaderResources() {
}
// verify hash
// if we hash anything other than text, change this
auto hash = calculateSHA256Text(file.path());
auto hash = sha256Text(file.path()).toString();
const auto& expected = LOADER_RESOURCE_HASHES.at(name);
if (hash != expected) {
log::debug("Resource hash mismatch: {} ({}, {})", name, hash.substr(0, 7), expected.substr(0, 7));
Expand Down
2 changes: 1 addition & 1 deletion loader/src/server/DownloadManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class ModDownload::Impl final {
return;
}

auto actualHash = ::calculateHash(response.data());
auto actualHash = geode::sha256(response.data()).toString();
if (actualHash != version.hash) {
log::error("Failed to download {}, hash mismatch ({} != {})", m_id, actualHash, version.hash);
m_status = DownloadStatusError {
Expand Down
91 changes: 91 additions & 0 deletions loader/src/utils/hash.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include <Geode/utils/hash.hpp>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <Geode/loader/Log.hpp>
#include <Geode/utils/terminate.hpp>
#include <Geode/utils/general.hpp>
#include <Geode/utils/file.hpp>

namespace geode {

Sha256Hasher::Sha256Hasher() {
// In theory, this should never fail unless out of memory, in which case there's little we can do anyway
m_state = EVP_MD_CTX_new();
if (!m_state) {
utils::terminate("EVP_MD_CTX_new failed");
}

this->reset();
}

void Sha256Hasher::reset() {
EVP_DigestInit_ex(static_cast<EVP_MD_CTX*>(m_state), EVP_sha256(), nullptr);
}

Sha256Hasher::~Sha256Hasher() {
EVP_MD_CTX_free(static_cast<EVP_MD_CTX*>(m_state));
}

Sha256Hasher::Sha256Hasher(Sha256Hasher&& other) noexcept {
*this = std::move(other);
}

Sha256Hasher& Sha256Hasher::operator=(Sha256Hasher&& other) noexcept {
if (this != &other) {
EVP_MD_CTX_free(static_cast<EVP_MD_CTX*>(m_state));
m_state = other.m_state;
other.m_state = nullptr;
}
return *this;
}

void Sha256Hasher::update(std::span<uint8_t const> data) {
update(data.data(), data.size());
}

void Sha256Hasher::update(void const* data, size_t bytes) {
EVP_DigestUpdate(static_cast<EVP_MD_CTX*>(m_state), data, bytes);
}

void Sha256Hasher::update(std::string_view data) {
update(data.data(), data.size());
}

Sha256 Sha256Hasher::finish() const {
Sha256 hash;
EVP_DigestFinal_ex(static_cast<EVP_MD_CTX*>(m_state), hash.data.data(), nullptr);
return hash;
}

Result<Sha256> sha256File(std::filesystem::path const& path) {
Sha256Hasher hasher;

std::ifstream file(path, std::ios::binary);
if (!file.is_open()) {
return Err("Failed to open file '{}' for reading", path);
}

uint8_t buf[4096];
while (file.good()) {
file.read(reinterpret_cast<char*>(buf), sizeof(buf));
auto read = file.gcount();

if (read > 0) {
hasher.update(buf, read);
}
}

return Ok(hasher.finish());
}

Sha256 sha256(std::span<uint8_t const> data) {
Sha256Hasher hasher;
hasher.update(data);
return hasher.finish();
}

Sha256 sha256(std::string_view data) {
return sha256(std::span{reinterpret_cast<const uint8_t*>(data.data()), data.size()});
}

}
Loading