#include "NativeAPI.h" #include "LiteLoader/include/llapi/HookAPI.h" #include #include "../Tools/Demangler/include/MicrosoftDemangle.h" Concurrency::concurrent_unordered_map NativeFunction::parsedSymbol; #define NATIVE_CHECK_ARG_TYPE(TYPE) \ if (args[i].getKind() != TYPE) { \ logger.error("Wrong type of argument!"); \ logger.error("In Symbol: " + funcSymbol->mSymbol); \ logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \ return Local(); \ } #define NATIVE_CHECK_ARG_CLASS(TYPE) \ if (!IsInstanceOf(args[i])) { \ logger.error("Wrong type of argument!"); \ logger.error("In Symbol: " + funcSymbol->mSymbol); \ logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \ return Local(); \ } Local NativeFunction::callNativeFunction(DCCallVM* vm, NativeFunction* funcSymbol, const Arguments& args) { if (args.size() < funcSymbol->mParams.size()) { logger.error("Too Few arguments!"); logger.error("In Component: NativeCall"); logger.error("In Symbol: " + funcSymbol->mSymbol); logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); return Local(); } dcReset(vm); for (int i = 0; i < funcSymbol->mParams.size(); ++i) { switch (funcSymbol->mParams[i]) { case NativeFunction::Types::Bool: NATIVE_CHECK_ARG_TYPE(ValueKind::kBoolean); dcArgChar(vm, args[i].asBoolean().value()); break; case NativeFunction::Types::Char: case NativeFunction::Types::UnsignedChar: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgChar(vm, (char)args[i].asNumber().toInt32()); break; case NativeFunction::Types::Short: case NativeFunction::Types::UnsignedShort: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgShort(vm, (short)args[i].asNumber().toInt32()); break; case NativeFunction::Types::Int: case NativeFunction::Types::UnsignedInt: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgInt(vm, (int)args[i].asNumber().toInt32()); break; case NativeFunction::Types::Long: case NativeFunction::Types::UnsignedLong: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgLong(vm, (long)args[i].asNumber().toInt32()); break; case NativeFunction::Types::LongLong: case NativeFunction::Types::UnsignedLongLong: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgLongLong(vm, args[i].asNumber().toInt64()); break; case NativeFunction::Types::Float: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgFloat(vm, args[i].asNumber().toFloat()); break; case NativeFunction::Types::Double: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgDouble(vm, args[i].asNumber().toDouble()); break; case NativeFunction::Types::Pointer: switch (args[i].getKind()) { case ValueKind::kObject: NATIVE_CHECK_ARG_CLASS(NativePointer); dcArgPointer(vm, NativePointer::extract(args[i])); break; case ValueKind::kNumber: NATIVE_CHECK_ARG_TYPE(ValueKind::kNumber); dcArgPointer(vm, (void*)args[i].asNumber().toInt64()); break; case ValueKind::kNull: default: dcArgPointer(vm, nullptr); break; } break; default: break; } } Local res; DCpointer func = (DCpointer)funcSymbol->mFunction; switch (funcSymbol->mReturnVal) { case NativeFunction::Types::Void: dcCallVoid(vm, func); break; case NativeFunction::Types::Bool: { // in c, bool is the alias of int // [c]#define bool int, sizeof(bool) == 4 // but in cpp, bool represent a true/false value // it'a a builtin type, sizeof(bool) == sizeof(char) == 1 res = Boolean::newBoolean(dcCallChar(vm, func)); break; } case NativeFunction::Types::Char: case NativeFunction::Types::UnsignedChar: res = Number::newNumber(dcCallChar(vm, func)); break; case NativeFunction::Types::Short: case NativeFunction::Types::UnsignedShort: res = Number::newNumber(dcCallShort(vm, func)); break; case NativeFunction::Types::Int: case NativeFunction::Types::UnsignedInt: res = Number::newNumber(dcCallInt(vm, func)); break; case NativeFunction::Types::Long: case NativeFunction::Types::UnsignedLong: res = Number::newNumber((int)dcCallLong(vm, func)); break; case NativeFunction::Types::LongLong: case NativeFunction::Types::UnsignedLongLong: res = Number::newNumber(dcCallLongLong(vm, func)); break; case NativeFunction::Types::Float: res = Number::newNumber(dcCallFloat(vm, func)); break; case NativeFunction::Types::Double: res = Number::newNumber(dcCallDouble(vm, func)); break; case NativeFunction::Types::Pointer: res = NativePointer::newNativePointer((NativePointer*)dcCallPointer(vm, func)); break; default: break; } return res; } char NativeFunction::getTypeSignature(NativeFunction::Types type) { switch (type) { case NativeFunction::Types::Bool: return 'B'; break; case NativeFunction::Types::Char: return 'c'; break; case NativeFunction::Types::UnsignedChar: return 'C'; break; case NativeFunction::Types::Short: return 's'; break; case NativeFunction::Types::UnsignedShort: return 'S'; break; case NativeFunction::Types::Int: return 'i'; break; case NativeFunction::Types::UnsignedInt: return 'I'; break; case NativeFunction::Types::Long: return 'j'; break; case NativeFunction::Types::UnsignedLong: return 'J'; break; case NativeFunction::Types::LongLong: return 'l'; break; case NativeFunction::Types::UnsignedLongLong: return 'L'; break; case NativeFunction::Types::Float: return 'f'; break; case NativeFunction::Types::Double: return 'd'; break; case NativeFunction::Types::Pointer: return 'p'; break; default: return 'v'; break; } } inline std::string NativeFunction::buildDynCallbackSig() { std::string result; size_t params_size = mParams.size(); result.reserve(params_size + 2); for (int i = 0; i < params_size; ++i) { result[i] = getTypeSignature(mParams[i]); } result[params_size + 0] = ')'; result[params_size + 1] = getTypeSignature(mReturnVal); return result; } NativeFunction::Types NativeFunction::getNativeType(llvm::ms_demangle::Node* type) { using namespace llvm::ms_demangle; if (type) { if (type->kind() == NodeKind::PrimitiveType) { switch (((PrimitiveTypeNode*)type)->PrimKind) { case PrimitiveKind::Bool: return NativeFunction::Types::Bool; break; case PrimitiveKind::Char: return NativeFunction::Types::Char; break; case PrimitiveKind::Uchar: return NativeFunction::Types::UnsignedChar; break; case PrimitiveKind::Short: return NativeFunction::Types::Short; break; case PrimitiveKind::Ushort: return NativeFunction::Types::UnsignedShort; break; case PrimitiveKind::Int: return NativeFunction::Types::Int; break; case PrimitiveKind::Uint: return NativeFunction::Types::UnsignedInt; break; case PrimitiveKind::Long: return NativeFunction::Types::Long; break; case PrimitiveKind::Ulong: return NativeFunction::Types::UnsignedLong; break; case PrimitiveKind::Int64: return NativeFunction::Types::LongLong; break; case PrimitiveKind::Uint64: return NativeFunction::Types::UnsignedLongLong; break; case PrimitiveKind::Float: return NativeFunction::Types::Float; break; case PrimitiveKind::Double: return NativeFunction::Types::Double; break; default: return NativeFunction::Types::Void; } } else if (type->kind() == NodeKind::PointerType || type->kind() == NodeKind::NamedIdentifier) { return NativeFunction::Types::Pointer; } else if (type->kind() == NodeKind::TagType) { switch (((TagTypeNode*)type)->Tag) { case TagKind::Enum: return NativeFunction::Types::Int; default: return NativeFunction::Types::Pointer; } } } return NativeFunction::Types::Void; } NativeFunction NativeFunction::getOrParse(const std::string& symbol) { // query cache if (auto iter = parsedSymbol.find(symbol); iter != parsedSymbol.end()) { return iter->second; // return by copy } // no cache found using namespace llvm::ms_demangle; NativeFunction result; result.mFunction = dlsym_real(symbol.c_str()); result.mSymbol = symbol; Demangler demangler; StringView symbolView{symbol.c_str()}; SymbolNode* AST = demangler.parse(symbolView); if (demangler.Error) throw std::runtime_error("failed to demangle symbol"); if (AST->kind() != NodeKind::FunctionSymbol) throw std::runtime_error("mangled name is not a function"); auto funcAST = static_cast(AST); result.mReturnVal = getNativeType(funcAST->Signature->ReturnType); if (funcAST->Signature->Quals & Q_Pointer64) result.mParams.push_back(NativeFunction::Types::Pointer); if (funcAST->Signature->Params) { result.mParams.reserve(funcAST->Signature->Params->Count + 1); for (auto i = 0; i < funcAST->Signature->Params->Count; ++i) { result.mParams.push_back(getNativeType(funcAST->Signature->Params->Nodes[i])); } } // push into cache parsedSymbol.insert({symbol, result}); return result; } Local ScriptNativeFunction::fromSymbol(const Arguments& args) { using namespace llvm::ms_demangle; CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); const std::string symbol = args[0].toStr(); auto scriptResult = args.engine()->newNativeClass(); ScriptNativeFunction* result = args.engine()->getNativeInstance(scriptResult); result->cloneFrom(getOrParse(symbol)); return scriptResult; } Local ScriptNativeFunction::fromDescription(const Arguments& args) { auto scriptResult = args.engine()->newNativeClass(); ScriptNativeFunction* result = args.engine()->getNativeInstance(scriptResult); CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); size_t argsSize = args.size(); result->mReturnVal = magic_enum::enum_cast(args[0].toInt()).value_or(NativeFunction::Types::Void); for (size_t i = 1; i < argsSize; i++) { CHECK_ARG_TYPE(args[i], ValueKind::kNumber); result->mParams.push_back(magic_enum::enum_cast(args[i].toInt()).value_or(NativeFunction::Types::Void)); } return scriptResult; } Local ScriptNativeFunction::fromScript(const Arguments& args) { CHECK_ARG_TYPE(args[args.size() - 1], ValueKind::kFunction); auto scriptResult = args.engine()->newNativeClass(args.thiz()); DynamicHookData* nativeScriptFunction = args.engine()->getNativeInstance(scriptResult); CHECK_ARG_TYPE(args[0], ValueKind::kNumber); size_t argsSize = args.size(); nativeScriptFunction->mReturnVal = magic_enum::enum_cast(args[0].toInt()).value_or(NativeFunction::Types::Void); for (size_t i = 1; i < argsSize - 1; i++) { CHECK_ARG_TYPE(args[i], ValueKind::kNumber); nativeScriptFunction->mParams.push_back(magic_enum::enum_cast(args[i].toInt()).value_or(NativeFunction::Types::Void)); } nativeScriptFunction->mEngine = args.engine(); nativeScriptFunction->mNativeCallack = dcbNewCallback(nativeScriptFunction->buildDynCallbackSig().c_str(), &nativeCallbackHandler, nativeScriptFunction); nativeScriptFunction->mFunction = nativeScriptFunction->mNativeCallack; nativeScriptFunction->mScriptCallback = args[args.size() - 1].asFunction(); return scriptResult; } Local NativeFunction::getCallableFunction() { return Function::newFunction([this](const Arguments& args) -> Local { return callNativeFunction(ENGINE_OWN_DATA()->dynamicCallVM, this, args); }); } char NativeFunction::nativeCallbackHandler(DCCallback* cb, DCArgs* args, DCValue* result, void* userdata) { DynamicHookData* hookInfo = (DynamicHookData*)userdata; EngineScope enter(hookInfo->mEngine); try { vector> paras; for (int i = 0; i < hookInfo->mParams.size(); ++i) { switch (hookInfo->mParams[i]) { case NativeFunction::Types::Bool: paras.emplace_back(Boolean::newBoolean(dcbArgBool(args))); break; case NativeFunction::Types::Char: paras.emplace_back(Number::newNumber(dcbArgChar(args))); break; case NativeFunction::Types::UnsignedChar: paras.emplace_back(Number::newNumber(dcbArgUChar(args))); break; case NativeFunction::Types::Short: paras.emplace_back(Number::newNumber(dcbArgShort(args))); break; case NativeFunction::Types::UnsignedShort: paras.emplace_back(Number::newNumber(dcbArgUShort(args))); break; case NativeFunction::Types::Int: paras.emplace_back(Number::newNumber(dcbArgInt(args))); break; case NativeFunction::Types::UnsignedInt: paras.emplace_back(Number::newNumber((int)dcbArgUInt(args))); break; case NativeFunction::Types::Long: paras.emplace_back(Number::newNumber((int)dcbArgLong(args))); break; case NativeFunction::Types::UnsignedLong: paras.emplace_back(Number::newNumber((int)dcbArgULong(args))); break; case NativeFunction::Types::LongLong: paras.emplace_back(Number::newNumber(dcbArgLongLong(args))); break; case NativeFunction::Types::UnsignedLongLong: paras.emplace_back(Number::newNumber((long long)dcbArgULongLong(args))); break; case NativeFunction::Types::Float: paras.emplace_back(Number::newNumber(dcbArgFloat(args))); break; case NativeFunction::Types::Double: paras.emplace_back(Number::newNumber(dcbArgDouble(args))); break; case NativeFunction::Types::Pointer: paras.emplace_back(NativePointer::newNativePointer((NativePointer*)dcbArgPointer(args))); break; default: break; } } Local res = hookInfo->mScriptCallback.get().call({}, paras); switch (hookInfo->mReturnVal) { case NativeFunction::Types::Bool: result->c = res.asBoolean().value();// pass bool by char break; case NativeFunction::Types::Char: result->c = (char)res.toInt(); break; case NativeFunction::Types::UnsignedChar: result->C = (unsigned char)res.toInt(); break; case NativeFunction::Types::Short: result->s = (short)res.toInt(); break; case NativeFunction::Types::UnsignedShort: result->S = (unsigned short)res.toInt(); break; case NativeFunction::Types::Int: result->i = (int)res.toInt(); break; case NativeFunction::Types::UnsignedInt: result->I = (unsigned int)res.toInt(); break; case NativeFunction::Types::Long: result->j = (long)res.toInt(); break; case NativeFunction::Types::UnsignedLong: result->J = (unsigned long)res.toInt(); break; case NativeFunction::Types::LongLong: result->l = (long long)res.asNumber().toInt64(); break; case NativeFunction::Types::UnsignedLongLong: result->L = (unsigned long long)res.asNumber().toInt64(); break; case NativeFunction::Types::Float: result->f = res.asNumber().toFloat(); break; case NativeFunction::Types::Double: result->d = res.asNumber().toDouble(); break; case NativeFunction::Types::Pointer: switch (res.getKind()) { case ValueKind::kObject: result->p = NativePointer::extract(res); break; case ValueKind::kNumber: result->p = (void*)res.asNumber().toInt64(); break; case ValueKind::kNull: default: result->p = nullptr; break; } break; default: break; } } catch (const Exception& e) { logger.error("Hook Callback Failed!"); logger.error("Message: {}",e.message()); logger.error("StackTrace: {}", e.stacktrace()); logger.error("In Symbol: " + hookInfo->mSymbol); logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); } return NativeFunction::getTypeSignature(hookInfo->mReturnVal); } Local ScriptNativeFunction::setAddress(const Local& value) { auto engine = this->getScriptEngine(); bool isNativePointer = engine->isInstanceOf(value); bool isInt64 = value.getKind() == ValueKind::kNumber; if (isNativePointer) { NativePointer* ptr = engine->getNativeInstance(value); this->mFunction = ptr->unwrap(); return Local(); } if (isInt64) { *((__int64*)&this->mFunction) = value.asNumber().toInt64(); return Local(); } LOG_WRONG_ARG_TYPE(); return Local(); } Local ScriptNativeFunction::getAddress() { try { return Number::newNumber(*((__int64*)&this->mFunction)); } CATCH("Fail in getUchar!") } Local ScriptNativeFunction::hook(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kFunction); auto scriptResult = args.engine()->newNativeClass(args.thiz()); DynamicHookData* hookSymbol = args.engine()->getNativeInstance(scriptResult); hookSymbol->cloneFrom(NativeFunction(*args.engine()->getNativeInstance(args.thiz()))); hookSymbol->mEngine = args.engine(); hookSymbol->mNativeCallack = dcbNewCallback(hookSymbol->buildDynCallbackSig().c_str(), &nativeCallbackHandler, hookSymbol); hookSymbol->mScriptCallback = args[0].asFunction(); void* hookOriginl = nullptr; int hookResult = HookFunction(hookSymbol->mFunction, &hookOriginl, hookSymbol->mNativeCallack); hookSymbol->mFunction = hookOriginl; return scriptResult; } ClassDefine NativeTypeEnumBuilder = defineClass("NativeTypes") .property("Void", &NativeFunction::getType) .property("Bool", &NativeFunction::getType) .property("Char", &NativeFunction::getType) .property("UnsignedChar", &NativeFunction::getType) .property("Short", &NativeFunction::getType) .property("UnsignedShort", &NativeFunction::getType) .property("Int", &NativeFunction::getType) .property("UnsignedInt", &NativeFunction::getType) .property("Long", &NativeFunction::getType) .property("UnsignedLong", &NativeFunction::getType) .property("LongLong", &NativeFunction::getType) .property("UnsignedLongLong", &NativeFunction::getType) .property("Float", &NativeFunction::getType) .property("Double", &NativeFunction::getType) .property("Pointer", &NativeFunction::getType) .build(); ClassDefine NativeCallBuilder = defineClass("NativeFunction") .constructor() .function("fromSymbol", &ScriptNativeFunction::fromSymbol) .function("fromDescription", &ScriptNativeFunction::fromDescription) .function("fromScript", &ScriptNativeFunction::fromScript) .instanceFunction("hook", &ScriptNativeFunction::hook) .instanceProperty("call", &ScriptNativeFunction::getCallableFunction) .instanceProperty("address", &ScriptNativeFunction::getAddress, &ScriptNativeFunction::setAddress) .build(); ClassDefine NativeHookBuilder = defineClass("NativeHook") .constructor() .instanceProperty("call", &ScriptNativeFunction::getCallableFunction) .instanceProperty("address", &ScriptNativeFunction::getAddress, &ScriptNativeFunction::setAddress) .build();