move bindings to their own separate repo

This commit is contained in:
HJfod 2023-12-20 14:23:21 +02:00
parent 151303c696
commit 3aa3ae7dce
13 changed files with 19 additions and 9000 deletions

View file

@ -115,11 +115,25 @@ add_library(GeodeFilesystemImpl ${CMAKE_CURRENT_SOURCE_DIR}/FilesystemImpl.cpp)
target_compile_features(GeodeFilesystemImpl PUBLIC cxx_std_20)
target_link_libraries(GeodeFilesystemImpl PUBLIC ghc_filesystem)
# Allow users to have their own copy of bindings that can be overwritten with a CMake option.
# If the option is not provided, by default just clone bindings with CPM and use that
if (NOT GEODE_BINDINGS_REPO_PATH)
message(STATUS
"No override path for bindings provided, using CPM to clone default. "
"If you would like to use a separate clone of the bindings repo "
"(for example in order to be able to efficiently change and "
"contribute new bindings) then set GEODE_BINDINGS_REPO_PATH to where you have "
"cloned the repository."
)
CPMAddPackage("gh:geode-sdk/bindings#2c4e7b9")
set(GEODE_BINDINGS_REPO_PATH ${GeodeBindings_SOURCE_DIR})
endif()
include(ExternalProject)
set(GEODE_CODEGEN_BINARY_OUT ${CMAKE_CURRENT_BINARY_DIR}/codegen)
ExternalProject_Add(CodegenProject
BUILD_ALWAYS ON
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/codegen
SOURCE_DIR ${GEODE_BINDINGS_REPO_PATH}
# manually set configure command as to not inherit generator used by geode,
# this should hopefully fix generator cache mismatch between different projects, however
# it causes a warning to be shown every time. if you know a better solution please tell us ok thx
@ -133,13 +147,13 @@ ExternalProject_Add(CodegenProject
file(GLOB CODEGEN_DEPENDS CONFIGURE_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bindings/*.bro
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/*.hpp
${GEODE_BINDINGS_REPO_PATH}/bindings/*.bro
${GEODE_BINDINGS_REPO_PATH}/codegen/src/*.cpp
${GEODE_BINDINGS_REPO_PATH}/codegen/src/*.hpp
)
if (NOT GEODE_BINDINGS_PATH)
set(GEODE_BINDINGS_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bindings)
set(GEODE_BINDINGS_PATH ${GEODE_BINDINGS_REPO_PATH}/bindings)
endif()
file(GLOB CODEGEN_OUTPUTS CONFIGURE_DEPENDS

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
#include <Cocos2d.bro>
#include <GeometryDash.bro>

File diff suppressed because it is too large Load diff

View file

@ -1,25 +0,0 @@
cmake_minimum_required(VERSION 3.21 FATAL_ERROR)
project(Codegen LANGUAGES C CXX)
include(../cmake/CPM.cmake)
CPMAddPackage("gh:fmtlib/fmt#9.1.0")
CPMAddPackage("gh:geode-sdk/Broma#38a3bba")
file(GLOB SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp
)
add_executable(${PROJECT_NAME} ${SOURCES})
target_compile_features(Codegen PUBLIC cxx_std_17)
target_link_libraries(Codegen PRIVATE fmt::fmt Broma)
target_include_directories(Codegen PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_precompile_headers(Codegen PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/Shared.hpp
)
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX})

View file

@ -1,218 +0,0 @@
#include "Shared.hpp"
#include "AndroidSymbol.hpp"
namespace {
namespace format_strings {
char const* address_begin = R"GEN(
#include <Geode/Bindings.hpp>
#include <Geode/modify/Addresses.hpp>
#include <Geode/modify/Traits.hpp>
#include <Geode/loader/Tulip.hpp>
using namespace geode;
)GEN";
char const* declare_address = R"GEN(
template <>
uintptr_t geode::modifier::address<{index}>() {{
static uintptr_t ret = {address};
return ret;
}}
)GEN";
char const* declare_metadata_begin = R"GEN(
Result<tulip::hook::HandlerMetadata> geode::modifier::handlerMetadataForAddress(uintptr_t address) {
static auto s_value = []() {
std::map<uintptr_t, tulip::hook::HandlerMetadata(*)()> ret;
)GEN";
char const* declare_metadata = R"GEN(
{{
using FunctionType = {return}(*)({class_name}{const}*{parameter_comma}{parameter_types});
ret[modifier::address<{index}>()] = +[](){{
return tulip::hook::HandlerMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}};
}};
}}
)GEN";
char const* declare_metadata_static = R"GEN(
{{
using FunctionType = {return}(*)({parameter_types});
ret[modifier::address<{index}>()] = +[](){{
return tulip::hook::HandlerMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}};
}};
}}
)GEN";
char const* declare_metadata_structor = R"GEN(
{{
using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types});
ret[modifier::address<{index}>()] = +[](){{
return tulip::hook::HandlerMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}};
}};
}}
)GEN";
char const* declare_metadata_end = R"GEN(
return ret;
}();
if (s_value.count(address) > 0) return geode::Ok(std::move(s_value[address]()));
return geode::Err("Address is not registered for wrapper");
}
)GEN";
}
}
std::string generateAddressHeader(Root const& root) {
std::string output;
output += format_strings::address_begin;
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
std::string address_str;
if (codegen::getStatus(f) == BindStatus::Binded) {
address_str = fmt::format(
"addresser::getNonVirtual(Resolve<{}>::func(&{}))",
codegen::getParameterTypes(f.prototype),
f.prototype.name
);
}
else if (codegen::getStatus(f) == BindStatus::NeedsBinding) {
address_str = fmt::format("base::get() + 0x{:x}", codegen::platformNumber(f.binds));
}
else {
continue;
}
output += fmt::format(
format_strings::declare_address,
fmt::arg("address", address_str),
fmt::arg("index", codegen::getId(&f))
);
}
for (auto& c : root.classes) {
for (auto& field : c.fields) {
if (codegen::getStatus(field) == BindStatus::Missing) continue;
std::string address_str;
auto fn = field.get_as<FunctionBindField>();
if (!fn) {
continue;
}
if (codegen::getStatus(field) == BindStatus::NeedsBinding || codegen::platformNumber(field) != -1) {
if (is_cocos_class(field.parent) && codegen::platform == Platform::Windows) {
address_str = fmt::format("base::getCocos() + 0x{:x}", codegen::platformNumber(fn->binds));
}
else {
address_str = fmt::format("base::get() + 0x{:x}", codegen::platformNumber(fn->binds));
}
}
else if (codegen::shouldAndroidBind(fn)) {
auto const mangled = generateAndroidSymbol(c, fn);
address_str = fmt::format( // thumb
"reinterpret_cast<uintptr_t>(dlsym(dlopen(\"libcocos2dcpp.so\", RTLD_NOW), \"{}\"))",
mangled
);
}
else if (codegen::getStatus(field) == BindStatus::Binded && fn->prototype.type == FunctionType::Normal) {
address_str = fmt::format(
"addresser::get{}Virtual(Resolve<{}>::func(&{}::{}))",
str_if("Non", !fn->prototype.is_virtual),
codegen::getParameterTypes(fn->prototype),
field.parent,
fn->prototype.name
);
}
else {
continue;
}
output += fmt::format(
format_strings::declare_address,
fmt::arg("address", address_str),
fmt::arg("index", codegen::getId(&field))
);
}
}
// TODO: this eats too much of compile time make it opt in maybe
// output += format_strings::declare_metadata_begin;
// for (auto& c : root.classes) {
// for (auto& field : c.fields) {
// std::string address_str;
// auto fn = field.get_as<FunctionBindField>();
// if (!fn) {
// continue;
// }
// if (codegen::getStatus(field) == BindStatus::Binded) {
// address_str = fmt::format(
// "addresser::get{}Virtual(Resolve<{}>::func(&{}::{}))",
// str_if("Non", !fn->beginning.is_virtual),
// codegen::getParameterTypes(fn->beginning),
// field.parent,
// fn->beginning.name
// );
// }
// else if (codegen::getStatus(field) == BindStatus::NeedsBinding) {
// address_str = fmt::format("base::get() + 0x{:x}", codegen::platformNumber(fn->binds));
// }
// else {
// continue;
// }
// char const* used_declare_format;
// switch (fn->beginning.type) {
// case FunctionType::Normal:
// used_declare_format = format_strings::declare_metadata;
// break;
// case FunctionType::Ctor:
// case FunctionType::Dtor:
// used_declare_format = format_strings::declare_metadata_structor;
// break;
// }
// if (fn->beginning.is_static)
// used_declare_format = format_strings::declare_metadata_static;
// output += fmt::format(
// used_declare_format,
// fmt::arg("address", address_str),
// fmt::arg("class_name", c.name),
// fmt::arg("const", str_if(" const ", fn->beginning.is_const)),
// fmt::arg("convention", codegen::getModifyConventionName(field)),
// fmt::arg("return", bank.getReturn(fn->beginning, c.name)),
// fmt::arg("parameters", codegen::getParameters(fn->beginning)),
// fmt::arg("parameter_types", codegen::getParameterTypes(fn->beginning)),
// fmt::arg("arguments", codegen::getParameterNames(fn->beginning)),
// fmt::arg("parameter_comma", str_if(", ", !fn->beginning.args.empty())),
// fmt::arg("index", field.field_id)
// );
// }
// }
// output += format_strings::declare_metadata_end;
return output;
}

View file

@ -1,155 +0,0 @@
#pragma once
#include "Shared.hpp"
#include <unordered_set>
#include <vector>
#include <string>
#include <string_view>
std::string mangleIdent(std::string_view str, bool ne = true) {
if (str.find("::") != -1) {
std::string result = ne ? "N" : "";
auto s = str;
do {
const auto i = s.find("::");
const auto t = s.substr(0, i);
result += std::to_string(t.size()) + std::string(t);
if (i == -1) s = "";
else
s = s.substr(i + 2);
} while(s.size());
return result + (ne ? "E" : "");
} else {
return std::to_string(str.size()) + std::string(str);
}
};
std::string intToString(unsigned int value, unsigned int radix) {
static constexpr char base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::string result;
do {
unsigned int remainder = value % radix;
value /= radix;
result.insert(result.begin(), base36[remainder]);
} while (value);
return result;
}
std::string lookForSeen(std::vector<std::string>& seen, std::string mangled) {
for (int i = 0; i < seen.size(); ++i) {
if (seen[i] == mangled) {
if (i == 0) return "S_";
// yes, its base 36
return "S" + intToString(i - 1, 36) + "_";
}
}
return "";
}
std::string subsSeen(std::vector<std::string>& seen, std::string mangled, bool subs) {
if (!subs) return mangled;
if (mangled.empty()) return mangled;
if (auto x = lookForSeen(seen, mangled); !x.empty()) return x;
seen.push_back(mangled);
return mangled;
}
std::string mangleType(std::vector<std::string>& seen, std::string name, bool subs = true) {
if (name == "int") return "i";
if (name == "float") return "f";
if (name == "bool") return "b";
if (name == "char") return "c";
if (name == "gd::string") return "Ss";
if (name == "cocos2d::ccColor3B") return mangleType(seen, "cocos2d::_ccColor3B", subs);
// too lazy
if (name == "gd::map<gd::string, gd::string>") return "St3mapISsSsSt4lessISsESaISt4pairIKSsSsEEE";
if (name == "cocos2d::SEL_MenuHandler") {
const auto a = mangleType(seen, "cocos2d::CCObject", subs);
const auto b = mangleType(seen, "cocos2d::CCObject*", subs);
const auto fnptr = subsSeen(seen, "Fv" + b + "E", subs);
return subsSeen(seen, "M" + a + fnptr, subs);
}
if (name.find('*') == name.size() - 1) {
auto inner = mangleType(seen, name.substr(0, name.size() - 1), false);
if (auto x = lookForSeen(seen, "P" + inner); !x.empty()) return x;
inner = mangleType(seen, name.substr(0, name.size() - 1), subs);
return subsSeen(seen, "P" + inner, subs);
}
if (name.find('&') == name.size() - 1) {
auto inner = mangleType(seen, name.substr(0, name.size() - 1), false);
if (auto x = lookForSeen(seen, "R" + inner); !x.empty()) return x;
inner = mangleType(seen, name.substr(0, name.size() - 1), subs);
return subsSeen(seen, "R" + inner, subs);
}
if (auto i = name.find("const"); i != -1) {
std::string inner;
// at the end of the name
if (i == name.size() - 5) {
inner = mangleType(seen, name.substr(0, i - 1));
} else if (i == 0) {
inner = mangleType(seen, name.substr(6));
} else {
inner = "v";
std::cout << "um " << name << std::endl;
}
return subsSeen(seen, "K" + inner, subs);
}
if (name.find("::") != -1) {
std::string result = "";
std::string substituted = "";
auto s = name;
do {
const auto i = s.find("::");
const auto t = s.substr(0, i);
auto part = std::to_string(t.size()) + std::string(t);
if (auto x = lookForSeen(seen, result + part); !x.empty()) {
substituted = x;
} else {
substituted = subsSeen(seen, substituted + part, subs);
}
result += part;
if (i == -1) s = "";
else s = s.substr(i + 2);
} while(s.size());
if (substituted.size() == 3 && substituted[0] == 'S')
return substituted;
return "N" + substituted + "E";
} else {
return subsSeen(seen, mangleIdent(name), subs);
}
};
std::string generateAndroidSymbol(const Class& clazz, const FunctionBindField* fn) {
auto& decl = fn->prototype;
std::string mangledSymbol;
switch (decl.type) {
case FunctionType::Ctor:
mangledSymbol = "_ZN" + mangleIdent(clazz.name, false) + "C2E";
break;
case FunctionType::Dtor:
mangledSymbol = "_ZN" + mangleIdent(clazz.name, false) + "D2E";
break;
default:
mangledSymbol = "_Z" + mangleIdent(clazz.name + "::" + decl.name);
break;
}
if (decl.args.empty()) {
mangledSymbol += "v";
} else {
std::vector<std::string> seen;
static constexpr auto firstPart = [](std::string_view str, std::string_view sep) {
return str.substr(0, str.find(sep));
};
// this is S_
seen.push_back(mangleIdent(firstPart(clazz.name, "::")));
for (auto& [ty, _] : decl.args) {
mangledSymbol += mangleType(seen, ty.name);
}
}
return mangledSymbol;
}

View file

@ -1,277 +0,0 @@
#include "Shared.hpp"
#include <iostream>
#include <set>
namespace { namespace format_strings {
// requires: base_classes, class_name
char const* binding_include = R"GEN(#include "binding/{file_name}"
)GEN";
char const* class_includes = R"GEN(#pragma once
#include <stdexcept>
#include <Geode/platform/platform.hpp>
#include <Geode/c++stl/gdstdlib.hpp>
#include <cocos2d.h>
#include <cocos-ext.h>
#include <Geode/GeneratedPredeclare.hpp>
#include <Geode/Enums.hpp>
#include <Geode/utils/SeedValue.hpp>
)GEN";
char const* class_no_includes = R"GEN(#pragma once
#include <Geode/platform/platform.hpp>
#include <stdexcept>
)GEN";
char const* class_include_prereq = R"GEN(#include "{file_name}"
)GEN";
char const* class_start = R"GEN(
class {class_name}{base_classes} {{
public:
static constexpr auto CLASS_NAME = "{class_name}";
)GEN";
char const* custom_constructor = R"GEN( GEODE_CUSTOM_CONSTRUCTOR_GD({class_name}, {first_base})
)GEN";
char const* custom_constructor_cutoff = R"GEN( GEODE_CUSTOM_CONSTRUCTOR_CUTOFF({class_name}, {first_base})
)GEN";
char const* function_definition = R"GEN(
/**
{docs}{docs_addresses} */
{static}{virtual}{return_type} {function_name}({parameters}){const};
)GEN";
char const* error_definition = R"GEN(
private:
[[deprecated("{class_name}::{function_name} not implemented")]]
/**
{docs}{docs_addresses} */
{static}{virtual}{return_type} {function_name}({parameters}){const};
public:
)GEN";
char const* structor_definition = R"GEN(
/**
{docs}{docs_addresses} */
{function_name}({parameters});
)GEN";
// requires: type, member_name, array
char const* member_definition = R"GEN({private} {type} {member_name};{public}
)GEN";
char const* pad_definition = R"GEN( GEODE_PAD({hardcode});
)GEN";
char const* class_end = R"GEN(};
)GEN";
}}
inline std::string nameForPlatform(Platform platform) {
switch (platform) {
case Platform::Mac: return "MacOS";
case Platform::Windows: return "Windows";
case Platform::iOS: return "iOS";
case Platform::Android: return "Android";
default: // unreachable
return "Windows";
}
}
template <class T>
std::string generateAddressDocs(T const& f, PlatformNumber pn) {
std::string ret;
for (auto platform : {Platform::Mac, 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)
);
}
}
return ret;
}
std::string generateDocs(std::string const& docs) {
if (docs.size() < 7) return "";
auto ret = docs.substr(1, docs.size() - 6); // i hate this but idk how to generalize
for (auto next = ret.find(" "); next != std::string::npos; next = ret.find(" ")) {
ret.replace(next, 8, " * ");
}
return ret;
}
std::string generateBindingHeader(Root const& root, ghc::filesystem::path const& singleFolder) {
std::string output;
{
std::string filename = "Standalones.hpp";
output += fmt::format(format_strings::binding_include,
fmt::arg("file_name", filename)
);
std::string single_output;
single_output += format_strings::class_includes;
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
FunctionProto const* fb = &f.prototype;
char const* used_format = format_strings::function_definition;
std::string addressDocs = generateAddressDocs(f, f.binds);
std::string docs = generateDocs(fb->docs);
single_output += fmt::format(used_format,
fmt::arg("virtual", ""),
fmt::arg("static", ""),
fmt::arg("class_name", ""),
fmt::arg("const", ""),
fmt::arg("function_name", fb->name),
fmt::arg("parameters", codegen::getParameters(*fb)),
fmt::arg("return_type", fb->ret.name),
fmt::arg("docs_addresses", addressDocs),
fmt::arg("docs", docs)
);
}
writeFile(singleFolder / filename, single_output);
}
for (auto& cls : root.classes) {
if (is_cocos_class(cls.name))
continue;
std::string filename = (codegen::getUnqualifiedClassName(cls.name) + ".hpp");
output += fmt::format(format_strings::binding_include,
fmt::arg("file_name", filename)
);
std::string single_output;
if (cls.name != "GDString") {
single_output += format_strings::class_includes;
} else {
single_output += format_strings::class_no_includes;
}
for (auto dep : cls.depends) {
if (can_find(dep, "cocos2d::")) continue;
std::string depfilename = (codegen::getUnqualifiedClassName(dep) + ".hpp");
single_output += fmt::format(format_strings::class_include_prereq, fmt::arg("file_name", depfilename));
}
std::string supers = str_if(
fmt::format(" : public {}", fmt::join(cls.superclasses, ", public ")),
!cls.superclasses.empty()
);
single_output += fmt::format(::format_strings::class_start,
fmt::arg("class_name", cls.name),
fmt::arg("base_classes", supers)//,
// fmt::arg("hidden", str_if("GEODE_HIDDEN ", (codegen::platform & (Platform::Mac | Platform::iOS)) != Platform::None))
);
// what.
if (!cls.superclasses.empty()) {
single_output += fmt::format(
is_cocos_class(cls.superclasses[0])
? format_strings::custom_constructor_cutoff
: format_strings::custom_constructor,
fmt::arg("class_name", cls.name),
fmt::arg("first_base", cls.superclasses[0])
);
}
bool unimplementedField = false;
for (auto field : cls.fields) {
if (codegen::getStatus(field) == BindStatus::Missing) continue;
MemberFunctionProto* fb;
char const* used_format = format_strings::function_definition;
std::string addressDocs;
if (auto i = field.get_as<InlineField>()) {
single_output += "\t" + i->inner + "\n";
continue;
} else if (auto m = field.get_as<MemberField>()) {
single_output += fmt::format(format_strings::member_definition,
fmt::arg("private", unimplementedField ? "private:\n" : ""),
fmt::arg("public", unimplementedField ? "\npublic:" : ""),
fmt::arg("type", m->type.name),
fmt::arg("member_name", m->name + str_if(fmt::format("[{}]", m->count), m->count))
);
continue;
} else if (auto p = field.get_as<PadField>()) {
auto hardcode = codegen::platformNumber(p->amount);
if (hardcode > 0) {
single_output += fmt::format(format_strings::pad_definition, fmt::arg("hardcode", hardcode));
}
else if (hardcode == 0) {
single_output += " // no padding\n";
}
else {
unimplementedField = true;
}
continue;
} else if (auto fn = field.get_as<OutOfLineField>()) {
fb = &fn->prototype;
addressDocs = " * @note[short] Out of line\n";
} else if (auto fn = field.get_as<FunctionBindField>()) {
fb = &fn->prototype;
if (codegen::platformNumber(fn->binds) == -1 && codegen::getStatus(field) != BindStatus::Binded) {
used_format = format_strings::error_definition;
if (fb->type != FunctionType::Normal)
continue;
}
addressDocs = generateAddressDocs(field, fn->binds);
}
std::string docs = generateDocs(fb->docs);
single_output += fmt::format(used_format,
fmt::arg("virtual", str_if("virtual ", fb->is_virtual)),
fmt::arg("static", str_if("static ", fb->is_static)),
fmt::arg("class_name", cls.name),
fmt::arg("const", str_if(" const ", fb->is_const)),
fmt::arg("function_name", fb->name),
fmt::arg("parameters", codegen::getParameters(*fb)),
fmt::arg("return_type", fb->ret.name),
fmt::arg("docs_addresses", addressDocs),
fmt::arg("docs", docs)
);
}
// if (hasClass)
single_output += ::format_strings::class_end;
writeFile(singleFolder / filename, single_output);
}
return output;
}

View file

@ -1,52 +0,0 @@
#include "Shared.hpp"
#include <ghc/filesystem.hpp> // bruh
using namespace codegen;
std::map<void const*, size_t> codegen::idMap;
int main(int argc, char** argv) try {
if (argc != 4)
throw codegen::error("Invalid number of parameters (expected 3 found {})", argc - 1);
std::string p = argv[1];
if (p == "Win32") codegen::platform = Platform::Windows;
else if (p == "MacOS") codegen::platform = Platform::Mac;
else if (p == "iOS") codegen::platform = Platform::iOS;
else if (p == "Android") codegen::platform = Platform::Android;
else throw codegen::error("Invalid platform {}\n", p);
auto rootDir = ghc::filesystem::path(argv[2]);
ghc::filesystem::current_path(rootDir);
auto writeDir = ghc::filesystem::path(argv[3]) / "Geode";
ghc::filesystem::create_directories(writeDir);
ghc::filesystem::create_directories(writeDir / "modify");
ghc::filesystem::create_directories(writeDir / "binding");
Root root = broma::parse_file("Entry.bro");
for (auto cls : root.classes) {
for (auto dep : cls.depends) {
if (!is_cocos_class(dep) &&
std::find(root.classes.begin(), root.classes.end(), dep) == root.classes.end()) {
throw codegen::error("Class {} depends on unknown class {}", cls.name, dep);
}
}
}
codegen::populateIds(root);
writeFile(writeDir / "GeneratedAddress.cpp", generateAddressHeader(root));
writeFile(writeDir / "GeneratedModify.hpp", generateModifyHeader(root, writeDir / "modify"));
writeFile(writeDir / "GeneratedBinding.hpp", generateBindingHeader(root, writeDir / "binding"));
writeFile(writeDir / "GeneratedPredeclare.hpp", generatePredeclareHeader(root));
writeFile(writeDir / "GeneratedSource.cpp", generateBindingSource(root));
}
catch (std::exception& e) {
std::cout << "Codegen error: " << e.what() << "\n";
return 1;
}

View file

@ -1,155 +0,0 @@
#include "Shared.hpp"
#include <iostream>
#include <set>
namespace {
namespace format_strings {
// requires: class_name, class_include
char const* modify_start = R"GEN(#pragma once
#include <Geode/modify/Modify.hpp>
#include <Geode/modify/Field.hpp>
#include <Geode/modify/Addresses.hpp>
{class_include}
using namespace geode::modifier;
namespace geode::modifier {{
{statics}
template<class Der>
struct ModifyDerive<Der, {class_name}> : ModifyBase<ModifyDerive<Der, {class_name}>> {{
using BaseModify = ModifyBase<ModifyDerive<Der, {class_name}>>;
using ModifyBase<ModifyDerive<Der, {class_name}>>::ModifyBase;
using Base = {class_name};
using Derived = Der;
void apply() override {{
using namespace geode::core::meta;
)GEN";
char const* statics_declare_identifier = R"GEN(
#ifndef GEODE_STATICS_{function_name}
#define GEODE_STATICS_{function_name}
GEODE_AS_STATIC_FUNCTION({function_name})
#endif
)GEN";
// requires: index, class_name, arg_types, function_name, raw_arg_types, non_virtual
char const* apply_function = R"GEN(
GEODE_APPLY_MODIFY_FOR_FUNCTION({addr_index}, {function_convention}, {class_name}, {function_name}, {parameter_types}))GEN";
char const* apply_constructor = R"GEN(
GEODE_APPLY_MODIFY_FOR_CONSTRUCTOR({addr_index}, {function_convention}, {class_name}, {parameter_types}))GEN";
char const* apply_destructor = R"GEN(
GEODE_APPLY_MODIFY_FOR_DESTRUCTOR({addr_index}, {function_convention}, {class_name}))GEN";
char const* modify_end = R"GEN(
}
};
}
)GEN";
char const* modify_include = R"GEN(#include "modify/{file_name}"
)GEN";
}
}
std::string generateModifyHeader(Root const& root, ghc::filesystem::path const& singleFolder) {
std::string output;
for (auto& c : root.classes) {
if (c.name == "cocos2d") continue;
std::string filename = (codegen::getUnqualifiedClassName(c.name) + ".hpp");
output += fmt::format(format_strings::modify_include, fmt::arg("file_name", filename));
std::string single_output;
std::string class_include;
if (c.name.find("cocos2d::extension") != std::string::npos) {
class_include = "#include <cocos-ext.h>";
}
else if (is_cocos_class(c.name)) {
class_include = "#include <cocos2d.h>";
}
else {
class_include = fmt::format(
"#include <Geode/binding/{class_name}.hpp>",
fmt::arg("class_name", codegen::getUnqualifiedClassName(c.name))
);
}
std::string statics;
std::set<std::string> used;
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (auto fn = f.get_as<FunctionBindField>()) {
if (fn->prototype.type == FunctionType::Normal && !used.count(fn->prototype.name)) {
used.insert(fn->prototype.name);
statics += fmt::format(
format_strings::statics_declare_identifier, fmt::arg("function_name", fn->prototype.name)
);
}
}
}
single_output += fmt::format(
format_strings::modify_start,
fmt::arg("statics", statics),
fmt::arg("class_name", c.name),
fmt::arg("class_include", class_include)
);
// modify
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
auto fn = f.get_as<FunctionBindField>();
if (!fn) {
continue;
}
if (codegen::getStatus(f) == BindStatus::NeedsBinding || codegen::platformNumber(f) != -1) {
}
else if (codegen::getStatus(f) == BindStatus::Binded && fn->prototype.type == FunctionType::Normal) {
}
else {
continue;
}
std::string format_string;
switch (fn->prototype.type) {
case FunctionType::Normal:
format_string = format_strings::apply_function;
break;
case FunctionType::Ctor:
format_string = format_strings::apply_constructor;
break;
case FunctionType::Dtor:
format_string = format_strings::apply_destructor;
break;
}
single_output += fmt::format(
format_string,
fmt::arg("addr_index", codegen::getId(&f)),
fmt::arg("class_name", c.name),
fmt::arg("function_name", fn->prototype.name),
fmt::arg("function_convention", codegen::getModifyConventionName(f)),
fmt::arg("parameter_types", codegen::getParameterTypes(fn->prototype))
);
}
single_output += format_strings::modify_end;
writeFile(singleFolder / filename, single_output);
}
return output;
}

View file

@ -1,22 +0,0 @@
#include "Shared.hpp"
#include <iostream>
#include <set>
namespace { namespace format_strings {
char const* class_predeclare = "class {class_name};\n";
}}
std::string generatePredeclareHeader(Root const& root) {
std::string output("#pragma once\n");
for (auto& cls : root.classes) {
if (is_cocos_class(cls.name))
continue;
output += fmt::format(::format_strings::class_predeclare,
fmt::arg("class_name", cls.name)
);
}
return output;
}

View file

@ -1,229 +0,0 @@
#pragma once
#include <array>
#include <broma.hpp>
#include <fmt/format.h>
#include <fmt/ranges.h>
#include <fstream>
#include <ghc/filesystem.hpp>
using std::istreambuf_iterator;
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
using namespace broma;
std::string generateAddressHeader(Root const& root);
std::string generateModifyHeader(Root const& root, ghc::filesystem::path const& singleFolder);
std::string generateBindingHeader(Root const& root, ghc::filesystem::path const& singleFolder);
std::string generatePredeclareHeader(Root const& root);
std::string generateBindingSource(Root const& root);
inline void writeFile(ghc::filesystem::path const& writePath, std::string const& output) {
std::ifstream readfile;
readfile >> std::noskipws;
readfile.open(writePath);
std::string data((std::istreambuf_iterator<char>(readfile)), std::istreambuf_iterator<char>());
readfile.close();
if (data != output) {
std::ofstream writefile;
writefile.open(writePath);
writefile << output;
writefile.close();
}
}
inline std::string str_if(std::string&& str, bool cond) {
return cond ? str : "";
}
inline bool can_find(std::string const& str, char const* text) {
return str.find(text) != std::string::npos;
}
inline bool is_cocos_class(std::string const& str) {
return can_find(str, "cocos2d") || can_find(str, "pugi::") || str == "DS_Dictionary";
}
enum class BindStatus {
Binded,
NeedsBinding,
Unbindable,
Missing,
};
struct codegen_error : std::runtime_error {
inline codegen_error(char const* msg) : std::runtime_error(msg) {}
};
namespace codegen {
extern std::map<void const*, size_t> idMap;
inline void populateIds(Root const& root) {
size_t id = 0;
for (auto& f : root.functions) {
idMap[&f] = id++;
}
for (auto& c : root.classes) {
for (auto& f : c.fields) {
if (auto fn = f.get_as<FunctionBindField>()) {
idMap[&f] = id++;
}
}
}
}
inline size_t getId(Function const* f) {
return idMap[f];
}
inline size_t getId(Field const* f) {
return idMap[f];
}
template <typename... Args>
inline codegen_error error(Args... args) {
return codegen_error(fmt::format(args...).c_str());
}
inline Platform platform;
inline ptrdiff_t platformNumberWithPlatform(Platform p, PlatformNumber const& pn) {
switch (p) {
case Platform::Mac: return pn.mac;
case Platform::Windows: return pn.win;
case Platform::iOS: return pn.ios;
case Platform::Android: return pn.android;
default: // unreachable
return pn.win;
}
}
inline ptrdiff_t platformNumber(PlatformNumber const& p) {
return platformNumberWithPlatform(codegen::platform, p);
}
inline uintptr_t platformNumber(Field const& field) {
if (auto fn = field.get_as<FunctionBindField>()) {
return platformNumberWithPlatform(codegen::platform, fn->binds);
}
return 0;
}
inline BindStatus getStatusWithPlatform(Platform p, Field const& field) {
if ((field.missing & p) != Platform::None) return BindStatus::Missing;
if (auto fn = field.get_as<FunctionBindField>()) {
if ((field.links & p) != Platform::None) return BindStatus::Binded;
if (platformNumberWithPlatform(p, fn->binds) != -1) return BindStatus::NeedsBinding;
}
return BindStatus::Unbindable;
}
inline BindStatus getStatusWithPlatform(Platform p, Function const& f) {
if ((f.missing & p) != Platform::None) return BindStatus::Missing;
if ((f.links & p) != Platform::None) return BindStatus::Binded;
if (platformNumberWithPlatform(p, f.binds) != -1) return BindStatus::NeedsBinding;
return BindStatus::Unbindable;
}
inline bool shouldAndroidBind(const FunctionBindField* fn) {
if (codegen::platform == Platform::Android) {
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(Field const& field) {
return getStatusWithPlatform(codegen::platform, field);
}
inline BindStatus getStatus(Function const& f) {
return getStatusWithPlatform(codegen::platform, f);
}
inline std::string getParameters(FunctionProto const& f) { // int p0, float p1
std::vector<std::string> parameters;
for (auto& [t, n] : f.args) {
parameters.push_back(fmt::format("{} {}", t.name, n));
}
return fmt::format("{}", fmt::join(parameters, ", "));
}
inline std::string getParameterTypes(FunctionProto const& f) { // int, float
std::vector<std::string> parameters;
for (auto& [t, n] : f.args) {
parameters.push_back(t.name);
}
return fmt::format("{}", fmt::join(parameters, ", "));
}
inline std::string getParameterNames(FunctionProto const& f) { // p0, p1
std::vector<std::string> parameters;
for (auto& [t, n] : f.args) {
parameters.push_back(n);
}
return fmt::format("{}", fmt::join(parameters, ", "));
}
inline std::string getModifyConventionName(Field const& f) {
if (codegen::platform != Platform::Windows) return "Default";
if (auto fn = f.get_as<FunctionBindField>()) {
auto status = getStatus(f);
if (fn->prototype.is_static) {
if (status == BindStatus::Binded) return "Cdecl";
else return "Optcall";
}
else if (fn->prototype.is_virtual || fn->prototype.is_callback) {
return "Thiscall";
}
else {
if (status == BindStatus::Binded) return "Thiscall";
else return "Membercall";
}
}
else throw codegen::error("Tried to get convention of non-function");
}
inline std::string getModifyConventionName(Function const& f) {
if (codegen::platform != Platform::Windows) return "Default";
return "Cdecl";
}
inline std::string getConvention(Field const& f) {
if (codegen::platform != Platform::Windows) return "DefaultConv";
return std::string("x86::") + getModifyConventionName(f);
}
inline std::string getModifyConvention(Field const& f) {
if (codegen::platform != Platform::Windows) return "tulip::hook::DefaultConvention";
return std::string("tulip::hook::") + getModifyConventionName(f) + "Convention";
}
inline std::string getUnqualifiedClassName(std::string const& s) {
auto index = s.rfind("::");
if (index == std::string::npos) return s;
return s.substr(index + 2);
}
}

View file

@ -1,245 +0,0 @@
#include "Shared.hpp"
namespace { namespace format_strings {
char const* source_start = R"CAC(
#include <stdexcept>
#include <Geode/Bindings.hpp>
#include <Geode/utils/addresser.hpp>
#include <Geode/modify/Addresses.hpp>
#include <Geode/modify/Traits.hpp>
#include <Geode/loader/Tulip.hpp>
using namespace geode;
using namespace geode::modifier;
using cocos2d::CCDestructor;
std::unordered_map<void*, bool>& CCDestructor::destructorLock() {{
static auto ret = new std::unordered_map<void*, bool>;
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<void*>(address), metadata);
if (wrapped.isErr()) {{
throw std::runtime_error(wrapped.unwrapErr());
}}
return wrapped.unwrap();
}
)CAC";
char const* declare_member = R"GEN(
auto {class_name}::{function_name}({parameters}){const} -> decltype({function_name}({arguments})) {{
using FunctionType = decltype({function_name}({arguments}))(*)({class_name}{const}*{parameter_comma}{parameter_types});
static auto func = wrapFunction(address<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
return reinterpret_cast<FunctionType>(func)(this{parameter_comma}{arguments});
}}
)GEN";
char const* declare_virtual = R"GEN(
auto {class_name}::{function_name}({parameters}){const} -> decltype({function_name}({arguments})) {{
auto self = addresser::thunkAdjust(Resolve<{parameter_types}>::func(&{class_name}::{function_name}), this);
using FunctionType = decltype({function_name}({arguments}))(*)({class_name}{const}*{parameter_comma}{parameter_types});
static auto func = wrapFunction(address<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
return reinterpret_cast<FunctionType>(func)(self{parameter_comma}{arguments});
}}
)GEN";
char const* declare_static = R"GEN(
auto {class_name}::{function_name}({parameters}){const} -> decltype({function_name}({arguments})) {{
using FunctionType = decltype({function_name}({arguments}))(*)({parameter_types});
static auto func = wrapFunction(address<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
return reinterpret_cast<FunctionType>(func)({arguments});
}}
)GEN";
char const* declare_destructor = 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<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
reinterpret_cast<FunctionType>(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;
}}
)GEN";
char const* declare_constructor = R"GEN(
{class_name}::{function_name}({parameters}) : {class_name}(geode::CutoffConstructor, sizeof({class_name})) {{
// 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;
{class_name}::~{unqualified_class_name}();
using FunctionType = void(*)({class_name}*{parameter_comma}{parameter_types});
static auto func = wrapFunction(address<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
reinterpret_cast<FunctionType>(func)(this{parameter_comma}{arguments});
}}
)GEN";
char const* declare_virtual_error = R"GEN(
auto {class_name}::{function_name}({parameters}){const} -> decltype({function_name}({arguments})) {{
throw std::runtime_error("{class_name}::{function_name} not implemented");
}}
)GEN";
char const* ool_function_definition = R"GEN(
{return} {class_name}::{function_name}({parameters}){const} {definition}
)GEN";
char const* ool_structor_function_definition = R"GEN(
{class_name}::{function_name}({parameters}){const} {definition}
)GEN";
char const* declare_standalone = R"GEN(
auto {function_name}({parameters}) -> decltype({function_name}({arguments})) {{
using FunctionType = decltype({function_name}({arguments}))(*)({parameter_types});
static auto func = wrapFunction(address<{addr_index}>(), tulip::hook::WrapperMetadata{{
.m_convention = geode::hook::createConvention(tulip::hook::TulipConvention::{convention}),
.m_abstract = tulip::hook::AbstractFunction::from(FunctionType(nullptr)),
}});
return reinterpret_cast<FunctionType>(func)({arguments});
}}
)GEN";
}}
std::string generateBindingSource(Root const& root) {
std::string output(format_strings::source_start);
for (auto& f : root.functions) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (codegen::getStatus(f) != BindStatus::NeedsBinding) {
continue;
}
output += fmt::format(format_strings::declare_standalone,
fmt::arg("convention", codegen::getModifyConventionName(f)),
fmt::arg("function_name", f.prototype.name),
fmt::arg("addr_index", codegen::getId(&f)),
fmt::arg("parameters", codegen::getParameters(f.prototype)),
fmt::arg("parameter_types", codegen::getParameterTypes(f.prototype)),
fmt::arg("arguments", codegen::getParameterNames(f.prototype)),
fmt::arg("parameter_comma", str_if(", ", !f.prototype.args.empty()))
);
}
for (auto& c : root.classes) {
for (auto& f : c.fields) {
if (codegen::getStatus(f) == BindStatus::Missing) continue;
if (auto i = f.get_as<InlineField>()) {
// yeah there are no inlines on cocos
}
else if (auto fn = f.get_as<OutOfLineField>()) {
if (is_cocos_class(c.name) && (c.links & codegen::platform) != Platform::None) {
continue;
}
if (codegen::getStatus(f) != BindStatus::Unbindable) {
continue;
}
switch (fn->prototype.type) {
case FunctionType::Ctor:
case FunctionType::Dtor:
output += fmt::format(format_strings::ool_structor_function_definition,
fmt::arg("function_name", fn->prototype.name),
fmt::arg("const", str_if(" const ", fn->prototype.is_const)),
fmt::arg("class_name", c.name),
fmt::arg("parameters", codegen::getParameters(fn->prototype)),
fmt::arg("definition", fn->inner)
);
break;
default:
output += fmt::format(format_strings::ool_function_definition,
fmt::arg("function_name", fn->prototype.name),
fmt::arg("const", str_if(" const ", fn->prototype.is_const)),
fmt::arg("class_name", c.name),
fmt::arg("parameters", codegen::getParameters(fn->prototype)),
fmt::arg("definition", fn->inner),
fmt::arg("return", fn->prototype.ret.name)
);
break;
}
}
else if (auto fn = f.get_as<FunctionBindField>()) {
char const* used_declare_format = nullptr;
if (
codegen::getStatus(f) == BindStatus::Unbindable &&
codegen::platformNumber(fn->binds) == -1 &&
fn->prototype.is_virtual && fn->prototype.type != FunctionType::Dtor
) {
used_declare_format = format_strings::declare_virtual_error;
}
else if (codegen::getStatus(f) != BindStatus::NeedsBinding && !codegen::shouldAndroidBind(fn)) {
continue;
}
if (!used_declare_format) {
switch (fn->prototype.type) {
case FunctionType::Normal:
used_declare_format = format_strings::declare_member;
break;
case FunctionType::Ctor:
used_declare_format = format_strings::declare_constructor;
break;
case FunctionType::Dtor:
used_declare_format = format_strings::declare_destructor;
break;
}
if (fn->prototype.is_static)
used_declare_format = format_strings::declare_static;
if (fn->prototype.is_virtual && fn->prototype.type != FunctionType::Dtor)
used_declare_format = format_strings::declare_virtual;
}
output += fmt::format(used_declare_format,
fmt::arg("class_name", c.name),
fmt::arg("unqualified_class_name", codegen::getUnqualifiedClassName(c.name)),
fmt::arg("const", str_if(" const ", fn->prototype.is_const)),
fmt::arg("convention", codegen::getModifyConventionName(f)),
fmt::arg("function_name", fn->prototype.name),
fmt::arg("addr_index", codegen::getId(&f)),
fmt::arg("parameters", codegen::getParameters(fn->prototype)),
fmt::arg("parameter_types", codegen::getParameterTypes(fn->prototype)),
fmt::arg("arguments", codegen::getParameterNames(fn->prototype)),
fmt::arg("parameter_comma", str_if(", ", !fn->prototype.args.empty()))
);
}
}
}
return output;
}