mirror of
https://github.com/geode-sdk/geode.git
synced 2025-04-24 05:14:40 -04:00
automatic resource repair
- loader checks if resources are correct at startup, if not, downloads them and replaces - add sha256 to hash - change hash to use paths instead of strings - cmake rework; GeodeFile.cmake now checks CLI version - add optional `DONT_INSTALL` argument to `create_geode_file` - test mods are now not installed by default - add package_geode_resources_now command for packaging resources at configure time and creating a header with their calculated hashes
This commit is contained in:
parent
c210ab2f33
commit
f9cda74b58
16 changed files with 728 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -43,6 +43,7 @@ build
|
|||
bin
|
||||
|
||||
loader/src/internal/about.hpp
|
||||
loader/src/internal/resources.hpp
|
||||
loader/resources/mod.json
|
||||
fods-catgirl-hideout.txt
|
||||
|
||||
|
|
|
@ -3865,6 +3865,7 @@ class LoadingLayer : cocos2d::CCLayer {
|
|||
m_fromRefresh = value;
|
||||
}
|
||||
|
||||
LoadingLayer() {}
|
||||
static LoadingLayer* create(bool fromReload) = mac 0x1df1f0, win 0x18bfe0, ios 0x130278;
|
||||
bool init(bool fromReload) = mac 0x1df2f0, win 0x18c080, ios 0x0;
|
||||
const char* getLoadingString() = mac 0x0, win 0x18cf40, ios 0x0;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
set(GEODE_CLI_MINIMUM_VERSION 1.0.5)
|
||||
|
||||
if (NOT DEFINED GEODE_CLI)
|
||||
find_program(GEODE_CLI NAMES geode.exe geode-cli.exe geode geode-cli)
|
||||
endif()
|
||||
|
@ -5,7 +7,30 @@ endif()
|
|||
if (GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(STATUS "Unable to find Geode CLI")
|
||||
else()
|
||||
message(STATUS "Found Geode CLI: ${GEODE_CLI}")
|
||||
if (NOT GEODE_DISABLE_CLI_CALLS)
|
||||
|
||||
# `geode --version` returns `geode x.x.x\n` so gotta do some wacky shit
|
||||
execute_process(
|
||||
COMMAND ${GEODE_CLI} --version
|
||||
OUTPUT_VARIABLE GEODE_CLI_VERSION
|
||||
)
|
||||
# Remove trailing newline
|
||||
string(STRIP ${GEODE_CLI_VERSION} GEODE_CLI_VERSION)
|
||||
# Remove program name
|
||||
string(REPLACE "geode " "" GEODE_CLI_VERSION ${GEODE_CLI_VERSION})
|
||||
|
||||
endif()
|
||||
|
||||
# Need at least v1.0.5 (--shut-up arg in geode package resources)
|
||||
if (${GEODE_CLI_VERSION} VERSION_LESS ${GEODE_CLI_MINIMUM_VERSION})
|
||||
message(FATAL_ERROR
|
||||
"Found Geode CLI: ${GEODE_CLI}, however it is version ${GEODE_CLI_VERSION} "
|
||||
"while minimum required is version ${GEODE_CLI_MINIMUM_VERSION}. Please update: "
|
||||
"https://github.com/geode-sdk/cli/releases/latest"
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "Found Geode CLI: ${GEODE_CLI} (version ${GEODE_CLI_VERSION})")
|
||||
endif()
|
||||
|
||||
function(create_geode_file_old proname)
|
||||
|
@ -36,6 +61,10 @@ function(create_geode_file_old proname)
|
|||
endfunction()
|
||||
|
||||
function(create_geode_file proname)
|
||||
# Get DONT_INSTALL argument
|
||||
set(options DONT_INSTALL)
|
||||
cmake_parse_arguments(CREATE_GEODE_FILE "${options}" "" "" ${ARGN})
|
||||
|
||||
if (GEODE_DISABLE_CLI_CALLS)
|
||||
message("Skipping creating geode file for ${proname}")
|
||||
return()
|
||||
|
@ -57,11 +86,17 @@ function(create_geode_file proname)
|
|||
OUTPUT_VARIABLE MOD_ID
|
||||
)
|
||||
|
||||
if (CREATE_GEODE_FILE_DONT_INSTALL)
|
||||
set(INSTALL_ARG "")
|
||||
else()
|
||||
set(INSTALL_ARG "--install")
|
||||
endif()
|
||||
|
||||
set_target_properties(${proname} PROPERTIES PREFIX "")
|
||||
set_target_properties(${proname} PROPERTIES OUTPUT_NAME ${MOD_ID})
|
||||
add_custom_target(${proname}_PACKAGE ALL
|
||||
DEPENDS ${proname} ${CMAKE_CURRENT_SOURCE_DIR}/mod.json
|
||||
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --binary $<TARGET_FILE:${proname}> --output $<TARGET_FILE_DIR:${proname}>/${proname}.geode --install
|
||||
COMMAND ${GEODE_CLI} package new ${CMAKE_CURRENT_SOURCE_DIR} --binary $<TARGET_FILE:${proname}> --output $<TARGET_FILE_DIR:${proname}>/${proname}.geode ${INSTALL_ARG}
|
||||
VERBATIM USES_TERMINAL
|
||||
)
|
||||
endfunction()
|
||||
|
@ -75,7 +110,10 @@ function(package_geode_resources proname src dest)
|
|||
message(STATUS "Packaging resources from ${src} into ${dest}")
|
||||
|
||||
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(WARNING "package_geode_resources called, but Geode CLI was not found - You will need to manually package the resources")
|
||||
message(WARNING
|
||||
"package_geode_resources called, but Geode CLI was "
|
||||
"not found - You will need to manually package the resources"
|
||||
)
|
||||
else()
|
||||
|
||||
add_custom_target(${proname}_PACKAGE ALL
|
||||
|
@ -83,5 +121,63 @@ function(package_geode_resources proname src dest)
|
|||
COMMAND ${GEODE_CLI} package resources ${src} ${dest}
|
||||
VERBATIM USES_TERMINAL
|
||||
)
|
||||
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(package_geode_resources_now proname src dest header_dest)
|
||||
if (GEODE_DISABLE_CLI_CALLS)
|
||||
message(FATAL_ERROR
|
||||
"package_geode_resources_now called, but GEODE_DISABLE_CLI_CALLS
|
||||
is set to true - This function requires CLI calls in order to work"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(GEODE_CLI STREQUAL "GEODE_CLI-NOTFOUND")
|
||||
message(FATAL_ERROR
|
||||
"package_geode_resources_now called, but Geode CLI "
|
||||
"was not found - Please install Geode CLI from "
|
||||
"https://github.com/geode-sdk/cli/releases/latest"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
message(STATUS "Packaging resources now from ${src} into ${dest}")
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GEODE_CLI} package resources ${src} ${dest} --shut-up
|
||||
RESULT_VARIABLE GEODE_PACKAGE_RES
|
||||
)
|
||||
|
||||
if (NOT GEODE_PACKAGE_RES EQUAL "0")
|
||||
message(FATAL_ERROR
|
||||
"Command \"${GEODE_CLI} package resources ${src} ${dest}\" returned "
|
||||
"${GEODE_PACKAGE_RES} - Expected 0"
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB RESOURCE_FILES "${dest}/*.*")
|
||||
|
||||
set(HEADER_FILE
|
||||
"#include <unordered_map>\n\n"
|
||||
"static const std::unordered_map<std::string, std::string> "
|
||||
"LOADER_RESOURCE_HASHES {\n"
|
||||
)
|
||||
|
||||
foreach(file ${RESOURCE_FILES})
|
||||
cmake_path(GET file FILENAME FILE_NAME)
|
||||
if (NOT FILE_NAME STREQUAL ".geode_cache")
|
||||
|
||||
file(SHA256 ${file} COMPUTED_HASH)
|
||||
list(APPEND HEADER_FILE "\t{ \"${FILE_NAME}\", \"${COMPUTED_HASH}\" },\n")
|
||||
|
||||
endif()
|
||||
|
||||
endforeach()
|
||||
|
||||
list(APPEND HEADER_FILE "}\;\n")
|
||||
|
||||
file(WRITE ${header_dest} ${HEADER_FILE})
|
||||
message(STATUS "Wrote resource hashes to ${header_dest}")
|
||||
|
||||
endfunction()
|
||||
|
|
|
@ -83,10 +83,11 @@ endif()
|
|||
|
||||
if (NOT GEODE_DISABLE_CLI_CALLS)
|
||||
# Package resources for UI
|
||||
package_geode_resources(
|
||||
package_geode_resources_now(
|
||||
${PROJECT_NAME}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${GEODE_BIN_PATH}/nightly/resources
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/internal/resources.hpp
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -6,4 +6,6 @@ project(GeodeChecksum VERSION 1.0)
|
|||
|
||||
add_executable(${PROJECT_NAME} hash.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC filesystem)
|
||||
|
||||
message(STATUS "Building Checksum Exe")
|
||||
|
|
|
@ -4,11 +4,24 @@
|
|||
#include <fstream>
|
||||
#include <ciso646>
|
||||
#include "picosha3.h"
|
||||
#include "picosha2.h"
|
||||
#include <vector>
|
||||
#include <fs/filesystem.hpp>
|
||||
|
||||
static std::string calculateHash(std::string const& path) {
|
||||
static std::string calculateSHA3_256(ghc::filesystem::path const& path) {
|
||||
std::vector<uint8_t> s(picosha3::bits_to_bytes(256));
|
||||
auto sha3_256 = picosha3::get_sha3_generator<256>();
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
return sha3_256.get_hex_string(file);
|
||||
}
|
||||
|
||||
static std::string calculateSHA256(ghc::filesystem::path const& path) {
|
||||
std::vector<uint8_t> hash(picosha2::k_digest_size);
|
||||
std::ifstream file(path, std::ios::binary);
|
||||
picosha2::hash256(file, hash.begin(), hash.end());
|
||||
return picosha2::bytes_to_hex_string(hash.begin(), hash.end());
|
||||
}
|
||||
|
||||
static std::string calculateHash(ghc::filesystem::path const& path) {
|
||||
return calculateSHA3_256(path);
|
||||
}
|
||||
|
|
377
loader/hash/picosha2.h
Normal file
377
loader/hash/picosha2.h
Normal file
|
@ -0,0 +1,377 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2017 okdshin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
#ifndef PICOSHA2_H
|
||||
#define PICOSHA2_H
|
||||
// picosha2:20140213
|
||||
|
||||
#ifndef PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR
|
||||
#define PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR \
|
||||
1048576 //=1024*1024: default is 1MB memory
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
namespace picosha2 {
|
||||
typedef unsigned long word_t;
|
||||
typedef unsigned char byte_t;
|
||||
|
||||
static const size_t k_digest_size = 32;
|
||||
|
||||
namespace detail {
|
||||
inline byte_t mask_8bit(byte_t x) { return x & 0xff; }
|
||||
|
||||
inline word_t mask_32bit(word_t x) { return x & 0xffffffff; }
|
||||
|
||||
const word_t add_constant[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2};
|
||||
|
||||
const word_t initial_message_digest[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372,
|
||||
0xa54ff53a, 0x510e527f, 0x9b05688c,
|
||||
0x1f83d9ab, 0x5be0cd19};
|
||||
|
||||
inline word_t ch(word_t x, word_t y, word_t z) { return (x & y) ^ ((~x) & z); }
|
||||
|
||||
inline word_t maj(word_t x, word_t y, word_t z) {
|
||||
return (x & y) ^ (x & z) ^ (y & z);
|
||||
}
|
||||
|
||||
inline word_t rotr(word_t x, std::size_t n) {
|
||||
assert(n < 32);
|
||||
return mask_32bit((x >> n) | (x << (32 - n)));
|
||||
}
|
||||
|
||||
inline word_t bsig0(word_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }
|
||||
|
||||
inline word_t bsig1(word_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }
|
||||
|
||||
inline word_t shr(word_t x, std::size_t n) {
|
||||
assert(n < 32);
|
||||
return x >> n;
|
||||
}
|
||||
|
||||
inline word_t ssig0(word_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ shr(x, 3); }
|
||||
|
||||
inline word_t ssig1(word_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ shr(x, 10); }
|
||||
|
||||
template <typename RaIter1, typename RaIter2>
|
||||
void hash256_block(RaIter1 message_digest, RaIter2 first, RaIter2 last) {
|
||||
assert(first + 64 == last);
|
||||
static_cast<void>(last); // for avoiding unused-variable warning
|
||||
word_t w[64];
|
||||
std::fill(w, w + 64, word_t(0));
|
||||
for (std::size_t i = 0; i < 16; ++i) {
|
||||
w[i] = (static_cast<word_t>(mask_8bit(*(first + i * 4))) << 24) |
|
||||
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 1))) << 16) |
|
||||
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 2))) << 8) |
|
||||
(static_cast<word_t>(mask_8bit(*(first + i * 4 + 3))));
|
||||
}
|
||||
for (std::size_t i = 16; i < 64; ++i) {
|
||||
w[i] = mask_32bit(ssig1(w[i - 2]) + w[i - 7] + ssig0(w[i - 15]) +
|
||||
w[i - 16]);
|
||||
}
|
||||
|
||||
word_t a = *message_digest;
|
||||
word_t b = *(message_digest + 1);
|
||||
word_t c = *(message_digest + 2);
|
||||
word_t d = *(message_digest + 3);
|
||||
word_t e = *(message_digest + 4);
|
||||
word_t f = *(message_digest + 5);
|
||||
word_t g = *(message_digest + 6);
|
||||
word_t h = *(message_digest + 7);
|
||||
|
||||
for (std::size_t i = 0; i < 64; ++i) {
|
||||
word_t temp1 = h + bsig1(e) + ch(e, f, g) + add_constant[i] + w[i];
|
||||
word_t temp2 = bsig0(a) + maj(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = mask_32bit(d + temp1);
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = mask_32bit(temp1 + temp2);
|
||||
}
|
||||
*message_digest += a;
|
||||
*(message_digest + 1) += b;
|
||||
*(message_digest + 2) += c;
|
||||
*(message_digest + 3) += d;
|
||||
*(message_digest + 4) += e;
|
||||
*(message_digest + 5) += f;
|
||||
*(message_digest + 6) += g;
|
||||
*(message_digest + 7) += h;
|
||||
for (std::size_t i = 0; i < 8; ++i) {
|
||||
*(message_digest + i) = mask_32bit(*(message_digest + i));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename InIter>
|
||||
void output_hex(InIter first, InIter last, std::ostream& os) {
|
||||
os.setf(std::ios::hex, std::ios::basefield);
|
||||
while (first != last) {
|
||||
os.width(2);
|
||||
os.fill('0');
|
||||
os << static_cast<unsigned int>(*first);
|
||||
++first;
|
||||
}
|
||||
os.setf(std::ios::dec, std::ios::basefield);
|
||||
}
|
||||
|
||||
template <typename InIter>
|
||||
void bytes_to_hex_string(InIter first, InIter last, std::string& hex_str) {
|
||||
std::ostringstream oss;
|
||||
output_hex(first, last, oss);
|
||||
hex_str.assign(oss.str());
|
||||
}
|
||||
|
||||
template <typename InContainer>
|
||||
void bytes_to_hex_string(const InContainer& bytes, std::string& hex_str) {
|
||||
bytes_to_hex_string(bytes.begin(), bytes.end(), hex_str);
|
||||
}
|
||||
|
||||
template <typename InIter>
|
||||
std::string bytes_to_hex_string(InIter first, InIter last) {
|
||||
std::string hex_str;
|
||||
bytes_to_hex_string(first, last, hex_str);
|
||||
return hex_str;
|
||||
}
|
||||
|
||||
template <typename InContainer>
|
||||
std::string bytes_to_hex_string(const InContainer& bytes) {
|
||||
std::string hex_str;
|
||||
bytes_to_hex_string(bytes, hex_str);
|
||||
return hex_str;
|
||||
}
|
||||
|
||||
class hash256_one_by_one {
|
||||
public:
|
||||
hash256_one_by_one() { init(); }
|
||||
|
||||
void init() {
|
||||
buffer_.clear();
|
||||
std::fill(data_length_digits_, data_length_digits_ + 4, word_t(0));
|
||||
std::copy(detail::initial_message_digest,
|
||||
detail::initial_message_digest + 8, h_);
|
||||
}
|
||||
|
||||
template <typename RaIter>
|
||||
void process(RaIter first, RaIter last) {
|
||||
add_to_data_length(static_cast<word_t>(std::distance(first, last)));
|
||||
std::copy(first, last, std::back_inserter(buffer_));
|
||||
std::size_t i = 0;
|
||||
for (; i + 64 <= buffer_.size(); i += 64) {
|
||||
detail::hash256_block(h_, buffer_.begin() + i,
|
||||
buffer_.begin() + i + 64);
|
||||
}
|
||||
buffer_.erase(buffer_.begin(), buffer_.begin() + i);
|
||||
}
|
||||
|
||||
void finish() {
|
||||
byte_t temp[64];
|
||||
std::fill(temp, temp + 64, byte_t(0));
|
||||
std::size_t remains = buffer_.size();
|
||||
std::copy(buffer_.begin(), buffer_.end(), temp);
|
||||
temp[remains] = 0x80;
|
||||
|
||||
if (remains > 55) {
|
||||
std::fill(temp + remains + 1, temp + 64, byte_t(0));
|
||||
detail::hash256_block(h_, temp, temp + 64);
|
||||
std::fill(temp, temp + 64 - 4, byte_t(0));
|
||||
} else {
|
||||
std::fill(temp + remains + 1, temp + 64 - 4, byte_t(0));
|
||||
}
|
||||
|
||||
write_data_bit_length(&(temp[56]));
|
||||
detail::hash256_block(h_, temp, temp + 64);
|
||||
}
|
||||
|
||||
template <typename OutIter>
|
||||
void get_hash_bytes(OutIter first, OutIter last) const {
|
||||
for (const word_t* iter = h_; iter != h_ + 8; ++iter) {
|
||||
for (std::size_t i = 0; i < 4 && first != last; ++i) {
|
||||
*(first++) = detail::mask_8bit(
|
||||
static_cast<byte_t>((*iter >> (24 - 8 * i))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void add_to_data_length(word_t n) {
|
||||
word_t carry = 0;
|
||||
data_length_digits_[0] += n;
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
data_length_digits_[i] += carry;
|
||||
if (data_length_digits_[i] >= 65536u) {
|
||||
carry = data_length_digits_[i] >> 16;
|
||||
data_length_digits_[i] &= 65535u;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void write_data_bit_length(byte_t* begin) {
|
||||
word_t data_bit_length_digits[4];
|
||||
std::copy(data_length_digits_, data_length_digits_ + 4,
|
||||
data_bit_length_digits);
|
||||
|
||||
// convert byte length to bit length (multiply 8 or shift 3 times left)
|
||||
word_t carry = 0;
|
||||
for (std::size_t i = 0; i < 4; ++i) {
|
||||
word_t before_val = data_bit_length_digits[i];
|
||||
data_bit_length_digits[i] <<= 3;
|
||||
data_bit_length_digits[i] |= carry;
|
||||
data_bit_length_digits[i] &= 65535u;
|
||||
carry = (before_val >> (16 - 3)) & 65535u;
|
||||
}
|
||||
|
||||
// write data_bit_length
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
(*begin++) = static_cast<byte_t>(data_bit_length_digits[i] >> 8);
|
||||
(*begin++) = static_cast<byte_t>(data_bit_length_digits[i]);
|
||||
}
|
||||
}
|
||||
std::vector<byte_t> buffer_;
|
||||
word_t data_length_digits_[4]; // as 64bit integer (16bit x 4 integer)
|
||||
word_t h_[8];
|
||||
};
|
||||
|
||||
inline void get_hash_hex_string(const hash256_one_by_one& hasher,
|
||||
std::string& hex_str) {
|
||||
byte_t hash[k_digest_size];
|
||||
hasher.get_hash_bytes(hash, hash + k_digest_size);
|
||||
return bytes_to_hex_string(hash, hash + k_digest_size, hex_str);
|
||||
}
|
||||
|
||||
inline std::string get_hash_hex_string(const hash256_one_by_one& hasher) {
|
||||
std::string hex_str;
|
||||
get_hash_hex_string(hasher, hex_str);
|
||||
return hex_str;
|
||||
}
|
||||
|
||||
namespace impl {
|
||||
template <typename RaIter, typename OutIter>
|
||||
void hash256_impl(RaIter first, RaIter last, OutIter first2, OutIter last2, int,
|
||||
std::random_access_iterator_tag) {
|
||||
hash256_one_by_one hasher;
|
||||
// hasher.init();
|
||||
hasher.process(first, last);
|
||||
hasher.finish();
|
||||
hasher.get_hash_bytes(first2, last2);
|
||||
}
|
||||
|
||||
template <typename InputIter, typename OutIter>
|
||||
void hash256_impl(InputIter first, InputIter last, OutIter first2,
|
||||
OutIter last2, int buffer_size, std::input_iterator_tag) {
|
||||
std::vector<byte_t> buffer(buffer_size);
|
||||
hash256_one_by_one hasher;
|
||||
// hasher.init();
|
||||
while (first != last) {
|
||||
int size = buffer_size;
|
||||
for (int i = 0; i != buffer_size; ++i, ++first) {
|
||||
if (first == last) {
|
||||
size = i;
|
||||
break;
|
||||
}
|
||||
buffer[i] = *first;
|
||||
}
|
||||
hasher.process(buffer.begin(), buffer.begin() + size);
|
||||
}
|
||||
hasher.finish();
|
||||
hasher.get_hash_bytes(first2, last2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InIter, typename OutIter>
|
||||
void hash256(InIter first, InIter last, OutIter first2, OutIter last2,
|
||||
int buffer_size = PICOSHA2_BUFFER_SIZE_FOR_INPUT_ITERATOR) {
|
||||
picosha2::impl::hash256_impl(
|
||||
first, last, first2, last2, buffer_size,
|
||||
typename std::iterator_traits<InIter>::iterator_category());
|
||||
}
|
||||
|
||||
template <typename InIter, typename OutContainer>
|
||||
void hash256(InIter first, InIter last, OutContainer& dst) {
|
||||
hash256(first, last, dst.begin(), dst.end());
|
||||
}
|
||||
|
||||
template <typename InContainer, typename OutIter>
|
||||
void hash256(const InContainer& src, OutIter first, OutIter last) {
|
||||
hash256(src.begin(), src.end(), first, last);
|
||||
}
|
||||
|
||||
template <typename InContainer, typename OutContainer>
|
||||
void hash256(const InContainer& src, OutContainer& dst) {
|
||||
hash256(src.begin(), src.end(), dst.begin(), dst.end());
|
||||
}
|
||||
|
||||
template <typename InIter>
|
||||
void hash256_hex_string(InIter first, InIter last, std::string& hex_str) {
|
||||
byte_t hashed[k_digest_size];
|
||||
hash256(first, last, hashed, hashed + k_digest_size);
|
||||
std::ostringstream oss;
|
||||
output_hex(hashed, hashed + k_digest_size, oss);
|
||||
hex_str.assign(oss.str());
|
||||
}
|
||||
|
||||
template <typename InIter>
|
||||
std::string hash256_hex_string(InIter first, InIter last) {
|
||||
std::string hex_str;
|
||||
hash256_hex_string(first, last, hex_str);
|
||||
return hex_str;
|
||||
}
|
||||
|
||||
inline void hash256_hex_string(const std::string& src, std::string& hex_str) {
|
||||
hash256_hex_string(src.begin(), src.end(), hex_str);
|
||||
}
|
||||
|
||||
template <typename InContainer>
|
||||
void hash256_hex_string(const InContainer& src, std::string& hex_str) {
|
||||
hash256_hex_string(src.begin(), src.end(), hex_str);
|
||||
}
|
||||
|
||||
template <typename InContainer>
|
||||
std::string hash256_hex_string(const InContainer& src) {
|
||||
return hash256_hex_string(src.begin(), src.end());
|
||||
}
|
||||
template<typename OutIter>void hash256(std::ifstream& f, OutIter first, OutIter last){
|
||||
hash256(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>(), first,last);
|
||||
|
||||
}
|
||||
}// namespace picosha2
|
||||
#endif // PICOSHA2_H
|
|
@ -147,6 +147,13 @@ namespace geode::utils::web {
|
|||
friend class AsyncWebResponse;
|
||||
|
||||
public:
|
||||
/**
|
||||
* An asynchronous, thread-safe web request. Downloads data from the
|
||||
* internet without slowing the main thread. All callbacks are run in the
|
||||
* GD thread, so interacting with the Cocos2d UI is perfectly safe
|
||||
*/
|
||||
AsyncWebRequest() = default;
|
||||
|
||||
/**
|
||||
* If you only want one instance of this web request to run (for example,
|
||||
* you're downloading some global data for a manager), then use this
|
||||
|
|
|
@ -11,8 +11,6 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
|||
add_subdirectory("test")
|
||||
endif()
|
||||
|
||||
message(STATUS ${GEODE_LOADER_PATH}/include)
|
||||
|
||||
target_include_directories(
|
||||
lilac_hook INTERFACE
|
||||
${lilac_SOURCE_DIR}/include/geode
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
#include <Geode/modify/LoadingLayer.hpp>
|
||||
#include <array>
|
||||
#include <InternalLoader.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#include <Geode/modify/LoadingLayer.hpp>
|
||||
class $modify(CustomLoadingLayer, LoadingLayer) {
|
||||
CCScale9Sprite* m_updatingResourcesBG;
|
||||
CCLabelBMFont* m_updatingResources;
|
||||
|
||||
CustomLoadingLayer() : m_updatingResources(nullptr) {}
|
||||
|
||||
bool init(bool fromReload) {
|
||||
if (!LoadingLayer::init(fromReload))
|
||||
return false;
|
||||
|
@ -21,9 +27,100 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
|
|||
);
|
||||
label->setPosition(winSize.width / 2, 30.f);
|
||||
label->setScale(.45f);
|
||||
label->setTag(5);
|
||||
label->setID("geode-loaded-info");
|
||||
this->addChild(label);
|
||||
|
||||
// verify loader resources
|
||||
if (!InternalLoader::get()->verifyLoaderResources(
|
||||
std::bind(
|
||||
&CustomLoadingLayer::updateResourcesProgress, this,
|
||||
std::placeholders::_1, std::placeholders::_2,
|
||||
std::placeholders::_3
|
||||
)
|
||||
)) {
|
||||
auto bg = CCScale9Sprite::create(
|
||||
"square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
);
|
||||
bg->setScale(.6f);
|
||||
bg->setColor({ 0, 0, 0 });
|
||||
bg->setOpacity(150);
|
||||
bg->setPosition(winSize / 2);
|
||||
this->addChild(bg);
|
||||
|
||||
m_fields->m_updatingResourcesBG = bg;
|
||||
|
||||
auto label = CCLabelBMFont::create("", "goldFont.fnt");
|
||||
label->setScale(1.1f);
|
||||
bg->addChild(label);
|
||||
|
||||
m_fields->m_updatingResources = label;
|
||||
|
||||
this->setUpdateText("Downloading Resources");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void setUpdateText(std::string const& text) {
|
||||
m_fields->m_updatingResources->setString(text.c_str());
|
||||
m_fields->m_updatingResourcesBG->setContentSize({
|
||||
m_fields->m_updatingResources->getScaledContentSize().width + 30.f,
|
||||
50.f
|
||||
});
|
||||
m_fields->m_updatingResources->setPosition(
|
||||
m_fields->m_updatingResourcesBG->getContentSize() / 2
|
||||
);
|
||||
}
|
||||
|
||||
void updateResourcesProgress(
|
||||
UpdateStatus status,
|
||||
std::string const& info,
|
||||
uint8_t progress
|
||||
) {
|
||||
switch (status) {
|
||||
case UpdateStatus::Progress: {
|
||||
this->setUpdateText(
|
||||
"Downloading Resources: " +
|
||||
std::to_string(progress) + "%"
|
||||
);
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Finished: {
|
||||
this->setUpdateText("Resources Downloaded");
|
||||
m_fields->m_updatingResourcesBG->runAction(CCSequence::create(
|
||||
CCDelayTime::create(1.f),
|
||||
CCRemoveSelf::create(),
|
||||
nullptr
|
||||
));
|
||||
m_fields->m_updatingResourcesBG = nullptr;
|
||||
m_fields->m_updatingResources = nullptr;
|
||||
this->loadAssets();
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Failed: {
|
||||
InternalLoader::platformMessageBox(
|
||||
"Error updating resources",
|
||||
"Unable to update Geode resources: " + info + ".\n"
|
||||
"The game will be loaded as normal, but please be aware "
|
||||
"that it may very likely crash."
|
||||
);
|
||||
this->setUpdateText("Resource Download Failed");
|
||||
m_fields->m_updatingResourcesBG->runAction(CCSequence::create(
|
||||
CCDelayTime::create(1.f),
|
||||
CCRemoveSelf::create(),
|
||||
nullptr
|
||||
));
|
||||
m_fields->m_updatingResourcesBG = nullptr;
|
||||
m_fields->m_updatingResources = nullptr;
|
||||
this->loadAssets();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void loadAssets() {
|
||||
if (m_fields->m_updatingResources) {
|
||||
return;
|
||||
}
|
||||
LoadingLayer::loadAssets();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -664,7 +664,7 @@ InstallItems::CallbackID InstallItems::start(
|
|||
|
||||
// verify checksum
|
||||
this->progress("Verifying", 100);
|
||||
if (::calculateHash(tempFile.string()) != item.m_download.m_hash) {
|
||||
if (::calculateHash(tempFile) != item.m_download.m_hash) {
|
||||
try { ghc::filesystem::remove(tempFile); } catch(...) {}
|
||||
return this->error(
|
||||
"Checksum mismatch! (Downloaded file did not match what "
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/utils/fetch.hpp>
|
||||
#include <thread>
|
||||
#include "resources.hpp"
|
||||
#include <hash.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
|
||||
InternalLoader::InternalLoader() : Loader() {}
|
||||
|
||||
|
@ -83,6 +86,121 @@ void InternalLoader::loadInfoAlerts(nlohmann::json& json) {
|
|||
m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
|
||||
}
|
||||
|
||||
void InternalLoader::downloadLoaderResources(IndexUpdateCallback callback) {
|
||||
auto version = this->getVersion().toString();
|
||||
auto tempResourcesZip = this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY / "new.zip";
|
||||
auto resourcesDir = this->getGeodeDirectory() /
|
||||
GEODE_RESOURCE_DIRECTORY / InternalMod::get()->getID();
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join("update-geode-loader-resources")
|
||||
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + version)
|
||||
.json()
|
||||
.then([tempResourcesZip, resourcesDir, callback](nlohmann::json const& json) {
|
||||
auto checker = JsonChecker(json);
|
||||
auto root = checker.root("[matching geode release]").obj();
|
||||
|
||||
// find resources.zip and download it
|
||||
for (auto asset : root.needs("assets").iterate()) {
|
||||
auto obj = asset.obj();
|
||||
if (obj.needs("name").get<std::string>() == "resources.zip") {
|
||||
auto url = obj.needs("browser_download_url").get<std::string>();
|
||||
if (url.size()) {
|
||||
web::AsyncWebRequest()
|
||||
.fetch(url)
|
||||
.into(tempResourcesZip)
|
||||
.then([tempResourcesZip, resourcesDir, callback](auto) {
|
||||
// unzip resources zip
|
||||
auto unzip = file::unzipTo(tempResourcesZip, resourcesDir);
|
||||
if (!unzip) {
|
||||
if (callback) callback(
|
||||
UpdateStatus::Failed,
|
||||
"Unable to unzip new resources: " + unzip.error(),
|
||||
0
|
||||
);
|
||||
return;
|
||||
}
|
||||
// delete resources zip
|
||||
try {
|
||||
ghc::filesystem::remove(tempResourcesZip);
|
||||
} catch(...) {}
|
||||
|
||||
if (callback) callback(
|
||||
UpdateStatus::Finished,
|
||||
"Resources updated",
|
||||
100
|
||||
);
|
||||
})
|
||||
.expect([callback](std::string const& info) {
|
||||
if (callback) callback(UpdateStatus::Failed, info, 0);
|
||||
})
|
||||
.progress([callback](auto&, double now, double total) {
|
||||
if (callback) callback(
|
||||
UpdateStatus::Progress,
|
||||
"Downloading resources",
|
||||
static_cast<uint8_t>(now / total * 100.0)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (checker.isError()) {
|
||||
if (callback) callback(UpdateStatus::Failed, checker.getError(), 0);
|
||||
}
|
||||
})
|
||||
.expect([callback](std::string const& info) {
|
||||
if (callback) callback(UpdateStatus::Failed, info, 0);
|
||||
});
|
||||
}
|
||||
|
||||
bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
|
||||
static std::optional<bool> CACHED = std::nullopt;
|
||||
if (CACHED.has_value()) {
|
||||
return CACHED.value();
|
||||
}
|
||||
|
||||
// geode/resources/geode.loader
|
||||
auto resourcesDir = this->getGeodeDirectory() /
|
||||
GEODE_RESOURCE_DIRECTORY / InternalMod::get()->getID();
|
||||
|
||||
// if the resources dir doesn't exist, then it's probably incorrect
|
||||
if (!(
|
||||
ghc::filesystem::exists(resourcesDir) &&
|
||||
ghc::filesystem::is_directory(resourcesDir)
|
||||
)) {
|
||||
this->downloadLoaderResources(callback);
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure every file was covered
|
||||
size_t coverage = 0;
|
||||
|
||||
// verify hashes
|
||||
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
|
||||
auto name = file.path().filename().string();
|
||||
// skip unknown files
|
||||
if (!LOADER_RESOURCE_HASHES.count(name)) {
|
||||
continue;
|
||||
}
|
||||
// verify hash
|
||||
auto hash = calculateSHA256(file.path());
|
||||
if (hash != LOADER_RESOURCE_HASHES.at(name)) {
|
||||
this->downloadLoaderResources(callback);
|
||||
return false;
|
||||
}
|
||||
coverage += 1;
|
||||
}
|
||||
|
||||
// make sure every file was found
|
||||
if (coverage != LOADER_RESOURCE_HASHES.size()) {
|
||||
this->downloadLoaderResources(callback);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(GEODE_IS_WINDOWS)
|
||||
void InternalLoader::platformMessageBox(const char* title, std::string const& info) {
|
||||
MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <unordered_set>
|
||||
#include <Geode/utils/json.hpp>
|
||||
#include <optional>
|
||||
#include "../index/Index.hpp"
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -27,6 +28,8 @@ protected:
|
|||
void saveInfoAlerts(nlohmann::json& json);
|
||||
void loadInfoAlerts(nlohmann::json& json);
|
||||
|
||||
void downloadLoaderResources(IndexUpdateCallback callback);
|
||||
|
||||
InternalLoader();
|
||||
~InternalLoader();
|
||||
|
||||
|
@ -54,6 +57,8 @@ public:
|
|||
void openPlatformConsole();
|
||||
void closePlatformConsole();
|
||||
static void platformMessageBox(const char* title, std::string const& info);
|
||||
|
||||
bool verifyLoaderResources(IndexUpdateCallback callback);
|
||||
|
||||
friend int geodeEntry(void* platformData);
|
||||
};
|
||||
|
|
|
@ -18,4 +18,4 @@ target_link_libraries(
|
|||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME})
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
|
|
|
@ -16,4 +16,4 @@ target_link_libraries(
|
|||
geode-sdk
|
||||
)
|
||||
|
||||
create_geode_file(${PROJECT_NAME})
|
||||
create_geode_file(${PROJECT_NAME} DONT_INSTALL)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue