diff --git a/bindings/2.2074/Cocos2d.bro b/bindings/2.2074/Cocos2d.bro index efe9765c2..68eeefb27 100644 --- a/bindings/2.2074/Cocos2d.bro +++ b/bindings/2.2074/Cocos2d.bro @@ -1948,6 +1948,7 @@ class cocos2d::CCFadeTo : cocos2d::CCActionInterval { class cocos2d::CCFileUtils : cocos2d::TypeInfo { // CCFileUtils(); // one of these two is 0x15bfa8 // CCFileUtils(cocos2d::CCFileUtils const&); + ~CCFileUtils(); virtual void addSearchPath(char const*) = m1 0x3a42d4, imac 0x4294e0, ios 0x155a30; virtual void addSearchResolutionsOrder(char const*) = imac 0x428f30, m1 0x3a3d24, ios 0x155538; virtual gd::string addSuffix(gd::string, gd::string) = imac 0x4273f0, m1 0x3a21f4, ios 0x15459c; @@ -2224,6 +2225,7 @@ class cocos2d::CCScheduler : cocos2d::CCObject { // CCScheduler(cocos2d::CCScheduler const&); // CCScheduler(); + ~CCScheduler(); void appendIn(cocos2d::_listEntry**, cocos2d::CCObject*, bool); bool isTargetPaused(cocos2d::CCObject*); diff --git a/codegen/src/BindingGen.cpp b/codegen/src/BindingGen.cpp index 58611f41d..e5d3ea725 100644 --- a/codegen/src/BindingGen.cpp +++ b/codegen/src/BindingGen.cpp @@ -93,21 +93,32 @@ std::string generateAddressDocs(T const& f, PlatformNumber pn) { for (auto platform : {Platform::MacArm, Platform::MacIntel, Platform::Windows, Platform::iOS, Platform::Android}) { auto status = codegen::getStatusWithPlatform(platform, f); - if (status == BindStatus::NeedsBinding) { - ret += fmt::format(" * @note[short] {}: 0x{:x}\n", - nameForPlatform(platform), - codegen::platformNumberWithPlatform(platform, pn) - ); - } - else if (status == BindStatus::Binded) { - ret += fmt::format(" * @note[short] {}\n", - nameForPlatform(platform) - ); - } - else if (status == BindStatus::Inlined) { - ret += fmt::format(" * @note[short] {}: Out of line\n", - nameForPlatform(platform) - ); + auto num = codegen::platformNumberWithPlatform(platform, pn); + + switch (status) { + case BindStatus::NeedsBinding: + ret += fmt::format(" * @note[short] {}: 0x{:x}\n", + nameForPlatform(platform), + num + ); + break; + case BindStatus::NeedsRebinding: + ret += fmt::format(" * @note[short] {}: Rebinded\n", + nameForPlatform(platform) + ); + break; + case BindStatus::Binded: + ret += fmt::format(" * @note[short] {}\n", + nameForPlatform(platform) + ); + break; + case BindStatus::Inlined: + ret += fmt::format(" * @note[short] {}: Out of line\n", + nameForPlatform(platform) + ); + break; + default: + break; } } @@ -267,7 +278,7 @@ std::string generateBindingHeader(Root const& root, std::filesystem::path const& fb = &fn->prototype; - if (codegen::platformNumber(fn->binds) == -1 && codegen::getStatus(*fn) != BindStatus::Binded && fb->type != FunctionType::Normal) { + if (codegen::platformNumber(fn->binds) == -1 && codegen::getStatus(*fn) == BindStatus::NeedsBinding) { continue; } diff --git a/codegen/src/JsonInterfaceGen.cpp b/codegen/src/JsonInterfaceGen.cpp index 03ad33c6d..7f1184411 100644 --- a/codegen/src/JsonInterfaceGen.cpp +++ b/codegen/src/JsonInterfaceGen.cpp @@ -28,7 +28,7 @@ std::string generateTextInterface(Root const& root) { // skip unbinded functions continue; } - else if (status != BindStatus::NeedsBinding && !codegen::shouldAndroidBind(fn)) { + else if (status != BindStatus::NeedsBinding) { continue; } output += fmt::format("{}::{} - {:#x}\n", c.name, fn->prototype.name, codegen::platformNumber(fn->binds)); diff --git a/codegen/src/ModifyGen.cpp b/codegen/src/ModifyGen.cpp index 535995b84..414d226cf 100644 --- a/codegen/src/ModifyGen.cpp +++ b/codegen/src/ModifyGen.cpp @@ -158,7 +158,7 @@ std::string generateModifyHeader(Root const& root, std::filesystem::path const& else if (status == BindStatus::Inlined && fn->prototype.type == FunctionType::Normal || codegen::platformNumber(fn->binds) == 0x9999999) { format_string = format_strings::apply_error_inline; } - else if ((status == BindStatus::NeedsBinding && codegen::platformNumber(f) > 0) || status == BindStatus::Binded) { + else if ((status == BindStatus::NeedsBinding && codegen::platformNumber(f) > 0) || status == BindStatus::Binded || status == BindStatus::NeedsRebinding) { // only if has an address // allow bound functions (including ctors/dtors) switch (fn->prototype.type) { diff --git a/codegen/src/Shared.hpp b/codegen/src/Shared.hpp index f6c3b0e16..595091f66 100644 --- a/codegen/src/Shared.hpp +++ b/codegen/src/Shared.hpp @@ -61,8 +61,12 @@ inline bool is_cocos_class(std::string const& str) { return can_find(str, "cocos2d") || can_find(str, "pugi::") || str == "DS_Dictionary" || str == "ObjectDecoder" || str == "ObjectDecoderDelegate" || str == "CCContentManager"; } +inline bool is_in_extensions_dll(std::string const& str) { + return can_find(str, "cocos2d::extension"); +} + inline bool is_in_cocos_dll(std::string const& str) { - return is_cocos_class(str) && !can_find(str, "CCLightning"); + return is_cocos_class(str) && !can_find(str, "CCLightning") && !is_in_extensions_dll(str); } inline bool is_fmod_class(std::string const& str) { @@ -79,6 +83,7 @@ enum class BindStatus { NeedsBinding, Unbindable, Missing, + NeedsRebinding, }; struct codegen_error : std::runtime_error { @@ -207,7 +212,17 @@ namespace codegen { if (platformNumberWithPlatform(p, fn.binds) == -2) return BindStatus::Inlined; if ((fn.prototype.attributes.missing & p) != Platform::None || codegen::sdkVersion < fn.prototype.attributes.since) return BindStatus::Missing; - if ((fn.prototype.attributes.links & p) != Platform::None) return BindStatus::Binded; + if ((fn.prototype.attributes.links & p) != Platform::None) { + if (fn.prototype.type != FunctionType::Normal) return BindStatus::NeedsRebinding; + + if ((int)p & (int)Platform::Android) { + for (auto& [type, name] : fn.prototype.args) { + if (can_find(type.name, "gd::")) return BindStatus::NeedsRebinding; + } + } + + return BindStatus::Binded; + } if (platformNumberWithPlatform(p, fn.binds) != -1) return BindStatus::NeedsBinding; @@ -225,17 +240,6 @@ namespace codegen { return BindStatus::Unbindable; } - inline bool shouldAndroidBind(const FunctionBindField* fn) { - if (codegen::platform == Platform::Android32 || codegen::platform == Platform::Android64) { - if (sdkVersion < fn->prototype.attributes.since) return false; - if (fn->prototype.type != FunctionType::Normal) return true; - for (auto& [type, name] : fn->prototype.args) { - if (can_find(type.name, "gd::")) return true; - } - } - return false; - } - inline BindStatus getStatus(FunctionBindField const& fn) { return getStatusWithPlatform(codegen::platform, fn); } @@ -285,14 +289,14 @@ namespace codegen { if (codegen::platformArch == PlatformArch::x86) { // for windows x86, aka versions before 2.206 if (fn->prototype.is_static) { - if (status == BindStatus::Binded) return "Cdecl"; + if (status == BindStatus::Binded || status == BindStatus::NeedsRebinding) return "Cdecl"; else return "Optcall"; } else if (fn->prototype.is_virtual || fn->prototype.is_callback) { return "Thiscall"; } else { - if (status == BindStatus::Binded) return "Thiscall"; + if (status == BindStatus::Binded || status == BindStatus::NeedsRebinding) return "Thiscall"; else return "Membercall"; } } else { @@ -350,35 +354,50 @@ namespace codegen { inline std::string getAddressString(Class const& c, Field const& field) { if (auto fn = field.get_as()) { - const auto isWindowsCocosCtor = [&] { - return codegen::platform == Platform::Windows - && is_cocos_class(field.parent) - // && codegen::getStatus(field) == BindStatus::Binded - && fn->prototype.type != FunctionType::Normal; - }; - - if (codegen::getStatus(*fn) == BindStatus::NeedsBinding || codegen::platformNumber(field) != -1) { - if (is_in_cocos_dll(field.parent) && codegen::platform == Platform::Windows) { - return fmt::format("base::getCocos() + 0x{:x}", codegen::platformNumber(fn->binds)); + if (codegen::getStatus(*fn) == BindStatus::NeedsRebinding) { + if ((int)codegen::platform & (int)Platform::Android) { + auto const mangled = generateAndroidSymbol(c, fn); + return fmt::format( // thumb + "reinterpret_cast(dlsym(dlopen(\"libcocos2dcpp.so\", RTLD_NOW), \"{}\"))", + mangled + ); + } + else if (codegen::platform == Platform::Windows) { + auto const mangled = generateWindowsSymbol(c, fn); + if (is_in_cocos_dll(field.parent)) { + return fmt::format( + "reinterpret_cast(GetProcAddress((HMODULE)base::getCocos(), \"{}\"))", + mangled + ); + } + else if (is_in_extensions_dll(field.parent)) { + return fmt::format( + "reinterpret_cast(GetProcAddress((HMODULE)base::getExtensions(), \"{}\"))", + mangled + ); + } + else { + // Uhhhh im not sure + return "(void*)0x9911991122"; + } + } + } + else if (codegen::getStatus(*fn) == BindStatus::NeedsBinding || codegen::platformNumber(field) != -1) { + if (codegen::platform == Platform::Windows) { + if (is_in_cocos_dll(field.parent)) { + return fmt::format("base::getCocos() + 0x{:x}", codegen::platformNumber(fn->binds)); + } + else if (is_in_extensions_dll(field.parent)) { + return fmt::format("base::getExtensions() + 0x{:x}", codegen::platformNumber(fn->binds)); + } + else { + return fmt::format("base::get() + 0x{:x}", codegen::platformNumber(fn->binds)); + } } else { return fmt::format("base::get() + 0x{:x}", codegen::platformNumber(fn->binds)); } } - else if (codegen::shouldAndroidBind(fn)) { - auto const mangled = generateAndroidSymbol(c, fn); - return fmt::format( // thumb - "reinterpret_cast(dlsym(dlopen(\"libcocos2dcpp.so\", RTLD_NOW), \"{}\"))", - mangled - ); - } - else if (isWindowsCocosCtor()) { - auto const mangled = generateWindowsSymbol(c, fn); - return fmt::format( - "reinterpret_cast(GetProcAddress(GetModuleHandleA(\"libcocos2d.dll\"), \"{}\"))", - mangled - ); - } else if (codegen::getStatus(*fn) == BindStatus::Binded && fn->prototype.type == FunctionType::Normal) { return fmt::format( "addresser::get{}Virtual(Resolve<{}>::func(&{}::{}))", diff --git a/codegen/src/SourceGen.cpp b/codegen/src/SourceGen.cpp index 62bfd940f..ca27f6387 100644 --- a/codegen/src/SourceGen.cpp +++ b/codegen/src/SourceGen.cpp @@ -10,22 +10,6 @@ namespace { namespace format_strings { using namespace geode; using namespace geode::modifier; -using cocos2d::CCDestructor; - -std::unordered_map& CCDestructor::destructorLock() {{ - static thread_local std::unordered_map ret; - return ret; -}} -bool& CCDestructor::globalLock() {{ - static thread_local bool ret = false; - return ret; -}} -bool& CCDestructor::lock(void* self) { - return destructorLock()[self]; -} -CCDestructor::~CCDestructor() {{ - destructorLock().erase(this); -}} auto wrapFunction(uintptr_t address, tulip::hook::WrapperMetadata const& metadata) { auto wrapped = geode::hook::createWrapper(reinterpret_cast(address), metadata); @@ -34,11 +18,6 @@ auto wrapFunction(uintptr_t address, tulip::hook::WrapperMetadata const& metadat }} return wrapped.unwrap(); } - -// So apparently Clang considers cdecl to return floats through ST0, whereas -// MSVC thinks they are returned through XMM0. This has caused a lot of pain -// and misery for me - )GEN"; constexpr char const* declare_member = R"GEN( @@ -79,32 +58,37 @@ auto {class_name}::{function_name}({parameters}){const} -> decltype({function_na {class_name}::{function_name}({parameters}) {{ // basically we destruct it once by calling the gd function, // then lock it, so that other gd destructors dont get called - if (CCDestructor::lock(this)) return; - using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types}); - static auto func = wrapFunction({address_inline}, tulip::hook::WrapperMetadata{{ - .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}), - .m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)), - }}); - reinterpret_cast(func)(this{parameter_comma}{arguments}); - // we need to construct it back so that it uhhh ummm doesnt crash - // while going to the child destructors - auto thing = new (this) {class_name}(geode::CutoffConstructor, sizeof({class_name})); - CCDestructor::lock(this) = true; + if (!geode::DestructorLock::isLocked(this)) {{ + using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types}); + static auto func = wrapFunction({address_inline}, tulip::hook::WrapperMetadata{{ + .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}), + .m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)), + }}); + reinterpret_cast(func)(this{parameter_comma}{arguments}); + + // we need to construct it back so that it uhhh ummm doesnt crash + // while going to the child destructors + auto thing = new (this) {class_name}(geode::CutoffConstructor, sizeof({class_name})); + geode::DestructorLock::addLock(this); + }} }} )GEN"; constexpr char const* declare_destructor_baseless = R"GEN( {class_name}::{function_name}({parameters}) {{ // basically we destruct it once by calling the gd function, - // then lock it, so that other gd destructors dont get called - if (CCDestructor::lock(this)) return; - using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types}); - static auto func = wrapFunction({address_inline}, tulip::hook::WrapperMetadata{{ - .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}), - .m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)), - }}); - reinterpret_cast(func)(this{parameter_comma}{arguments}); - CCDestructor::lock(this) = true; + // then we release the lock because there are no other destructors after this + if (!geode::DestructorLock::isLocked(this)) {{ + using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types}); + static auto func = wrapFunction({address_inline}, tulip::hook::WrapperMetadata{{ + .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}), + .m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)), + }}); + reinterpret_cast(func)(this{parameter_comma}{arguments}); + }} + else {{ + geode::DestructorLock::removeLock(this); + }} }} )GEN"; @@ -113,8 +97,9 @@ auto {class_name}::{function_name}({parameters}){const} -> decltype({function_na // here we construct it as normal as we can, then destruct it // using the generated functions. this ensures no memory gets leaked // no crashes :pray: - CCDestructor::lock(this) = true; + geode::DestructorLock::addLock(this); {class_name}::~{unqualified_class_name}(); + using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types}); static auto func = wrapFunction({address_inline}, tulip::hook::WrapperMetadata{{ .m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}), @@ -165,6 +150,10 @@ auto {class_name}::{function_name}({parameters}){const} -> decltype({function_na )GEN"; }} +bool areSuperclassesEmpty(Class const& c) { + return c.superclasses.empty() || (c.superclasses.size() == 1 && c.superclasses[0].find("CCCopying") != std::string::npos); +} + std::string generateBindingSource(Root const& root, bool skipPugixml) { std::string output(format_strings::source_start); @@ -237,7 +226,7 @@ std::string generateBindingSource(Root const& root, bool skipPugixml) { if (codegen::platformNumber(fn->binds) == 0x9999999) { used_declare_format = format_strings::declare_unimplemented_error; } - else if (codegen::getStatus(*fn) != BindStatus::NeedsBinding && !codegen::shouldAndroidBind(fn)) { + else if (codegen::getStatus(*fn) != BindStatus::NeedsBinding && codegen::getStatus(*fn) != BindStatus::NeedsRebinding) { continue; } @@ -247,7 +236,7 @@ std::string generateBindingSource(Root const& root, bool skipPugixml) { used_declare_format = format_strings::declare_member; break; case FunctionType::Ctor: - if (c.superclasses.empty()) { + if (areSuperclassesEmpty(c)) { used_declare_format = format_strings::declare_constructor_begin; } else { @@ -255,7 +244,7 @@ std::string generateBindingSource(Root const& root, bool skipPugixml) { } break; case FunctionType::Dtor: - used_declare_format = c.superclasses.empty() ? format_strings::declare_destructor_baseless : format_strings::declare_destructor; + used_declare_format = areSuperclassesEmpty(c) ? format_strings::declare_destructor_baseless : format_strings::declare_destructor; break; } diff --git a/scripts/dll2lib.py b/scripts/dll2lib.py new file mode 100644 index 000000000..15dcfa2b9 --- /dev/null +++ b/scripts/dll2lib.py @@ -0,0 +1,47 @@ +import subprocess +import sys +import os + +try: + subprocess.run(["dumpbin"], capture_output=True) +except Exception: + print("dumpbin not found, run this in a VS environment") + print("search for \"Developer Command Prompt for VS\" or something") + exit(1) + +if len(sys.argv) != 2: + print(f"Usage: python dll2lib.py ", file=sys.stderr) + exit(1) + +DLL_FILE = sys.argv[1] + +if not os.path.exists(DLL_FILE): + print("File not found", file=sys.stderr) + exit(1) + +DLL_FILE = DLL_FILE.removeprefix('.\\') +DLL_NAME = DLL_FILE.removesuffix('.dll') +DEF_FILE = f"{DLL_NAME}.def" +LIB_FILE = f"{DLL_NAME}.lib" + +headers_raw = subprocess.run(["dumpbin", "/headers", DLL_FILE], capture_output=True).stdout.decode()[:350] +machine = 'x64' if 'machine (x64)' in headers_raw else 'x86' + +exports_raw = subprocess.run(["dumpbin", "/exports", DLL_FILE], capture_output=True).stdout.decode() +exports = [l.split()[3] for l in exports_raw.splitlines()[19:] if len(l.split()) > 3] + +# Removes constructors and destructors +for e in exports: + if e.startswith('??0') or e.startswith('??1'): + print(f"Removing {e}") +exports = [e for e in exports if not e.startswith('??0') and not e.startswith('??1')] + +with open(DEF_FILE, 'w') as file: + file.write(f"LIBRARY {DLL_NAME}.dll\n") + file.write("EXPORTS\n") + file.write('\n'.join(exports)) + +subprocess.run(["lib", f"/def:{DEF_FILE}", f"/out:{LIB_FILE}", f"/machine:{machine}"]) + +os.remove(DEF_FILE) +os.remove(f"{DLL_NAME}.exp") \ No newline at end of file