issuimo/UnityResolve.hpp

关于obj直接invoke的建议

Opened this issue · 12 comments

ps:本来想直接提交pr的,但是看到你的项目好像转移到另一个github账号了,就直接这样给你反馈吧

很多时候我想使用unity obj直接去调用obj下的方法,然后看了一下你的源码,发现有几处可以优化一下

这里举例说明一下我想实现的效果:

    // [-]Assembly-CSharp @ 0x78eebf5220
    //  [-]Assembly-CSharp.dll @ 0x79806bed60 | C:433
    //    [-]IronSourceManager @ 0x77a03b2900 | M:90 | F:13
    //      [-]public Boolean ShowReward(UnityEvent onCompleted, UnityEvent onFailed) @ MI: 0x77a03d30a0 & MP: 0x788db69e28 & RP: 0xbe5e28
    //        [-]onCompleted         | type: 0x788ea15c28 | @ class:0x78eedf6540 | UnityEngine.Events.UnityEvent
    //        [-]onFailed            | type: 0x788ea15c28 | @ class:0x78eedf6540 | UnityEngine.Events.UnityEvent
    //        [-]_RET_               | type: 0x788e9f97b8 | @ class:0x78eede4300 | System.Boolean
    static auto Class_IronSourceManager = UnityResolve::Get("Assembly-CSharp.dll")
            ->Get("IronSourceManager");
    for (auto *method : Class_IronSourceManager->methods) {
        logd("IronSourceManager %s", method->name.c_str());
        if (method->name == "ShowReward") {
            HookManager::registerHook(method->function, *[](void *instance, UnityResolve::UnityType::Object *onCompleted, UnityResolve::UnityType::Object *onFailed) {
                logd("called IronSourceManager ShowReward");

                // ------------------------------------------------------
                //| Found 6 Methods  in class: UnityEvent @ 0x78f02ec2c0 |
                // ------------------------------------------------------
                //[*] 0x789e322ef0 ---> 0x7890926bd8 ---> 0xa53bd8        |  public Void .ctor()
                //[*] 0x789e322f40 ---> 0x7890926cb4 ---> 0xa53cb4        |  public Void AddListener(UnityAction call)
                //[*] 0x789e323080 ---> 0x7890926ff4 ---> 0xa53ff4        |  public Void Invoke()
                //[*] 0x789e322f90 ---> 0x7890926d6c ---> 0xa53d6c        |  protected override MethodInfo FindMethod_Impl(String name, Type targetObjType)
                //[*] 0x789e323030 ---> 0x7890926cf0 ---> 0xa53cf0        |  private static BaseInvokableCall GetDelegate(UnityAction action)
                //[*] 0x789e322fe0 ---> 0x7890926f84 ---> 0xa53f84        |  internal override BaseInvokableCall GetDelegate(Object target, MethodInfo theFunction)
                onCompleted->invokeMethod<void>("Invoke");
            });
        }
    });

我希望通过obj直接去调用obj(所属类"UnityEngine.Events.UnityEvent")下面的invoke方法,按照你现在的代码逻辑还得去获取一遍,就不太方便,我这里为obj多封装了两个函数

struct Object {
            union {
                void *klass{nullptr};
                void *vtable;
            } Il2CppClass;

            struct MonitorData *monitor{nullptr};

            auto GetType() -> CsType * {
                static Method *method;
                if (!method)
                    method = Get("mscorlib.dll")->Get("Object", "System")->Get<Method>("GetType");
                if (method)
                    return method->Invoke<CsType *>(this);
                return nullptr;
            }

            auto ToString() -> String * {
                static Method *method;
                if (!method)
                    method = Get("mscorlib.dll")->Get("Object", "System")->Get<Method>("ToString");
                if (method)
                    return method->Invoke<String *>(this);
                return {};
            }

            int GetHashCode() {
                static Method *method;
                if (!method)
                    method = Get("mscorlib.dll")->Get("Object", "System")->Get<Method>("GetHashCode");
                if (method)
                    return method->Invoke<int>(this);
                return 0;
            }

            [[nodiscard]] auto tryMethod(const std::string &name, const std::vector<std::string> &args = {}) const {
                UnityResolve::Class clazz(this->Il2CppClass.klass);
                return clazz.Get<UnityResolve::Method>(name, args);
            }

            template<typename T>
            T invokeMethod(const std::string &name, const std::vector<std::string> &args = {}, const std::vector<void *> &params = {}) {
                auto method = tryMethod(name, args);
                if (!method) {
                    if constexpr (!std::is_void_v<T>)
                        return T{};
                    else
                        return;
                }
                if constexpr (std::is_void_v<T>) {
                    method->Invoke<void>(this, params);
                } else {
                    return method->Invoke<T>(this, params);
                }
            }

        };

以及修改了一下UnityResolve::Class的构造函数

    struct Class final {
        void *address{};
        std::string name;
        std::string parent;
        std::string namespaze;
        std::vector<Field *> fields;
        std::vector<Method *> methods;
        void *objType{};

        Class()= default;

        explicit Class(void* pClass){
            this->address = pClass;
            this->name = Invoke<const char *>("il2cpp_class_get_name", pClass);
            if (const auto pPClass = Invoke<void *>("il2cpp_class_get_parent", pClass))
                this->parent = Invoke<const char *>("il2cpp_class_get_name", pPClass);
            this->namespaze = Invoke<const char *>("il2cpp_class_get_namespace", pClass);

            ForeachFields(this, pClass);
            ForeachMethod(this, pClass);

            void *i_class;
            void *iter{};
            do {
                if ((i_class = Invoke<void *>("il2cpp_class_get_interfaces", pClass, &iter))) {
                    ForeachFields(this, i_class);
                    ForeachMethod(this, i_class);
                }
            } while (i_class);
        }
       
        ......

    }

这样一来我们就可以直接方便的去访问类实例下面的函数

总的来说,我是觉得他不应该一开始就遍历完了,然后就写"死了",这样的操作方法多了性能表现很差不说而且也不够灵活
而是需要的时候去构造Class(ptr)得到一个解析好的class,需要什么构造什么 ...
其实就是内存地址和类型的随时任意的转换,这样才更像是一个灵活的工具

至于性能方面,所有获取和初始化都是一次性的,对性能影响几乎没有

至于你修改过的,每次都去获取一遍性能会更差,没有特殊需求的话,请在初始化时获取对应的函数指针,再在需要时调用

你这是win端的当然是没啥性能的关系,但是在移动端还是一开始就把所有类 方法 都解析一遍 明显能看出来进游戏速度慢了很多

你这是win端的当然是没啥性能的关系,但是在移动端还是一开始就把所有类 方法 都解析一遍 明显能看出来进游戏速度慢了很多

请使用异步操作

建议在游戏运行后注入,一次性初始化后对后面执行没有任何的性能影响,但实时获取就不一样了

还有个问题就是 拿着类实例指针去调用类方法,当然是用 instance -> invokeMethod("methodname") 这种方式看起来更自然一点
要不然就是像你前面说的那样 先拿到函数地址 然后转指针 然后在调用,就是c和c++写法的差别

可以尝试
static Method* method = class_->Get("Update");
method->Invoke(...);

可以尝试 static Method* method = class_->Get("Update"); method->Invoke(...);

我也就是这样写的

            [[nodiscard]] auto tryMethod(const std::string &name, const std::vector<std::string> &args = {}) const {
                UnityResolve::Class clazz(this->Il2CppClass.klass);
                return clazz.Get<UnityResolve::Method>(name, args);
            }

只是封装了一下trymethod 和 invoke 放在了一起而已

问题在这,每次都获取开销会很大

[[nodiscard]] auto tryMethod(const std::string &name, const std::vector<std::string> &args = {}) const {
    UnityResolve::Class clazz(this->Il2CppClass.klass);
    return clazz.Get<UnityResolve::Method>(name, args);
}
explicit Class(void* pClass){
            this->address = pClass;
            this->name = Invoke<const char *>("il2cpp_class_get_name", pClass);
            if (const auto pPClass = Invoke<void *>("il2cpp_class_get_parent", pClass))
                this->parent = Invoke<const char *>("il2cpp_class_get_name", pPClass);
            this->namespaze = Invoke<const char *>("il2cpp_class_get_namespace", pClass);

            ForeachFields(this, pClass);
            ForeachMethod(this, pClass);

            void *i_class;
            void *iter{};
            do {
                if ((i_class = Invoke<void *>("il2cpp_class_get_interfaces", pClass, &iter))) {
                    ForeachFields(this, i_class);
                    ForeachMethod(this, i_class);
                }
            } while (i_class);
        }

推荐

static Method* method = class_->Get<Method>("Update");
method->Invoke<void>(...);

MethodPointer<void, CameraController*> update;
class_->Get<Method>("Update")->Cast(update);

的原因就是直接通过函数指针调用,而不需要其他多余的处理方式

转成汇编也就差不多两行开销, arm可能会多一点但总体不会超过10行

mov rcx, qword ptr ds:[rcx+0x30]
call rcx