• 周年纪念勋章活动已圆满结束,如有已购买但仍未申请的用户,可以通过对应勋章的下载链接申请~

教程 【图文】MCBE插件制作教程

  • 主题发起人 主题发起人 YYZ
  • 开始时间 开始时间

YYZ

【Lv:3】

注册
2019/01/11
消息
19
金粒
3,621金粒
为MCPE之崛起而分享
壮哉我大基岩

译者:xiaoyuan9894
正文开始


你好!这是一个给安卓或者苹果版的Minecraft创作本地插件(以下简称“插件”)的教程。你也许会问:“插件是什么?”插件是优良MCPE模组的基础,使用C语言或C++编写,不过C++更好。很多人都知道Minecraft的js,但是知道插件的人却不多。ModPE(携带版的模组,即js)是由插件构建起来的。它是一种把插件代码包含于Javascript代码的应用程序接口,由于Javascript相对于C++来说容易学些,任何人都能较轻松地制作模组。尽管如此,MCPE是100%由C++编写的,为了制作最好的模组,充分开掘其发挥的性能,你必须学会制作插件。插件可以修改MCPE中任何的源码(这里“任何”的指代比较宽泛,具体参看“可以修改什么”)。ModPE只允许模组中使用约50种函数,但是插件中有超过12000种函数供你使用!这就是插件厉害的地方!如果你对插件可以做什么还饶有兴趣的话,继续读下去。
尽管MCPE中有超过12000种函数,不是所有的都可以被编辑。导致这的因素如下:

·有些函数的名称太短:事实上底层在调出短名称函数时会有问题,不过这种情况并不常见。有时候可以用直接的vtable(虚函数表)替换来处理这个问题。

·有些函数很weak(脆弱):目前weak是什么意思还不清楚,不过若你在一个函数的名称旁看到WEAK这个词,则它不能被正常调出。其实有办法处理这种情况。

·有些函数的符号被剥离:这意味着写插件的人识别不了这些函数,函数名称只会显示为sub_xxxxxx。这种函数依然能被调出,不过识别它们要花点功夫。

·有些函数被内联:这是最糟的情况。Mojang使用特定的符号编译这个游戏,导致许多函数被内联。这些函数会被彻底删除,而代码会加在用到它们的地方。这种情况无法逆转也很难处理,而且你得知道在苹果版中会严重得多!


为了制作模组,你需要做些准备。强烈建议使用PC或者Mac,不过你也可以在安卓设备上直接制作模组(苹果尚不明确)。以下是PC,Mac和安卓各自的说明:
·你需要一些基础的ARM汇编知识


PC
·IDA Evaluation Version:IDA称得上是最好的软件。这个程序可以拆解游戏的数据库让你看到里面的一切。

·The Android SDK and NDK:需安装the AndroidSDK and NDK。建议把它们安装在C盘。

·Apache Ant:你需要它来安装。
·A text editor:你需要某些文本编辑器来写代码。建议用Sublime Text或者Notepad++。

Mac

·IDA Evaluation Version:(同上)
·Theos or Xcode:用来建立模组(仅供苹果)


Android
·AIDE: The Android IDE (With NDK installed):AIDE是你最好的助手。此应用让你可以完全在安卓设备上写代码(需专业版)并且建立模组,也可以查看或者搜索MCPE数据库【译者注:不知如何用AIDE查看数据库,原文也没有说清楚】。你需要在设置里面选择安装50MB的NDK部分。

·A text/code editor app:若你没有已付费版的AIDE,你就不能编写代码。那么你就需要一个后援的文本编辑器来写代码。

[LINE]准备好制作第一个插件。[/LINE]

去找一个MCPE的apk安装包,这里不提供。用7-Zip打开它或者干脆把后缀名改为.zip查看。打开lib/armeabi-v7a/文件夹,提取出libminecraftpe.so文件——这就是被编译过的数据库,里面有MCPE使用的所有C++代码。如果你用的是苹果,打开ipa文件(先把它解密) ,找到无后缀名的minecraftpe文件。苹果版的数据库就是它。现在可以打开 IDA Evaluation Version(见上文)

不选“Display at startup(在启动时显示)”。现在选Go。当你来到主界面,把上面提到的数据库拖放到这里。
点击下拉菜单里的“MetaPC (disassemble all
opcodes) [metapc]”。

选择“ARM Little-endian [ARM]”,然后点“set”按钮。
点“OK”。如果你打开一个安卓数据库,不用担心,点“OK”。

如果你打开一个苹果的数据库
点“Yes”,继续。
如果你看到窗口,点NO:
现在等几分钟,取决于你电脑的速度。

现在如果你不在graph view(图像模式),请切换过去。在代码窗口(最大的那个窗口)任何地方右击,选择graph view。如果没有这个选项,也许你已经在图像模式了。

最后一步是在左边找到函数窗口并点击窗口顶部:函数名称会按字母表排序。

完成!

现在你的IDA已经建立好了,你可以进行下一步!:)

MCPE是用C++编写的,它不能被反编译成可读的源代码,而只能被分解为ARM汇编语言。因此,你需要些ARM知识去理解MCPE的代码。

The ARM processor(ARM处理器)

安卓和苹果等可移动设备会在ARM处理器上运行,它能读懂所有计算机编码并执行之。你可以把这个处理器看作一段代码:

指令就像函数,它们并不是处理器的一部分,而是在处理器运行的代码里面。它们的功能是表现某些处理器水平的操作,比如更改register(寄存器)。


MOV r0, #0
该语句会把数值0放入register 0。在ARM中,目标总是代码中的第一个参数,参数之间用逗号分隔。所以,这句语句中r0是目标,0就是放进去的东西。#表示它是一个单纯的数值。ARM中的register用r表示,分别有r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12, r13, r14, r15。各register也各司其职。r13是一个stack pointer(SP,堆栈指针),它指向栈的顶部,在ARM代码中常用sp表示而不是r13。r14是一个link register(LR,连接寄存器),在进入一个子程序时它会保留回归地址,使得CPU知道回到哪里去。以后会详述。r15是一个Program Counter(PC,程序计数器),它总会记录正在运行的地址。它非常有用,你可以编辑它(比如用MOV)来跳转到某个地址。


LR(r14)就像这样工作:
当你想跳转到一个不同的地址时,要使用指令B,例如:
B 0x184A7C

该语句会跳转到地址0x184A7C并从那里开始执行。不过,很少用这种方式,因为不能回去。指令BL更常用,B指Branch(分支),L指Link(连接)。它表示在CPU执行该指令时,会让LR(r14)记录目前所在的后一行的地址。

插件运行的方式是通过一种叫做"钩子"的东西。那么我接下来将详细讲解有关"钩子"的内容。

每一个程序都是由程序代码写成的。每一个程序,游戏,包都是用某个人在某处写的程序代码构成的。安卓软件是用java编写的,但是你也可以使用本地代码,即c或者c++,这也就是为啥C++也被叫做"本地代码"(看不懂就百度)。
IOS上的软件是由Objective-C编写的,最近比较常用Swift。但是你依然可以使用C或者C++代码。人们经常想,因为有MODPE,所以mcpe就是用js编写的。这种想法完全是逗…事实上,js是游戏开发的另一个天地。 目前,有这样一种库叫做MobileSubstrate(MS)。有没有听说过?没有?如果有告诉你它的另一个名字是Cydia呢?现在觉得听说过了吗?我相信大部分IOS的用户听说过。这个库能做到的就是允许程序员来制作一种叫做tweak的东西。 这个tweak能做到的是去更改其他程序的代码。MS是一个非常有用的库,因为就算其他人的软件是闭源(不开放源代码)的,无法通过其他软件修改的,你也可以直接修改它的代码,只要你知道一丁点反编译的知识。通过MS,你可以随意重写别人的软件。

当mcpe运行时,它当然是不断地运行这13000个自定义函数中的一部分。举个例子,当你在游戏的时候,游中用一个物品点击一个方块的时戏中有一个钩子函数名为

Gamemode::useItemOn()

在这个钩子函数中,不同的模式将发生不同效果,就如下界反应核只会在生存模式中运行。MS工作的方式是让我们“hook(钩住)”【译者注:hook即钩子、钩住,这里可以解释为调出】其它程序(在这里是MCPE)的函数,并任意地重写它们。因此,在底层模组里面,我们可以调出GameMode::useItemOn这个函数,再用自己的函数替换它。此后,每当游戏用到那个函数时(即游戏中点击任何东西),底层就会拦截这个信号而送到我们自己的代码那去,我们就可以借此改变游戏。这就是ModPE里面 useItem函数的原理。底层让我们也能用其他应用里面的函数而不替换原函数。这就是ModPE里面Level.explode函数的原理。你并没有替换任何游戏中爆炸发生时的内容,但是你使用了游戏的爆炸函数,让游戏正常地产生爆炸。有了这两种操作,你简直可以修改游戏中的任何内容。

你可能会想:“这个长长的字符串是什么玩意?”

解释得花点时间。底层调出其他应用的函数是通过导入那个应用的数据库。对于MCPE模组来说,我们导入libminecraftpe.so或者苹果上的minecraftpe。这里面有应用大部分的C++代码(已编译,所以读不懂),也就是游戏所有的动作发生的地方。底层所做的就是导入那个数据库,当我们的代码写着库中某个函数的mangled string【译者注:直译为错乱字符串,应该是一种格式】(就是那个很长的字符串。因为底层不能识别函数名称和参数,比如Common::getGameVersionString(void),所以必须是mangled的)时,底层就会调出那个函数,并用我们自己的函数替换它。为了写插件,你确实需要理解mangled strings。不过不必担心,IDA会给你这些mangled strings。我会解释他们以便于你快速掌握。
举个例子,你想调出MCPE数据库里面GameMode::useItemOn这个函数。IDA中那个函数显示为

GameMode::useItemOn(GameMode* this, Player&, ItemInstance*, TilePos const&, signed char, Vec3 const&)

现在把它mangle,如下所示::
1.安卓上以_ZN开头,苹果上用__ZN。

2.加上类名称的长度。这里GameMode是8个字母。(_ZN8)

3.加上类名称。(_ZN8GameMode)
4.加上函数名的长度。这里useItemOn是9个字母。(_ZN8GameMode9)

5.加上函数名称。(_ZN8GameMode9useItemOn)

6.加上大写字母E。此后是参数行。(_ZN8GameMode9useItemOnE)
7.用如下方式添加参数:

8.如果参数是指针(有*),就加上P,参数名长度和参数名。(P4Item)

9.如果参数是常数(有const),加上K,参数名长度和参数名(K4Item)
10.如果参数是引用(有&),加上R,参数名长度和参数名(R6Player)

11.如果参数是常引用(有const和&):RK+参数名长度+参数名(RK4Vec3)

12.如果参数是整形,加i;浮点型加f;布尔型加b;字符型加a
13.如果参数不在上述范围内,仅仅是一个类名称(如ItemInstance):参数名长度+参数名(12ItemInstance)

这是一个参数样例:

(Item, Level, const Player&, int, float, bool, float, float, int)


// EP4ItemP5LevelRK6Playerifbffi

如果你只打算使用MCPE的一个函数,而不是调出并代替之,可以这么做:


现在我们可以把这些放到一起使得爆炸发生在任何你点击的地方:

目前这比较复杂,以至于许多人难以理解,但是这里一并展示了函数的调出和使用。

为了修改MCPE,我们必须了解其代码的工作
原理。我不会深入讨论这个问题,因为许多函

数本身就能被解释。现在我们用IDA来寻找想

用的函数。(在"建立IDA"之后)你要做的第一件
事就是在左边滚动函数窗口(应该按字母表

排序)。如果你看到一个函数可能和你想要

修改的有关的话,记住它。如果你在这个窗
口的某个函数上点击一下并打字,你就能查找

函数。比如说,如果你想加入新物品并设置属

性,你可以在Item类找函数。在函数窗口查找
Item::,你会来到这个类的开头,看到如下的界面:
2530

C++中,函数的名称是像

ClassName::functionName

这样,所以你想要的所有函数都会在Item类。如果类名称和函数名
称都相同,那么这个函数是个构造函数,创建

这个类的对象时会用到它。【译者注:类、对

象和构造函数是面向对象语言特有的,如C++、
JAVA等,详情自查面向对象语言】


让我们看看上个教程中Item::Item构造函数的子程序。双击函数窗口中的该函数。

如果你看到前一个界面:
25312532

你需要切换为图像视图。在那个函数上任何地方右击,
在弹出菜单中选择“Graph view”。现在让我们看看它的工作原理。

1.这里的函数名既有mangled的也有没

mangled的。Item::Item(int)有参数的详细信息

(没mangled的),而_ZN4ItemC2Ei是mangled
字符串,也就是你在调用这个函数时需要的那个。(参见“插件是如何运行的&它们看起来怎样”)

2.注意MOV.W LR, #0x40。#说明这是一个单纯的数字,不过也正如你看到的那样IDA默认把数字转成十六进制。单击这个数字(即0x40),再按“h”键。这样就能把数字转换成十进制(这里是64)。

3.这里显示了一个类的vtable(虚函数表)是如何通过汇编被抓取的。这一行LDR R3, -(_ZTV4Item_ptr - 0x411B18)(上面某行)和本行配合把Item的虚函数表存入register3(自查C++虚函数表的教程)。我指明这一点为的是让你更好地理解某些行里面的灰色字符串。

4.这是个字符串。这里用了和上面相同的方法在反汇编中从指定的地方抓取字符串。这个字符串写着“item-opaque.png”,因为它把这个字符串插入下面某处的一个函数来设置图像,而该物品从这里获取图标。(MCPE的文件中items-opaque.png在assets文件夹中,所有物品的图标都在这个文件夹里面)

我们现在已经知道基础了,试着把这些综合起来做自己的第一个基础插件!

Android:

在这个模组里,我们把所有的client messages(客户通知)设置成“TEN GAME WAS HAXED!!”。Client messages就是显示在屏幕上类似于“You can only sleep at night”的信息,也就是js里面的clientMessage()函数。

首先,我们要找到并调出发送client messages的函数。你可以在函数窗口花点时间找,而我直接告诉你是Gui::displayClientMessage(string)。

现在下载这里的插件模板文件,打开jni文件夹。把libminecraftpe.so文件(即IDA中用的那个)复制到这个文件夹。打开mod.cpp文件。

在mod.cpp文件中,在开头加入以下重要的几行:
#include <jni.h>

#include <mcpelauncher.h>

#include <dlfcn.h>
#include <android>

2533

jni.h让我们可以使用JNI相关的资料,例如

JNI_OnLoad macro(宏指令)。

mcpelauncher.h让我们可以使用BlockLauncher(方
块启动器)提供的函数(主要是底层函数,如

MSHookFunction)。

dlfcn.h让我们可以使用dlopen和dlsym。这些被用于安卓mods而不是MobileSubstrate(手机底层)函数,因为安卓上对MS有所限制。
android/log.h让我可以打印CatLog,即对于各种安卓开发人员都有用的一种控制台,不过需要获取root权限。


现在在mod.cpp文件的最后加上以下几行:
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {

void* handle = dlopen("libminecraftpe.so", RTLD_LAZY);

dlerror();
// insert hooks here

const char* error = dlerror();

if(error) __android_log_print(ANDROID_LOG_INFO, TAG, "dlerror: %s", error);
return JNI_VERSION_1_2;

}

你的mod.cpp文件现在应该像这样:

添加一个新的方块

往MCPE添加一个新的方块稍微有点复杂。很容易崩溃,而且你不知道原因。在一定的练习之后,你就能添加任何样子的方块。

Android

你首先要做的就是调用一些函数。我们现在重温一下。

·Tile constructor:你需要用到Tile(MC中的方块id)构造器中的一个,它能为我们初始化一个Tile。通常来说安卓上MCPE提供3个:Tile::Tile(int id)
Tile::Tile(int id Material material)Tile::Tile(intid,std::string name, Material* material)。
我建议用第三个,因为它能初始化最多的数据。

·Tile::tiles.*
虽然这并不是一个函数,但它还是非常重要。这是一个拥有游戏中所有tiles的清单。你要把你的tile加进去。

·TileItem::TileItem:*
在创造Tile的时候,我们也需要给它创造一个TileItem,即我们拿着的或者是在背包里面的那个Tile。

·TileItem vtable:*
我们需要给TileItem合适的虚函数表,以防崩溃。

·I18n::strings:*
尽管这个并不是必要的,不过你总不希望自己的方块的名称像tile.mytile.name吧!

调出teh资料

关于hooking的信息,参见“制作第一个插件”

在mod文件的开头我们要有自己的指针:

static void (Tile$Tile)(Tile, int, std::string const&, void*); // The void

// represents the Material*
static void (TileItem$TileItem)(Item, int); // When making a pointer name for a

// function, just use the name of the function, but replace the :: with a $

static Tile** Tile$tiles;
static void** TileItem$vtable;

std::map <std::string, std::string>* I18n$strings;

就是这样。现在我们要进入JNI_OnLoad initializer(初始化程序)并添加以下hooks:
2528

内容有删改
转自:百度攻略 插件制作教程
 

附件

  • 7af40ad162d9f2d3295cb185a1ec8a136327cc0f.png
    7af40ad162d9f2d3295cb185a1ec8a136327cc0f.png
    16.1 KB · 查看: 3
最后编辑:
发帖这么久居然没人看
这么高级的文章都不看
基岩插件这么少还不看
八张图我还改了五张看
图文详解清晰明了不看
我要删帖了你们都不看
%@/!#*
 
不错,但是稍微排排版可以让人看起来更轻松点
 
很不错的亚子,但是我好像看不懂:surprised:
 

在线管理成员

在线会员

  • s648555685
  • 648645654465456
  • 离川
  • abcd43295
  • Tighnari
  • YYT
  • 温暖舒适的熊
  • 卡卡豆
  • 小小花生
  • sssjiu
  • shuiwucan
  • 太白
  • 清茶菌
  • NovaCraftStudio
  • Dieskeleton
  • 苏热
  • 赤_焰
  • 鲨狐
  • pingguo
  • 超级小柴
...和 67 更多。
后退
顶部 底部