mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
move bindings to their own separate repo
This commit is contained in:
parent
151303c696
commit
3aa3ae7dce
13 changed files with 19 additions and 9000 deletions
|
@ -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
|
||||
|
|
1315
bindings/Cocos2d.bro
1315
bindings/Cocos2d.bro
File diff suppressed because it is too large
Load diff
|
@ -1,2 +0,0 @@
|
|||
#include <Cocos2d.bro>
|
||||
#include <GeometryDash.bro>
|
File diff suppressed because it is too large
Load diff
|
@ -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})
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue