教學:利用Aide在Android裝置上構建原生遊戲模組

出自Minecraft基岩版开发Wiki
  • Nmod是一種用來變更Minecraft的工具。具體實現是使用 Cydia Substrate 來Hook掉libminecraftpe.so裡的函式。
  • 本教學不講解c++的語法,因此你需要一定的c++基礎
  • 本教學的目的是取得編譯完成的so檔案,如何進行使用參見InnerCore原生遊戲模組教學
  • so的檔案位於

    ./libs/armeabi-v7a

    目錄下

連結[編輯]

第一步:環境組態[編輯]

  1. 確認你目前Android裝置的版本
  2. 進入 Aide高級版 官網並下載
  3. 下載好後,進入app頁面(這裏以2.8.3版本為例)
  4. 點擊右上角
  5. 點擊更多
  6. 點擊設定
  7. 點擊構建&執行
  8. 點擊管理Native程式碼支援包
  9. 如果你的 Android裝置版本 < 13.0 請選擇線上安裝,如果 Android裝置版本 >= 13.0 請指定NDK路徑,手動安裝

第二步:下載模板[編輯]

  1. 如果你有能力自己透過內建模板進行改編,可跳過本步驟,否則請下載我給出的Native模板,此模板在原有的基礎上進行了一定程度的改編,你可以以此為基礎,建立你自己的原生遊戲模組

內容講解[編輯]

Native模板中的庫[編輯]

hook.h

//
// Created by zheka on 18/07/19.
//

#include <functional>

#ifndef HORIZON_HOOK_H
#define HORIZON_HOOK_H

typedef long long int64_t;

namespace SubstrateInterface {
    /** change protection mode of given memory address and surrounding pages
     * - address - address to change protection
     * - offset - offset in pages before page, that contains address
     * - size - size in pages
     * - mode - protection mode
     * returns 0 on success or error code
     * */
    int protect(void* address, int offset, int size, int mode);

    /**
     * technical hook function, use HookManager::addCallback
     * - origin - function to hook address
     * - hook - function to replace
     * - result - pointer, to pass original hooked function
     * returns true on success
     */
    bool hook(void *origin, void *hook, void **result);
}

/**
 * core namespace for callback creation
 */
namespace HookManager {
    enum CallbackType {
        // usual listener, does not create any special conditions
        LISTENER = 0,

        // called as target, will force all RETURN callbacks to be called after it
        REPLACE = 4
    };

    enum CallbackTarget {
        // called before target call and cannot be prevented
        CALL = 0,

        // called after "CALL" callbacks, can be prevented by ones before it
        ACTION = 1,

        // called just before target call if its not prevented, cannot be prevented by other such callback
        TARGET = 2,

        // called after target or replace callback is called to process return value and change it if required. RETURN | REPLACE combination is illegal
        RETURN = 3
    };

    enum CallbackParams {
        // should be passed, if callback returns result, otherwise engine will ignore it
        RESULT = 16,

        // should be passed, if callback requires controller, HookManager::CallbackController* will be passed as first parameter
        CONTROLLER = 32
    };

    // priority for adding callbacks, greater priority = earlier call inside one CallbackTarget
    enum CallbackPriority {
        PRIORITY_MIN = -10,
        PRIORITY_LESS = -5,
        PRIORITY_DEFAULT = 0,
        PRIORITY_GREATER = 5,
        PRIORITY_MAX = 10
    };

    // not implemented for now
    struct CallbackAddStatus {

    };

    /**
     * used to access callback logic, result, status, ect.
     * will be passed as first parameter, if CONTROLLER flag is given
     */
    struct CallbackController {
        // returns, if callback was prevented
        bool isPrevented();

        // returns, if callback was replaced
        bool isReplaced();

        // returns, if contains result from previous calls
        bool hasResult();

        // returns saved result
        void* getResult();

        // prevents callback, all future calls wont happen, this will force all RETURN callbacks to execute right after
        void prevent();

        // replaces callback, prevents target and TARGET callbacks from being called, equivalent to REPLACE flag effect
        void replace();

        // returns pointer to target function
        void* getTarget();

        // calls target with given params and casts result to R, usage: int result = controller->call<int>(1, "a");
        template<typename R, typename... ARGS>
        R call (ARGS... args)  {
            return ((R(*)(ARGS...)) getTarget())(args ...);
        }

        template<typename R, typename... ARGS>
        R callAndReplace (ARGS... args)  {
            replace();
            return ((R(*)(ARGS...)) getTarget())(args ...);
        }
    };

    // technical struct
    struct Hook {
    public:
        void* getCaller();
        void* getTarget();
        void* getAddress();
    };


    template<typename R, typename... ARGS>
    class CallInterface;

    /*
     * represents interface to call specified function, its callback or target
     * CallInterface is immune to future addCallback calls for its target method
     */
    template<typename R, typename... ARGS>
    class CallInterface<R(ARGS...)> {
    public:
        // calls target (original) function
        R target(ARGS... args);

        // calls function callback, or original function, if no callbacks exist
        R hook(ARGS... args);

        // equivalent to hook(), but as operator
        R operator()(ARGS... args);

        // creates same interface for other type
        template<typename T>
        CallInterface<T>* cast();

        // returns target (original) function address
        void* getTargetCaller();

        // returns hook function address
        void* getHookCaller();

        void* getAddrHookCaller();
    };

    /*
     * returns call interface for given function, call interface allows to call both callback and original function and immune to creating callbacks of this method
     * usage:
     *  auto method = HookManager::getCallInterface<void*(int, int)>(...);
     */
    template<typename T>
    CallInterface<T>* getCallInterface(void* addr);

    // -- function pointers --
    /*
     * adds func as callback for address with given params and priority
     * - addr - address to add
     * - func - function pointer, cast to void*
     * - flags - all flags are described above, default combination is CALL | LISTENER
     * - priority - higher priority - earlier call, default is DEFAULT_PRIORITY (0)
     * */
    CallbackAddStatus* addCallback(void* addr, void* func, int flags, int priority);
    CallbackAddStatus* addCallback(void* addr, void* func, int flags);
    CallbackAddStatus* addCallback(void* addr, void* func);


    // -"- with usage of LAMBDA()
    CallbackAddStatus* addCallback(void* addr, int64_t lambda, int flags, int priority);
    CallbackAddStatus* addCallback(void* addr, int64_t lambda, int flags);
    CallbackAddStatus* addCallback(void* addr, int64_t lambda);
}

#define LAMBDA(ARGS, CODE, VALUES, ...) ((int64_t) new std::function<void ARGS>([VALUES] ARGS CODE))



#endif //HORIZON_HOOK_H

該庫提供了兩種hook方法

  • SubstrateInterface
  • HookManager
  • 如果你想寫獨立的原生遊戲模組,推薦使用SubstrateInterface,HookManager在多數情況下應用於與Java和JavaScript的互動中
  • 值得一提的是,在比較新的版本中HookManager方法可能存在報錯情況

mod.h

//
// Created by zheka on 18/07/15.
//

#include <jni.h>
#include <functional>
#include <vector>
#include <fstream>
#include <map>
#include "definitions.h"

#ifndef HORIZON_MOD_H
#define HORIZON_MOD_H

class Module;

/* represents mod library instance */
class ModLibrary {
public:
    // library handle ptr
    void* handle = nullptr;

    // initialization result
    int result = 0;

    // returns list of all modules
    std::vector<Module*> getModules();
};

/*
 * Modules are main structural units of most mod libraries, that can receive and handle events, contain information etc.
 * - Custom module class must extend Module class or some of its subclasses
 * - Modules must be instantiated in library entry point (MAIN {...})
 * - All initialization logic, including adding callbacks, must happen inside module's initialize() method
 *
 * Properties:
 * - Modules can have name IDs and inherit other modules, this will be represented in UI to show hierarchy
 * - Module name ID is used to search its description in manifest
 *
 * Tips:
 * - Modules are used to divide all code into separate logic pieces
 * - You should have global variables of module instances, created in MAIN {...}
 * - All global variables should be put inside modules and accessed through its instances
 * - All API events should be processed inside modules through addListener
 * - Modules also used for profiling and crash handling
 */
class Module {
private:
    ModLibrary* library = NULL;
    Module* parent = NULL;
    const char* id = NULL;
    bool _isInitialized = false;

    std::map<std::string, std::vector<std::function<void()>*>> listeners;

public:
    // receives parent or name ID or both, root module must have name ID
    Module(Module* parent, const char* id);
    Module(const char* id);
    Module(Module* parent);

    // adds mod listener method, that will be called upon given event
    template <typename T>
    void addListener(std::string event, std::function<T> const& function);

    // invokes all listeners for given event
    template <typename... ARGS>
    void onEvent(std::string event, ARGS... args);

    // all initialization must happen here
    virtual void initialize();

    // separate method for java initialization
    virtual void initializeJava(JNIEnv* env);

    // returns parent module
    Module* getParent();
    // returns name ID
    const char* getNameID();
    // returns type, that used inside UI
    virtual const char* getType();
    // used to create separate mod log, used, if current profiled section belongs to this module
    virtual std::ofstream* getLogStream();

    bool isInitialized();
    // returns mod library, this module belongs to
    ModLibrary* getLibrary();
};

/*
 * same class as module, but interpreted as mod inside UI, root module of your mod should implement this class
 */
class Mod : public Module {
public:
    Mod(Module *parent, const char *id);
    Mod(Module *parent);
    Mod(const char *id);
};


namespace ModuleRegistry {
    // returns all modules for given name ID
    std::vector<Module*> getModulesById(std::string id);

    // invokes event with given name and args for all modules, if filter function returned true
    template <typename... ARGS>
    void onFilteredEvent(std::string event, std::function<bool(Module*)> filter,  ARGS... args);

    // invokes event with given name and args for all modules
    template <typename... ARGS>
    void onEvent(std::string event, ARGS... args);

    // invokes event with given name and args for all modules with given name ID
    template <typename... ARGS>
    void onTargetEvent(std::string module, std::string event, ARGS... args);

    // invokes method for all modules
    void onAction(std::function<void(Module*)> action);
};

namespace SignalHandler {
    // initializes signal handles inside given process, this usually happens automatically
    void initialize();
}



#define JNI_VERSION JNI_VERSION_1_4

/**
 * describes library entry point
 * - There must be only one entry point per library
 * - In most cases used only for module instantiation
 * - Inside its body there are variables ModLibrary* library - instance of this mod library and int* result - pointer to initialization result (0 is OK)
 * MAIN {
 *
 * }
 */

#define NO_JNI_MAIN \
            void __entry(ModLibrary* library, int* result); \
            int __mod_main(ModLibrary* library) {\
                int result = 0; \
                SignalHandler::initialize();\
                __entry(library, &result);\
                return result;\
            }\
            void __entry(ModLibrary* library, int* result)

#define MAIN \
            NO_JNI_MAIN
            


#endif //HORIZON_MOD_H
  • 該庫主要負責原生遊戲模組之間的隔離和原生遊戲模組的執行
  • 本庫已經過編者的修改,你可以多載JNI_OnLoad方法來取得jvm
  • 一定要注意,每個原生遊戲模組的Module名稱不可相同,否則會出現閃退情況

symbol.h

//
// Created by zheka on 18/07/19.
//

#ifndef HORIZON_SYMBOL_H
#define HORIZON_SYMBOL_H

#include "definitions.h"

/*
 * represents dynamic library handle
 * */
class DLHandle {
private:
    const char* name = "<unknown>";

    void* handle = nullptr;

    // if dlsym failed, uses elf elf_dlsym instead
    bool elf_support = true;

public:
    void* symbol(const char* name);
};


/*
 * interface to access dynamic libraries and symbols
 * */
namespace DLHandleManager {
    DLHandle* getHandle(const char* name);

    /*
     * initializes dynamic library handle, that can be further accessed by SYMBOL macros
     * name - full library name
     * key - name to access handle from SYMBOL, equals to name by default
     * flags - flags, that are passed to dlopen, RTLD_LAZY by default
     * support_elf, if dlsym fails, tries internal method, based on ELF format, true by default
     * */
    DLHandle* initializeHandle(const char* name, const char* key, int flags, bool support_elf);
    DLHandle* initializeHandle(const char* name, int flags, bool support_elf);
    DLHandle* initializeHandle(const char* name, const char* key, int flags);
    DLHandle* initializeHandle(const char* name, int flags);
    DLHandle* initializeHandle(const char* name, const char* key);
    DLHandle* initializeHandle(const char* name);

    // used in macros
    void* _symbol(DLHandle* handle, const char* symbol);
    void* _symbol(const char* dlname, const char* symbol);
}

// converts any type to (void*)
#define ADDRESS(X) ((void*) X)

// returns symbol address, if search failed, returns NULL and writes error to log
// HANDLE - DLHandle* or string, representing dynamic library to search ("mcpe" represents minecraft pe library)
// NAME - symbol name
#define SYMBOL(HANDLE, NAME) (DLHandleManager::_symbol(HANDLE, NAME))

// converts function pointer to (void*)
#define FUNCTION(X) ((void*) ((unsigned long long) &(X)))

#endif //HORIZON_SYMBOL_H
  • 這個庫是原生遊戲模組想要執行的關鍵庫,你可以透過該庫取得so檔案的Handle,從而進行操作

其它Hook方法[編輯]

dlfcn庫[編輯]

  • 如果你熟悉dlfcn庫的話,可以透過dlopen,dlsym函式之間的配合,透過函式名稱實現Hook
  • 如果你熟悉記憶體位址,也可以省略符號名,使用記憶體位址進行Hook(此方法編者未研究)

標頭檔[編輯]

  • 如果你接觸過ModdedPE,並了解它的遊戲模組編寫方式也可以使用標頭檔的方法進行Hook