基岩版服务器开启坐标显示,基岩版官方服务端MOD的安装和编写

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x

本帖最后由 Ginkgo06 于 2018-12-24 16:59 编辑

复制代码Minecraft基岩版 MOD的安装以及编写

转载请注明出处,有问题请留下评论,也希望大佬能够指出文章中的问题

https://github.com/minecraft-linux/server-modloader/wiki/Making-mods-%232:-Disassembly-&-Hooking, 原版是英文版,我翻译了一下主要的步骤,总结并解释了一些要点,水平渣,不会cpp,仅作参考

下载了SDK后把SDK添加到环境变量中去

nano /etc/profile

#在最后一行添加

export MODLOADER_SDK=$MODLOADER_SDK/home/bedrock/ModDev

#之前在$MODLOADER_SDK后面加了引号可把自己坑惨了...注意

#SDK(解压的文件夹)所在的路径

#保存后执行

source  /etc/profile

echo $MODLOADER_SDK

#此时便可以看见我们SDK的路径了复制代码

尝试编写Mod

这里我采用的开发方式是直接在服务器上创建mod文件,并利用VSCODE的ssh插件在电脑上编辑这个mod,此外g++等等环境需要配置好。 Mod Loader属于第三方的扩展,Mod SDK可以在Loader的下载页中获得,SDK提供了 C和C++的API 尽管只学过C,但这次还是采用C++吧... 基岩版官方服务端使用C++编写,Mod也相应的采用了C/C++来编写,浏览作者MCMrARM的Wiki,首先我们可以看到对Mod SDK中 Hooking API的介绍,看到Hook(钩子)这个词,我们可以了解到,Mod采取的机制是对诸如命中、移动、跳跃等等游戏中的事件进行Hook,当这些事件发生时,Mod会进行处理。

Mod SDK 以C++ API为例

以SDK中提供的C++Api为例,SDK提供了C++的 作用域基础上的钩子(Hook),在这些钩子之上建立了基于宏的静态Hook API 自己翻译得好烂啊...看原文吧 The library provides a simple C++ scoped-based hook (modloader::AutoHook), and a macro-based static hook API is built upon it:

//代码如下

// #include

#define THook2(iname, ret, sym, args...) ...

#define THook(ret, sym, args...) ...

#define TClasslessInstanceHook2(iname, ret, sym, args...) ...

#define TClasslessInstanceHook(ret, sym, args...) ...

#define TInstanceHook2(iname, ret, sym, type, args...) ...

#define TInstanceHook(ret, sym, type, args...) ...

#define TStaticHook2(iname, ret, sym, type, args...) ...

#define TStaticHook(ret, sym, type, args...) ...复制代码

这里需要提到的一点就是C++中的 name mangling规则(命名粉碎规则),这个规则会给函数不同的签名,以明确具体调用的是哪一个函数,避免调用重载的函数时会出现二义性 具体一些的例子

可以看见这里定义了一些static Hook 的写法,宏中的钩子会hook签名为sym,返回类型为ret且参数为args的函数,而TInstanceHook 和 TStaticHook两个宏因为没有iname字段,所以必须要给出有效的类名。

作者对不同宏作用的介绍以及我的理解:

You should use THook for hooking global functions, that are not part of any class. 当要hook的函数为全局函数而不是某一个类的一部分时使用THook 例如 printf()

You should use TInstanceHook for hooking member functions. Requires a full class declaration使用TInstanceHook hook成员函数(不是静态),需要完整地声明类 例如DedicatedServer::run()

Use TClasslessInstanceHook if you do not want to use a full class declaration如果不想使用完整的类的声明时使用

You should use TStaticHook for hooking static (class) functions. Requires a full class declaration想要hook 静态函数时使用,需要完整的类的声明 例如Common::getServerVersionString()

Use TClasslessInstanceHook if you do not want to use a full class declaration反之

对于full class declaration的解释 Requires a full class declaration means that to use it you need to declare the class somewhere and include it (class DedicatedServer { }; in an included header is OK, but a forward declaration like class DedicatedServer; is not enough).

意思是如果我要使用需要full class declaration的Hook时,需要在代码中包含这个类的声明(或者是包含包含了这个类的声明的头文件,如果只在代码中创建一个引用(不知道C++中怎么说的)是不够的)

如果在一个Mod中需要多次hook一个函数,需要使用后缀为2的宏并且传递不同的第一个参数(iname)

HelloMinecraft MOD

创建HelloMinecraftMod 这里我们会用到ModLoader的Loger工具 Loger有以下几个等级的输出

verbose

debug

info

warn

error

编辑 testmod.cpp

#include填入头文件路径,如果不使用绝对路径需要在编译时指明

#include

using namespace modloader;

extern "C" void modloader_on_server_start(void* serverInstance) {

Log::verbose("BeginMod", "Hello Minecraft!");

Log::info("BeginMod","BeginServer");

Log::debug("BeginMod","CreateByhaojie")

}复制代码

编译生成.so mod文件 /home/bedrock/ModDev# g++ testmod.cpp -std=c++11 -I ${MODLOADER_SDK}/include/ -L ${MODLOADER_SDK}/lib/ -lserver_modloader -shared -fPIC -o BeginMod.so

注意

-L用于指明连接的库所在的文件夹

-l用于指明具体的库

-I指明#include的文件所在的文件夹

没有报错就说明成功了 ls 在当前文件夹出现了 BeginMod.so 文件,这个就是我们的MOD了,将它移动到服务端文件夹中的mods文件夹中并启动服务端 请使用 ./start_modloader 启动服务器 在启动的过程中我们便可以在命令行中看见输出了,第一个MOD成功(虽然还没有实际作用)

thread-835302-1-1.html

创建一个具有实际作用的MOD (爆炸箭矢)

参考作者的wiki https://github.com/minecraft-linux/server-modloader/wiki/Making-mods-%232:-Disassembly-&-Hooking 为了实现具体的功能我们需要将服务端解包,这里还需要掌握一定的汇编知识 我们这里使用到IDA解包 下载 IDA有一款插件HexRays CodeXplorer似乎可以把汇编语言转化为更好懂的形式?不过这次还没有使用。

开始制作

使用IDA->new->选择从官网下载的服务端压缩包解压后的 bedrock_server 文件 (注意这个文件没有扩展名,所以在选择是要将文件类型改为所有文件*)

thread-835302-1-1.html

然后便开始解包 耗时十来分钟

thread-835302-1-1.html

感觉这个IDA查找起来非常耗CPU啊,低压i5占用100%????... ps:卡顿是因为还在建立索引,当索引建立完毕之后就不卡了。

找到需要Hook的函数

我们要创建一个箭矢碰撞后爆炸的MOD,首先我们要找到箭矢碰撞的函数,::onHit (其实找到对应的函数是非常困难的一件事情,毕竟这里面的函数太多了。而且官方并没有给出说明...)

你是不是以为我们的目标是 Throwable::onHit ?但经过对Arrow的继承树的查看我们可以发现(作者发现的),Throwable并没有出现过,实际上我们应该使用的是 ProjectileComponent::onHit

Hooking的函数名

找到事件发生的函数之后怎么对其进行Hook呢?

首先我们要获得这个函数的 mangled name(关于mangled name前面有介绍),在IDA中双击FunctionName在IDA VIEW里面展示具体的函数。

thread-835302-1-1.html

在这里我们可以看见一串以_ZN 开头的字符(_ZN是name mangling 的 symbol) eg:_ZN19ProjectileComponent5onHitERK9HitResult

我们Hook时使用的就是这个名字,在IDA里面无法查看函数的返回值以及函数是否是静态的,IDA可以对这个返回值进行猜测,不过据作者MCMrARM所说,猜测的一般会失败...注释中所写的也不是完全正确的。(难道只能自己猜了嘛??)

公布结果:ProjectileComponent::onHit的返回值为void

编写MOD,检测击中

看注释吧,这里还没有爆炸,只是当事件发生的时候在控制台输出

thread-835302-1-1.html 编写完成之后像之前一样进行编译

thread-835302-1-1.html

g++ testmod.cpp -std=c++11 -I ${MODLOADER_SDK}/include/ -L ${MODLOADER_SDK}/lib/ -lserver_modloader -shared -fPIC -o explodeArrow.so复制代码

满怀期待地打开服务器,捡起弓箭,shot,然后发现控制台并没有输出????....

问题解决了,真是个低级的错误,看上面的指令就能发现问题,我忘了修改源代码文件的名字了!!将 testmod.cpp 改为 explodeArrow.cpp 编译,成功之后将.so移动到../mods下,启动游戏,掏出弓箭,射击,控制台成功显示!

thread-835302-1-1.html

编写MOD,处理击中之后的爆炸

添加爆炸效果

已经能检测到击中事件了,接下来我们要做的就是在击中的坐标处引发一次爆炸

首先我们要在IDA中找到负责爆炸的函数,搜索 ::explode,这一次我们使用的是Level(ModLoader作者在wiki中指明,说实话我也不知道怎么选择) Level::explode 现在我们面临的问题是如何获得level的指针以及爆炸的方块的源以及位置。

因为如前面所说 TInstanceHook hook成员函数(不是静态),需要完整地声明类 因此我们要像下面这样声明需要使用到的方法

声明获得Actor指针的方法

使用ProjectileComponent::getEntity() 我们可以获得一个额 Actor* 指针(实体的指针,根据wiki所说,之所以不是getActor是因为几个版本前的更新Mojang还没有彻底将Entity转变为Actor)

//添加

class Actor;

class ProjectileComponent {

public:

Actor& getEntity();

};复制代码

声明获得Leveland BlockSource指针的方法

//添加

class BlockSource;

class Level;

class Actor {

public:

BlockSource* getRegion() const;

Level* getLevel();

};复制代码

最后我们要从HITRESULT中获得击中的坐标(即爆炸坐标),声明获得坐标的方法 HitResult::getPos() const 会返回一个 Vec3 const& 因此我们先声明 Vec3,再声明这个方法

class Vec3;

class HitResult {

public:

Vec3 const& getPos() const;

};·复制代码

声明爆炸的方法,各个参数的意思我们也不知道,只能不断地修改,然后在游戏中测试

class Level {

public:

void explode(BlockSource&, Actor*, Vec3 const&, float, bool, bool, float, bool);

};复制代码

最后 ,在TInstanceHook中添加,当弓箭击中物体时,这个hook便会调用其中的方法做出响应

TInstanceHook(void, _ZN19ProjectileComponent5onHitERK9HitResult, ProjectileComponent, HitResult const& hitResult) {

Log::verbose(TAG, "ProjectileComponent::onHit");

original(this, hitResult);

Actor& entity = this->getEntity();

entity.getLevel()->explode(*entity.getRegion(), nullptr, hitResult.getPos(), 5.f, /* 是否产生燃烧 */ false, /*是否破坏方块 */ true, 1.f, true);

}复制代码

完成了这些,我们的爆炸箭矢MOD的源代码就完成了,最后编译之后将.so移动到../mods文件夹下,开启server 弓箭射击..BOOM!

总结

一开始我以为官方服务器是没有MOD了的,没想到能在github上发现这个MODLoader,而且作者MCMrARM大大还热心的写了wiki教程,一步一步的写出了一个基岩版官方服务端的MOD,不过要说做BDS的MOD开发还是会面临一些很大的问题

官方没有很好的API支持,写一个MOD还得解包

解包后函数过多,很难确定究竟应该Hook哪一个函数

资料非常的少

在这个框架下MOD的潜力有多大?之后基岩版服务器的MOD还会有什么发展,这都是值得我们拭目以待的。

转自我的博客 blog.haojie06.me

Minecraft基岩版 MOD的安装以及编写

转载请注明出处,有问题请留下评论,也希望大佬能够指出文章中的问题 文章有点长,如果感兴趣请耐心看完,我参考的教程是Mod Loader的作者MCMrARM编写的Wikihttps://github.com/minecraft-linux/server-modloader/wiki/Making-mods-%232:-Disassembly-&-Hooking, 原版是英文版,我翻译了一下主要的步骤,总结并解释了一些要点,水平渣,不会cpp,仅作参考

目录

thread-835302-1-1.html 昨天安装好了微软推出的Minecraft基岩版官方服务端,这个服务端虽然非常的接近原版,但相对于其它的第三方版本,它少了一个很重要的功能,"MOD",没有了MOD也就少了许多趣味,当然,我怎么会甘心?

今天,在万能的Github上搜索Bedrock Dedicate Server的时候,搜索结果中出现的

安装ModLoader

参看作者的wiki,安装这个loader很简单,已经有了现成的脚本。在服务器的目录中执行 #!/binbash,之后再使用chmod +x setup_server_modloader.sh 以及 ./setup_server_modloader.sh 便可以实现一键安装,之后再查看目录,便可以看见Mods文件夹以及 start_modloader.sh 启动脚本。

安装启动器是很简单,但是哪里有Mod呢?我找了挺久,反正是没有找到写好的Mod(当然和之前的Java以及PHP的Mode不兼容),只在作者的Wiki发现了Making Mods 的介绍,看来在服务端的初期,这些东西都得自己动手啊。

安装MODSDK

查看wiki 首先需要前往作者的github下载SDK并解压。

在服务器上进行操作

mkdir /home/moddev

cd /home/moddev

wget https://github.com/minecraft-linux/server-modloader/releases/download/v0.0.1-alpha1/mod_sdk.zip

unzip mod_sdk.zip

#创建一个mod

test testmod.cpp

#之后我采取的方式是在VSCODE上编辑(当然你也可以直接在服务器上使用Vim或者Nano)复制代码

下载了SDK后把SDK添加到环境变量中去

nano /etc/profile

#在最后一行添加

export MODLOADER_SDK=$MODLOADER_SDK/home/bedrock/ModDev

#之前在$MODLOADER_SDK后面加了引号可把自己坑惨了...注意

#SDK(解压的文件夹)所在的路径

#保存后执行

source /etc/profile

echo $MODLOADER_SDK

#此时便可以看见我们SDK的路径了复制代码

尝试编写Mod

这里我采用的开发方式是直接在服务器上创建mod文件,并利用VSCODE的ssh插件在电脑上编辑这个mod,此外g++等等环境需要配置好。 Mod Loader属于第三方的扩展,Mod SDK可以在Loader的下载页中获得,SDK提供了 C和C++的API 尽管只学过C,但这次还是采用C++吧... 基岩版官方服务端使用C++编写,Mod也相应的采用了C/C++来编写,浏览作者MCMrARM的Wiki,首先我们可以看到对Mod SDK中 Hooking API的介绍,看到Hook(钩子)这个词,我们可以了解到,Mod采取的机制是对诸如命中、移动、跳跃等等游戏中的事件进行Hook,当这些事件发生时,Mod会进行处理。

Mod SDK 以C++ API为例

以SDK中提供的C++Api为例,SDK提供了C++的 作用域基础上的钩子(Hook),在这些钩子之上建立了基于宏的静态Hook API 自己翻译得好烂啊...看原文吧 The library provides a simple C++ scoped-based hook (modloader::AutoHook), and a macro-based static hook API is built upon it:

//代码如下

// #include

#define THook2(iname, ret, sym, args...) ...

#define THook(ret, sym, args...) ...

#define TClasslessInstanceHook2(iname, ret, sym, args...) ...

#define TClasslessInstanceHook(ret, sym, args...) ...

#define TInstanceHook2(iname, ret, sym, type, args...) ...

#define TInstanceHook(ret, sym, type, args...) ...

#define TStaticHook2(iname, ret, sym, type, args...) ...

#define TStaticHook(ret, sym, type, args...) ...复制代码

这里需要提到的一点就是C++中的 name mangling规则(命名粉碎规则),这个规则会给函数不同的签名,以明确具体调用的是哪一个函数,避免调用重载的函数时会出现二义性 具体一些的例子

可以看见这里定义了一些static Hook 的写法,宏中的钩子会hook签名为sym,返回类型为ret且参数为args的函数,而TInstanceHook 和 TStaticHook两个宏因为没有iname字段,所以必须要给出有效的类名。

作者对不同宏作用的介绍以及我的理解:

You should use THook for hooking global functions, that are not part of any class. 当要hook的函数为全局函数而不是某一个类的一部分时使用THook 例如 printf()

You should use TInstanceHook for hooking member functions. Requires a full class declaration使用TInstanceHook hook成员函数(不是静态),需要完整地声明类 例如DedicatedServer::run()

Use TClasslessInstanceHook if you do not want to use a full class declaration如果不想使用完整的类的声明时使用

You should use TStaticHook for hooking static (class) functions. Requires a full class declaration想要hook 静态函数时使用,需要完整的类的声明 例如Common::getServerVersionString()

Use TClasslessInstanceHook if you do not want to use a full class declaration反之

对于full class declaration的解释 Requires a full class declaration means that to use it you need to declare the class somewhere and include it (class DedicatedServer { }; in an included header is OK, but a forward declaration like class DedicatedServer; is not enough).

意思是如果我要使用需要full class declaration的Hook时,需要在代码中包含这个类的声明(或者是包含包含了这个类的声明的头文件,如果只在代码中创建一个引用(不知道C++中怎么说的)是不够的)

如果在一个Mod中需要多次hook一个函数,需要使用后缀为2的宏并且传递不同的第一个参数(iname)

HelloMinecraft MOD

创建HelloMinecraftMod 这里我们会用到ModLoader的Loger工具 Loger有以下几个等级的输出

verbose

debug

info

warn

error

编辑 testmod.cpp

#include填入头文件路径,如果不使用绝对路径需要在编译时指明

#include

using namespace modloader;

extern "C" void modloader_on_server_start(void* serverInstance) {

Log::verbose("BeginMod", "Hello Minecraft!");

Log::info("BeginMod","BeginServer");

Log::debug("BeginMod","CreateByhaojie")

}复制代码

编译生成.so mod文件 /home/bedrock/ModDev# g++ testmod.cpp -std=c++11 -I ${MODLOADER_SDK}/include/ -L ${MODLOADER_SDK}/lib/ -lserver_modloader -shared -fPIC -o BeginMod.so

注意

-L用于指明连接的库所在的文件夹

-l用于指明具体的库

-I指明#include的文件所在的文件夹

没有报错就说明成功了 ls 在当前文件夹出现了 BeginMod.so 文件,这个就是我们的MOD了,将它移动到服务端文件夹中的mods文件夹中并启动服务端 请使用 ./start_modloader 启动服务器 在启动的过程中我们便可以在命令行中看见输出了,第一个MOD成功(虽然还没有实际作用)

thread-835302-1-1.html

创建一个具有实际作用的MOD (爆炸箭矢)

参考作者的wiki https://github.com/minecraft-linux/server-modloader/wiki/Making-mods-%232:-Disassembly-&-Hooking 为了实现具体的功能我们需要将服务端解包,这里还需要掌握一定的汇编知识 我们这里使用到IDA解包 下载 IDA有一款插件HexRays CodeXplorer似乎可以把汇编语言转化为更好懂的形式?不过这次还没有使用。

开始制作

使用IDA->new->选择从官网下载的服务端压缩包解压后的 bedrock_server 文件 (注意这个文件没有扩展名,所以在选择是要将文件类型改为所有文件*)

thread-835302-1-1.html

然后便开始解包 耗时十来分钟

thread-835302-1-1.html

感觉这个IDA查找起来非常耗CPU啊,低压i5占用100%????... ps:卡顿是因为还在建立索引,当索引建立完毕之后就不卡了。

找到需要Hook的函数

我们要创建一个箭矢碰撞后爆炸的MOD,首先我们要找到箭矢碰撞的函数,::onHit (其实找到对应的函数是非常困难的一件事情,毕竟这里面的函数太多了。而且官方并没有给出说明...)

你是不是以为我们的目标是 Throwable::onHit ?但经过对Arrow的继承树的查看我们可以发现(作者发现的),Throwable并没有出现过,实际上我们应该使用的是 ProjectileComponent::onHit

Hooking的函数名

找到事件发生的函数之后怎么对其进行Hook呢?

首先我们要获得这个函数的 mangled name(关于mangled name前面有介绍),在IDA中双击FunctionName在IDA VIEW里面展示具体的函数。

thread-835302-1-1.html

在这里我们可以看见一串以_ZN 开头的字符(_ZN是name mangling 的 symbol) eg:_ZN19ProjectileComponent5onHitERK9HitResult

我们Hook时使用的就是这个名字,在IDA里面无法查看函数的返回值以及函数是否是静态的,IDA可以对这个返回值进行猜测,不过据作者MCMrARM所说,猜测的一般会失败...注释中所写的也不是完全正确的。(难道只能自己猜了嘛??)

公布结果:ProjectileComponent::onHit的返回值为void

编写MOD,检测击中

看注释吧,这里还没有爆炸,只是当事件发生的时候在控制台输出

thread-835302-1-1.html 编写完成之后像之前一样进行编译

thread-835302-1-1.html

g++ testmod.cpp -std=c++11 -I ${MODLOADER_SDK}/include/ -L ${MODLOADER_SDK}/lib/ -lserver_modloader -shared -fPIC -o explodeArrow.so复制代码

满怀期待地打开服务器,捡起弓箭,shot,然后发现控制台并没有输出????....

问题解决了,真是个低级的错误,看上面的指令就能发现问题,我忘了修改源代码文件的名字了!!将 testmod.cpp 改为 explodeArrow.cpp 编译,成功之后将.so移动到../mods下,启动游戏,掏出弓箭,射击,控制台成功显示!

thread-835302-1-1.html

编写MOD,处理击中之后的爆炸

添加爆炸效果

已经能检测到击中事件了,接下来我们要做的就是在击中的坐标处引发一次爆炸

首先我们要在IDA中找到负责爆炸的函数,搜索 ::explode,这一次我们使用的是Level(ModLoader作者在wiki中指明,说实话我也不知道怎么选择) Level::explode 现在我们面临的问题是如何获得level的指针以及爆炸的方块的源以及位置。

因为如前面所说 TInstanceHook hook成员函数(不是静态),需要完整地声明类 因此我们要像下面这样声明需要使用到的方法

声明获得Actor指针的方法

使用ProjectileComponent::getEntity() 我们可以获得一个额 Actor* 指针(实体的指针,根据wiki所说,之所以不是getActor是因为几个版本前的更新Mojang还没有彻底将Entity转变为Actor)

//添加

class Actor;

class ProjectileComponent {

public:

Actor& getEntity();

};复制代码

声明获得Leveland BlockSource指针的方法

//添加

class BlockSource;

class Level;

class Actor {

public:

BlockSource* getRegion() const;

Level* getLevel();

};复制代码

最后我们要从HITRESULT中获得击中的坐标(即爆炸坐标),声明获得坐标的方法 HitResult::getPos() const 会返回一个 Vec3 const& 因此我们先声明 Vec3,再声明这个方法

class Vec3;

class HitResult {

public:

Vec3 const& getPos() const;

};·复制代码

声明爆炸的方法,各个参数的意思我们也不知道,只能不断地修改,然后在游戏中测试

class Level {

public:

void explode(BlockSource&, Actor*, Vec3 const&, float, bool, bool, float, bool);

};复制代码

最后 ,在TInstanceHook中添加,当弓箭击中物体时,这个hook便会调用其中的方法做出响应

TInstanceHook(void, _ZN19ProjectileComponent5onHitERK9HitResult, ProjectileComponent, HitResult const& hitResult) {

Log::verbose(TAG, "ProjectileComponent::onHit");

original(this, hitResult);

Actor& entity = this->getEntity();

entity.getLevel()->explode(*entity.getRegion(), nullptr, hitResult.getPos(), 5.f, /* 是否产生燃烧 */ false, /*是否破坏方块 */ true, 1.f, true);

}复制代码

完成了这些,我们的爆炸箭矢MOD的源代码就完成了,最后编译之后将.so移动到../mods文件夹下,开启server 弓箭射击..BOOM!

总结

一开始我以为官方服务器是没有MOD了的,没想到能在github上发现这个MODLoader,而且作者MCMrARM大大还热心的写了wiki教程,一步一步的写出了一个基岩版官方服务端的MOD,不过要说做BDS的MOD开发还是会面临一些很大的问题

官方没有很好的API支持,写一个MOD还得解包

解包后函数过多,很难确定究竟应该Hook哪一个函数

资料非常的少

在这个框架下MOD的潜力有多大?之后基岩版服务器的MOD还会有什么发展,这都是值得我们拭目以待的。

[/code]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值