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:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
paths:
|
||||
- 'bindings/**' # only when adjusting bindings
|
||||
|
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -27,6 +27,26 @@
|
|||
* Check modified date when unzipping `.geode` files (5c765c6)
|
||||
* 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
|
||||
* Allow error responses in our WebRequest classes (237128bf)
|
||||
* Display unhandled C++ exceptions in crash log (fdd78aca, 0d091626, 52421d8c, 0472075f)
|
||||
|
|
|
@ -1156,6 +1156,7 @@ class cocos2d::CCTransitionFade {
|
|||
class cocos2d::ZipUtils {
|
||||
static auto compressString(gd::string, bool, int) = mac 0xe9a50;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3031,6 +3031,7 @@ class GameLevelManager : cocos2d::CCNode {
|
|||
void uploadAccountComment(gd::string text) = win 0xb3250;
|
||||
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 deleteComment(int commentID, CommentType type, int levelID) = win 0xb41a0;
|
||||
void downloadLevel(int id, bool downloadData) = win 0xaa730;
|
||||
bool hasDownloadedLevel(int id) = win 0xab830;
|
||||
GJGameLevel* getSavedLevel(int id) = win 0xa2ee0;
|
||||
|
@ -6286,9 +6287,14 @@ class LevelTools {
|
|||
static bool verifyLevelIntegrity(gd::string, int) = mac 0x294360, win 0x18b180;
|
||||
static float xPosForTime(float, cocos2d::CCArray*, int) = mac 0x293d90, win 0x18acd0;
|
||||
static float timeForXPos(float, cocos2d::CCArray*, int) = mac 0x293eb0, win 0x18ae70;
|
||||
static gd::string getAudioFileName(int) = mac 0x292840;
|
||||
static gd::string getAudioTitle(int) = mac 0x2922f0;
|
||||
static gd::string artistForAudio(int) = mac 0x292d90;
|
||||
static gd::string urlForAudio(int) = mac 0x292f10;
|
||||
static gd::string getAudioFileName(int) = mac 0x292840, win 0x189fa0;
|
||||
static gd::string getAudioTitle(int) = mac 0x2922f0, win 0x189c60;
|
||||
static int artistForAudio(int) = mac 0x292d90, win 0x18A2D0;
|
||||
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
|
||||
|
|
|
@ -16,5 +16,5 @@ namespace geode {
|
|||
|
||||
namespace {
|
||||
// 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(
|
||||
"Updating Index", NotificationIcon::Loading, 0
|
||||
);
|
||||
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
|
||||
INDEX_UPDATE_NOTIF->show();
|
||||
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) {
|
||||
// the lock does not exist for not logged in users
|
||||
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(this, 6 + loggedInOffset, "player-icon");
|
||||
setIDSafe<CCTextInputNode>(this, 0, "username-label");
|
||||
setIDSafe<SimplePlayer>(this, 0, "player-icon");
|
||||
|
||||
auto winSize = CCDirector::get()->getWinSize();
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ $execute {
|
|||
int geodeEntry(void* platformData) {
|
||||
log::Logger::setup();
|
||||
|
||||
log::info("Entry");
|
||||
log::info("Running {} {}", Mod::get()->getName(), Mod::get()->getVersion());
|
||||
|
||||
auto begin = std::chrono::high_resolution_clock::now();
|
||||
|
||||
|
|
|
@ -673,7 +673,10 @@ Result<IndexInstallList> Index::getInstallList(IndexItemHandle item) const {
|
|||
}
|
||||
// recursively add dependencies
|
||||
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
|
||||
else {
|
||||
|
@ -745,6 +748,7 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
|||
|
||||
auto item = list.list.at(index);
|
||||
auto tempFile = dirs::getTempDir() / (item->getMetadata().getID() + ".index");
|
||||
log::debug("Installing {}", item->getMetadata().getID());
|
||||
m_runningInstallations[list.target] = web::AsyncWebRequest()
|
||||
.join("install_item_" + item->getMetadata().getID())
|
||||
.fetch(item->getDownloadURL())
|
||||
|
@ -780,6 +784,8 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
|||
|
||||
item->setIsInstalled(true);
|
||||
|
||||
log::debug("Installed {}", item->getMetadata().getID());
|
||||
|
||||
// Install next item in queue
|
||||
this->installNext(index + 1, list);
|
||||
})
|
||||
|
|
|
@ -690,6 +690,11 @@ Mod* Loader::Impl::getInternalMod() {
|
|||
auto& mod = Mod::sharedMod<>;
|
||||
if (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();
|
||||
if (!infoRes) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
|
@ -706,7 +711,6 @@ Mod* Loader::Impl::getInternalMod() {
|
|||
}
|
||||
mod->m_impl->m_enabled = true;
|
||||
m_mods.insert({ mod->getID(), mod });
|
||||
log::debug("Created internal mod {}", mod->getName());
|
||||
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));
|
||||
|
||||
// show message box on debug mode
|
||||
#ifdef GEODE_DEBUG
|
||||
MessageBoxA(nullptr, text.c_str(), "Geode Crashed", MB_ICONERROR);
|
||||
#endif
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
|
|
@ -68,6 +68,19 @@ Addresser::MultipleInheritance* Addresser::instance() {
|
|||
|
||||
intptr_t Addresser::followThunkFunction(intptr_t address) {
|
||||
#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
|
||||
if (*reinterpret_cast<uint8_t*>(address) == 0xFF && *reinterpret_cast<uint8_t*>(address + 1) == 0x25) {
|
||||
// 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]() {
|
||||
std::lock_guard _(self->m_mutex);
|
||||
std::unique_lock<std::mutex> l(self->m_mutex);
|
||||
for (auto& prog : self->m_progresses) {
|
||||
l.unlock();
|
||||
prog(*self->m_self, now, total);
|
||||
l.lock();
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
|
@ -328,9 +330,11 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
|||
m_finished = true;
|
||||
|
||||
Loader::get()->queueInMainThread([this, ret]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& then : m_thens) {
|
||||
l.unlock();
|
||||
then(*m_self, ret);
|
||||
l.lock();
|
||||
}
|
||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||
RUNNING_REQUESTS.erase(m_id);
|
||||
|
@ -355,9 +359,11 @@ void SentAsyncWebRequest::Impl::doCancel() {
|
|||
}
|
||||
|
||||
Loader::get()->queueInMainThread([this]() {
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& canc : m_cancelleds) {
|
||||
l.unlock();
|
||||
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]() {
|
||||
{
|
||||
std::lock_guard _(m_mutex);
|
||||
std::unique_lock<std::mutex> l(m_mutex);
|
||||
for (auto& expect : m_expects) {
|
||||
l.unlock();
|
||||
expect(error, code);
|
||||
l.lock();
|
||||
}
|
||||
}
|
||||
std::lock_guard _(RUNNING_REQUESTS_MUTEX);
|
||||
|
|
Loading…
Reference in a new issue