mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
Merge branch 'main' into 1.4.0-dev
This commit is contained in:
commit
ebcc23e7a9
14 changed files with 156 additions and 17 deletions
1
.github/workflows/test-offsets.yml
vendored
1
.github/workflows/test-offsets.yml
vendored
|
@ -2,6 +2,7 @@ name: Test Offsets
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- 'bindings/**' # only when adjusting bindings
|
- 'bindings/**' # only when adjusting bindings
|
||||||
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -27,6 +27,26 @@
|
||||||
* Check modified date when unzipping `.geode` files (5c765c6)
|
* Check modified date when unzipping `.geode` files (5c765c6)
|
||||||
* Only hash markdown files on resource checking (f563c46)
|
* Only hash markdown files on resource checking (f563c46)
|
||||||
|
|
||||||
|
## v1.3.10
|
||||||
|
* Panic if decompressString2 fails, to prevent data loss (0787b8f4)
|
||||||
|
|
||||||
|
## v1.3.9
|
||||||
|
* Fix followThunkFunction (4b766301)
|
||||||
|
|
||||||
|
## v1.3.8
|
||||||
|
* Implement a save file fix for Windows (391f63ed)
|
||||||
|
* Recursively follow jumps in followThunkFunction (44a018cd)
|
||||||
|
* Remove need for GEODE_DEBUG for crashlogs (e8a326f7)
|
||||||
|
* Some bindings (f18335fa)
|
||||||
|
|
||||||
|
## v1.3.7
|
||||||
|
* Fix web download deadlock (16418562)
|
||||||
|
* Set max time for updating index notification (f7962246)
|
||||||
|
* Log geode version on startup (c5550a67)
|
||||||
|
* Fix logic error in addHook (5cf0f3c2)
|
||||||
|
* Improve logging + minor refactors (5083017b)
|
||||||
|
* Some bindings changes
|
||||||
|
|
||||||
## v1.3.6
|
## v1.3.6
|
||||||
* Allow error responses in our WebRequest classes (237128bf)
|
* Allow error responses in our WebRequest classes (237128bf)
|
||||||
* Display unhandled C++ exceptions in crash log (fdd78aca, 0d091626, 52421d8c, 0472075f)
|
* Display unhandled C++ exceptions in crash log (fdd78aca, 0d091626, 52421d8c, 0472075f)
|
||||||
|
|
|
@ -1156,6 +1156,7 @@ class cocos2d::CCTransitionFade {
|
||||||
class cocos2d::ZipUtils {
|
class cocos2d::ZipUtils {
|
||||||
static auto compressString(gd::string, bool, int) = mac 0xe9a50;
|
static auto compressString(gd::string, bool, int) = mac 0xe9a50;
|
||||||
static auto decompressString(gd::string, bool, int) = mac 0xea380;
|
static auto decompressString(gd::string, bool, int) = mac 0xea380;
|
||||||
|
static auto decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey);
|
||||||
static int ccDeflateMemory(unsigned char*, unsigned int, unsigned char**) = mac 0xe9cf0;
|
static int ccDeflateMemory(unsigned char*, unsigned int, unsigned char**) = mac 0xe9cf0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3031,6 +3031,7 @@ class GameLevelManager : cocos2d::CCNode {
|
||||||
void uploadAccountComment(gd::string text) = win 0xb3250;
|
void uploadAccountComment(gd::string text) = win 0xb3250;
|
||||||
void uploadLevelComment(int levelID, gd::string text, int unk) = win 0xb31c0;
|
void uploadLevelComment(int levelID, gd::string text, int unk) = win 0xb31c0;
|
||||||
void uploadComment(gd::string text, CommentType type, int levelID, int unk) = win 0xb32e0;
|
void uploadComment(gd::string text, CommentType type, int levelID, int unk) = win 0xb32e0;
|
||||||
|
void deleteComment(int commentID, CommentType type, int levelID) = win 0xb41a0;
|
||||||
void downloadLevel(int id, bool downloadData) = win 0xaa730;
|
void downloadLevel(int id, bool downloadData) = win 0xaa730;
|
||||||
bool hasDownloadedLevel(int id) = win 0xab830;
|
bool hasDownloadedLevel(int id) = win 0xab830;
|
||||||
GJGameLevel* getSavedLevel(int id) = win 0xa2ee0;
|
GJGameLevel* getSavedLevel(int id) = win 0xa2ee0;
|
||||||
|
@ -6286,9 +6287,14 @@ class LevelTools {
|
||||||
static bool verifyLevelIntegrity(gd::string, int) = mac 0x294360, win 0x18b180;
|
static bool verifyLevelIntegrity(gd::string, int) = mac 0x294360, win 0x18b180;
|
||||||
static float xPosForTime(float, cocos2d::CCArray*, int) = mac 0x293d90, win 0x18acd0;
|
static float xPosForTime(float, cocos2d::CCArray*, int) = mac 0x293d90, win 0x18acd0;
|
||||||
static float timeForXPos(float, cocos2d::CCArray*, int) = mac 0x293eb0, win 0x18ae70;
|
static float timeForXPos(float, cocos2d::CCArray*, int) = mac 0x293eb0, win 0x18ae70;
|
||||||
static gd::string getAudioFileName(int) = mac 0x292840;
|
static gd::string getAudioFileName(int) = mac 0x292840, win 0x189fa0;
|
||||||
static gd::string getAudioTitle(int) = mac 0x2922f0;
|
static gd::string getAudioTitle(int) = mac 0x2922f0, win 0x189c60;
|
||||||
static gd::string artistForAudio(int) = mac 0x292d90;
|
static int artistForAudio(int) = mac 0x292d90, win 0x18A2D0;
|
||||||
static gd::string urlForAudio(int) = mac 0x292f10;
|
static gd::string urlForAudio(int) = mac 0x292f10, win 0x18a4a0;
|
||||||
|
static gd::string nameForArtist(int) = win 0x18A3A0;
|
||||||
|
static gd::string ngURLForArtist(int) = win 0x18A7C0;
|
||||||
|
static gd::string ytURLForArtist(int) = win 0x18A8C0;
|
||||||
|
static gd::string fbURLForArtist(int) = win 0x18A9B0;
|
||||||
|
static gd::string getAudioString(int) = win 0x18AAA0;
|
||||||
}
|
}
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
|
@ -16,5 +16,5 @@ namespace geode {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// to make sure the instance is set into the sharedMod<> in load time
|
// to make sure the instance is set into the sharedMod<> in load time
|
||||||
static auto mod = geode::getMod();
|
static auto mod = geode::getMod();
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,6 +174,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||||
INDEX_UPDATE_NOTIF = Notification::create(
|
INDEX_UPDATE_NOTIF = Notification::create(
|
||||||
"Updating Index", NotificationIcon::Loading, 0
|
"Updating Index", NotificationIcon::Loading, 0
|
||||||
);
|
);
|
||||||
|
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
|
||||||
INDEX_UPDATE_NOTIF->show();
|
INDEX_UPDATE_NOTIF->show();
|
||||||
Index::get()->update();
|
Index::get()->update();
|
||||||
}
|
}
|
||||||
|
|
78
loader/src/hooks/SaveFileFix.cpp
Normal file
78
loader/src/hooks/SaveFileFix.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include <Geode/loader/Loader.hpp>
|
||||||
|
|
||||||
|
#if defined(GEODE_IS_WINDOWS) || defined(GEODE_IS_ANDROID)
|
||||||
|
|
||||||
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
#include <Geode/cocos/support/base64.h>
|
||||||
|
#include "../loader/LoaderImpl.hpp"
|
||||||
|
|
||||||
|
void panic(std::string reason) {
|
||||||
|
LoaderImpl::get()->platformMessageBox("Critical", fmt::format(
|
||||||
|
"Your save file failed to load (reason: {})\n"
|
||||||
|
"As to not lose all of your data, the game will now abort.\n"
|
||||||
|
"Please backup your save files and try opening the game again, it might work.\n"
|
||||||
|
"Please contact the Geode Team about this", reason
|
||||||
|
));
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is well known for crashing on certain save files,
|
||||||
|
// causing the game to crash at startup, known as the infamous save file bug.
|
||||||
|
//
|
||||||
|
// Rob ends up relying on strlen for knowing the size of `data`, instead of just using the passed in `size`.
|
||||||
|
// Its a miracle this works most of the time, considering `data` is just binary data
|
||||||
|
//
|
||||||
|
// To fix this, we just rewrite the function.
|
||||||
|
gd::string decompressString2(unsigned char* data, bool decrypt, int size, int decryptionKey) {
|
||||||
|
log::debug("decompressString2 data={} size={}", reinterpret_cast<const void*>(data), size);
|
||||||
|
if (data == nullptr || size == 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> copiedData(data, data + size);
|
||||||
|
if (decrypt) {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
copiedData[i] ^= decryptionKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: maybe not use cocos's base64 and inflateMemory..
|
||||||
|
|
||||||
|
unsigned char* out = nullptr;
|
||||||
|
auto const decodedSize = cocos2d::base64Decode(copiedData.data(), size, &out);
|
||||||
|
std::unique_ptr<unsigned char> b64decoded { out };
|
||||||
|
|
||||||
|
if (decodedSize <= 0) {
|
||||||
|
panic(fmt::format("base64 (size={}) (data={} size={})", decodedSize, reinterpret_cast<const void*>(data), size));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
out = nullptr;
|
||||||
|
auto const inflatedSize = cocos2d::ZipUtils::ccInflateMemory(b64decoded.get(), decodedSize, &out);
|
||||||
|
|
||||||
|
std::unique_ptr<unsigned char> inflated { out };
|
||||||
|
|
||||||
|
if (inflatedSize <= 0) {
|
||||||
|
panic(fmt::format("inflate (size={}) (data={} size={})", inflatedSize, reinterpret_cast<const void*>(data), size));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(reinterpret_cast<char*>(inflated.get()), inflatedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify doesnt want to work for some reason!
|
||||||
|
$execute {
|
||||||
|
(void) Mod::get()->addHook(
|
||||||
|
reinterpret_cast<void*>(
|
||||||
|
geode::addresser::getNonVirtual(
|
||||||
|
&cocos2d::ZipUtils::decompressString2
|
||||||
|
)
|
||||||
|
),
|
||||||
|
&decompressString2,
|
||||||
|
"cocos2d::ZipUtils::decompressString2",
|
||||||
|
tulip::hook::TulipConvention::Cdecl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -9,9 +9,13 @@ using namespace geode::prelude;
|
||||||
$register_ids(GJGarageLayer) {
|
$register_ids(GJGarageLayer) {
|
||||||
// the lock does not exist for not logged in users
|
// the lock does not exist for not logged in users
|
||||||
auto loggedInOffset = GJAccountManager::get()->m_accountID == GJAccountManager::get()->m_playerID ? -1 : 0;
|
auto loggedInOffset = GJAccountManager::get()->m_accountID == GJAccountManager::get()->m_playerID ? -1 : 0;
|
||||||
|
if (loggedInOffset == -1 && !GameManager::get()->m_clickedName) {
|
||||||
|
// adjusts for the sprite asking for your name
|
||||||
|
loggedInOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
setIDSafe(this, 2, "username-label");
|
setIDSafe<CCTextInputNode>(this, 0, "username-label");
|
||||||
setIDSafe(this, 6 + loggedInOffset, "player-icon");
|
setIDSafe<SimplePlayer>(this, 0, "player-icon");
|
||||||
|
|
||||||
auto winSize = CCDirector::get()->getWinSize();
|
auto winSize = CCDirector::get()->getWinSize();
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ $execute {
|
||||||
int geodeEntry(void* platformData) {
|
int geodeEntry(void* platformData) {
|
||||||
log::Logger::setup();
|
log::Logger::setup();
|
||||||
|
|
||||||
log::info("Entry");
|
log::info("Running {} {}", Mod::get()->getName(), Mod::get()->getVersion());
|
||||||
|
|
||||||
auto begin = std::chrono::high_resolution_clock::now();
|
auto begin = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
|
|
@ -673,7 +673,10 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
||||||
}
|
}
|
||||||
// recursively add dependencies
|
// recursively add dependencies
|
||||||
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
|
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
|
||||||
ranges::push(list.list, deps.list);
|
for (auto& dep : deps.list) {
|
||||||
|
if (ranges::contains(list.list, dep)) continue;
|
||||||
|
list.list.push_back(dep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// otherwise user must get this dependency manually from somewhere
|
// otherwise user must get this dependency manually from somewhere
|
||||||
else {
|
else {
|
||||||
|
@ -745,6 +748,7 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
||||||
|
|
||||||
auto item = list.list.at(index);
|
auto item = list.list.at(index);
|
||||||
auto tempFile = dirs::getTempDir() / (item->getMetadata().getID() + ".index");
|
auto tempFile = dirs::getTempDir() / (item->getMetadata().getID() + ".index");
|
||||||
|
log::debug("Installing {}", item->getMetadata().getID());
|
||||||
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
||||||
.join("install_item_" + item->getMetadata().getID())
|
.join("install_item_" + item->getMetadata().getID())
|
||||||
.fetch(item->getDownloadURL())
|
.fetch(item->getDownloadURL())
|
||||||
|
@ -780,6 +784,8 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
||||||
|
|
||||||
item->setIsInstalled(true);
|
item->setIsInstalled(true);
|
||||||
|
|
||||||
|
log::debug("Installed {}", item->getMetadata().getID());
|
||||||
|
|
||||||
// Install next item in queue
|
// Install next item in queue
|
||||||
this->installNext(index + 1, list);
|
this->installNext(index + 1, list);
|
||||||
})
|
})
|
||||||
|
|
|
@ -690,6 +690,11 @@ Mod* Loader::Impl::getInternalMod() {
|
||||||
auto& mod = Mod::sharedMod<>;
|
auto& mod = Mod::sharedMod<>;
|
||||||
if (mod)
|
if (mod)
|
||||||
return mod;
|
return mod;
|
||||||
|
if (m_mods.contains("geode.loader")) {
|
||||||
|
log::warn("Something went wrong and Mod::sharedMod<> got unset after the internal mod was created! Setting sharedMod back...");
|
||||||
|
mod = m_mods["geode.loader"];
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
auto infoRes = getModImplInfo();
|
auto infoRes = getModImplInfo();
|
||||||
if (!infoRes) {
|
if (!infoRes) {
|
||||||
LoaderImpl::get()->platformMessageBox(
|
LoaderImpl::get()->platformMessageBox(
|
||||||
|
@ -706,7 +711,6 @@ Mod* Loader::Impl::getInternalMod() {
|
||||||
}
|
}
|
||||||
mod->m_impl->m_enabled = true;
|
mod->m_impl->m_enabled = true;
|
||||||
m_mods.insert({ mod->getID(), mod });
|
m_mods.insert({ mod->getID(), mod });
|
||||||
log::debug("Created internal mod {}", mod->getName());
|
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -264,10 +264,7 @@ static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) {
|
||||||
|
|
||||||
auto text = crashlog::writeCrashlog(faultyMod, getInfo(info, faultyMod), getStacktrace(info->ContextRecord), getRegisters(info->ContextRecord));
|
auto text = crashlog::writeCrashlog(faultyMod, getInfo(info, faultyMod), getStacktrace(info->ContextRecord), getRegisters(info->ContextRecord));
|
||||||
|
|
||||||
// show message box on debug mode
|
|
||||||
#ifdef GEODE_DEBUG
|
|
||||||
MessageBoxA(nullptr, text.c_str(), "Geode Crashed", MB_ICONERROR);
|
MessageBoxA(nullptr, text.c_str(), "Geode Crashed", MB_ICONERROR);
|
||||||
#endif
|
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,19 @@ Addresser::MultipleInheritance* Addresser::instance() {
|
||||||
|
|
||||||
intptr_t Addresser::followThunkFunction(intptr_t address) {
|
intptr_t Addresser::followThunkFunction(intptr_t address) {
|
||||||
#ifdef GEODE_IS_WINDOWS
|
#ifdef GEODE_IS_WINDOWS
|
||||||
|
// if theres a jmp at the start
|
||||||
|
if (*reinterpret_cast<uint8_t*>(address) == 0xE9) {
|
||||||
|
auto relative = *reinterpret_cast<uint32_t*>(address + 1);
|
||||||
|
auto newAddress = address + relative + 5;
|
||||||
|
// and if that jmp leads to a jmp dword ptr, only then follow it,
|
||||||
|
// because otherwise its just a hook.
|
||||||
|
// For some reason this [jmp -> jmp dword ptr] chain happens with a few cocos functions,
|
||||||
|
// but not all. For example: cocos2d::ZipUtils::decompressString2
|
||||||
|
if (*reinterpret_cast<uint8_t*>(newAddress) == 0xFF && *reinterpret_cast<uint8_t*>(newAddress + 1) == 0x25) {
|
||||||
|
address = newAddress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check if first instruction is a jmp dword ptr [....], i.e. if the func is a thunk
|
// check if first instruction is a jmp dword ptr [....], i.e. if the func is a thunk
|
||||||
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
||||||
// read where the jmp reads from
|
// read where the jmp reads from
|
||||||
|
|
|
@ -298,9 +298,11 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::get()->queueInMainThread([self = data->self, now, total]() {
|
Loader::get()->queueInMainThread([self = data->self, now, total]() {
|
||||||
std::lock_guard _(self->m_mutex);
|
std::unique_lock<std::mutex> l(self->m_mutex);
|
||||||
for (auto& prog : self->m_progresses) {
|
for (auto& prog : self->m_progresses) {
|
||||||
|
l.unlock();
|
||||||
prog(*self->m_self, now, total);
|
prog(*self->m_self, now, total);
|
||||||
|
l.lock();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -328,9 +330,11 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
m_finished = true;
|
m_finished = true;
|
||||||
|
|
||||||
Loader::get()->queueInMainThread([this, ret]() {
|
Loader::get()->queueInMainThread([this, ret]() {
|
||||||
std::lock_guard _(m_mutex);
|
std::unique_lock<std::mutex> l(m_mutex);
|
||||||
for (auto& then : m_thens) {
|
for (auto& then : m_thens) {
|
||||||
|
l.unlock();
|
||||||
then(*m_self, ret);
|
then(*m_self, ret);
|
||||||
|
l.lock();
|
||||||
}
|
}
|
||||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||||
RUNNING_REQUESTS.erase(m_id);
|
RUNNING_REQUESTS.erase(m_id);
|
||||||
|
@ -355,9 +359,11 @@ void SentAsyncWebRequest::Impl::doCancel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader::get()->queueInMainThread([this]() {
|
Loader::get()->queueInMainThread([this]() {
|
||||||
std::lock_guard _(m_mutex);
|
std::unique_lock<std::mutex> l(m_mutex);
|
||||||
for (auto& canc : m_cancelleds) {
|
for (auto& canc : m_cancelleds) {
|
||||||
|
l.unlock();
|
||||||
canc(*m_self);
|
canc(*m_self);
|
||||||
|
l.lock();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -393,9 +399,11 @@ void SentAsyncWebRequest::Impl::error(std::string const& error, int code) {
|
||||||
});
|
});
|
||||||
Loader::get()->queueInMainThread([this, error, code]() {
|
Loader::get()->queueInMainThread([this, error, code]() {
|
||||||
{
|
{
|
||||||
std::lock_guard _(m_mutex);
|
std::unique_lock<std::mutex> l(m_mutex);
|
||||||
for (auto& expect : m_expects) {
|
for (auto& expect : m_expects) {
|
||||||
|
l.unlock();
|
||||||
expect(error, code);
|
expect(error, code);
|
||||||
|
l.lock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
||||||
|
|
Loading…
Reference in a new issue