- 致编者:请牢记我们的域名wiki.mcbe-dev.net!
- 致编者:欢迎加入本Wiki的官方交流QQ群或Discord服务器!
- 基岩版1.19.31现已发布!(了解更多)
- Inner Core现已支持Xbox模组联机!(了解更多)
- 如果您是第一次来到本Wiki,欢迎注册一个账户
- 点击顶部的“编辑”或“编辑源代码”按钮即可编辑当前页面
- 请知悉:在不登录时也可以编辑和新建页面,但是您当前的IP地址会记录在编辑历史中
教學:利用Aide在Android裝置上構建原生遊戲模組
出自Minecraft基岩版开发Wiki
- Nmod是一種用來變更Minecraft的工具。具體實現是使用 Cydia Substrate 來Hook掉
libminecraftpe.so
裡的函式。 - 本教學不講解c++的語法,因此你需要一定的c++基礎
- 本教學的目的是取得編譯完成的so檔案,如何進行使用參見InnerCore原生遊戲模組教學
- so的檔案位於
目錄下./libs/armeabi-v7a
連結[編輯]
- Aide高級版官網:https://www.aidepro.top/
- Aide高級版可用NDK:https://pan.baidu.com/s/1oHedYeVo93OV7p0INs2PKA?pwd=jktg
- Native模板連結:https://pan.baidu.com/s/1do_-0sBMl0B6cjT8J5iaCg?pwd=8733
- Disassembler連結:https://pan.baidu.com/s/1xHZqg2-ORP8Q5dfbY-tXMQ?pwd=2476
- 可諮詢QQ群:795440679
- 本文作者QQ:2584300846
第一步:環境組態[編輯]
- 確認你目前Android裝置的版本
- 進入 Aide高級版 官網並下載
- 下載好後,進入app頁面(這裏以2.8.3版本為例)
- 點擊右上角
- 點擊更多
- 點擊設定
- 點擊構建&執行
- 點擊管理Native程式碼支援包
- 如果你的 Android裝置版本 < 13.0 請選擇線上安裝,如果 Android裝置版本 >= 13.0 請指定NDK路徑,手動安裝
第二步:下載模板[編輯]
- 如果你有能力自己透過內建模板進行改編,可跳過本步驟,否則請下載我給出的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