在 Manjaro 上编译 YGOPro

本篇文章是编译 YGOPro 的纪实,重点并不是机械的操作步骤,而是解决问题的思路。


缘起

单纯地心血来潮,突然就很想玩 YGOPro (可以简单理解为使用标准游戏王规则的联网对战游戏)。之前都是用 Windows 玩的,而我现在日常使用 Manjaro,切系统多麻烦啊,所以想试试看能不能找到 Linux 版本的 YGOPro。可惜去了很多的社区都没有找到直接提供的二进制程序,那就只能从圆神大佬的 仓库 里拉取源码然后动手编译了。

分析结构

想好要动手编译了,那就得先来看看项目的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
├── cmake/
├── gframe/
├── ocgcore/
├── script/
├── sound/
├── textures/
├── .gitmodules
├── CMakeLists.txt
├── README.md
├── LICENSE
├── lflist.conf
├── premake4.lua
├── strings.conf
└── system.conf

仔细一看这个仓库里还有 ocgcorescript 两个子模块仓库。script 里是连 GitHub 都显示不过来的超多 Lua 脚本,这些应该就是卡牌的效果,不过它们暂时不是重点。比较重要的是这个 ocgcore,正如其名,这就是解释卡牌脚本的核心引擎。YGOPro 首页的 README 开头便介绍这是“A script engine for “yu-gi-oh!” and sample gui”,可见 YGOPro “不过”是一个示例的图形界面外壳而已,大佬的余裕啊。再看看其他的文件,gframe 里存放的就是图形界面的代码,cmake、CMakeLists.txt 和 premake4.lua 则是两种构建工程时要用到的蓝图,剩下的基本都是运行游戏时才用到的素材和配置吧。那我大概在准备好相关库之后,要用 CMake 或者 Premake 构建项目,编译后试试看能不能运行成功,最后再去社区找卡片素材资源吧。

README 好像也没什么重要的东西,主要都是些运行的使用说明,那么我们再来看看 Wiki。Wiki 中的 Home 页面介绍了如何分别在 Windows 和 GNU/Linux 上编译源代码,而 build 页面则是各个平台的构建细则。介绍说在 Windows 下用 CMake 构建项目,而类 Unix 下面却全是推荐用 Premake 的。估计 CMake 是后来项目维护者加上去的,他们主要针对 Windows 平台开发,应该也想做跨平台构建,但是 CMake 配置写得不算好,所以一直没删掉圆神留下的 Premake 配置。

不扯了,接下来从教程入手,准备开始构建吧。

构建项目

有现成的教程

我用的是 Manjaro,那自然要找 ArchLinux 的构建说明,恰好 Wiki 里的 build 页面则写得清清楚楚:

Arch Linux x86-64
sudo pacman -S gcc make premake freetype2 libevent sqlite irrlicht lua git
git clone https://github.com/Fluorohydride/ygopro.git
cd ygopro
premake4 gmake
make -Cbuild

可以看到,除了 GCC、Make、Git、Premake 这些必备的工具,其他的都是些第三方库。FreeType2 是字体渲染库,libevent 是封装了网络 IO 多路复用与定时器以及信号的事件通知库,SQLite 用于提取永久化保存的卡牌数据库文件,Irrlicht 则是用来支撑图形界面的跨平台 3D 游戏引擎。更不用提 Lua,这个就是用来解释脚本并与 C/C++ 协作的库。不得不说这个阵仗是很全面且完备的,其中的 Lua 也算是游戏开发的经典套餐之一。Lua 使得卡牌素材相关的开发工作变得容易了不少,方便爱好者社区进行协作,说明作者想得还是比较远的。

但却掉坑里了

用 Pacman 把上述的软件都下载安装好并下载好仓库之后,刚键入 premake4 gmake 竟然就提示我命令不存在了。好家伙,用 Zsh 的自动补全以及 --version 参数看了一下:

1
2
$ premake5 --version
premake5 (Premake Build Script Generator) 5.0.0-alpha15

原来 Pacman 给我装的是 Premake 5.0.0,还是个 alpha 版本。这里要提一下,ArchLinux 的官方软件库的思路是滚动更新,所有的软件都是激进式的越新越好,甚至很多都只提供最新版本,而不像 Ubuntu/Debian 那样尽量提供多版本的支持 ,这也就是 ArchLinux 下没有 update-alternative 这种核武器的原因。Manjaro 是 ArchLinux 的下游稳定版,所以思路差不多。这篇 Wiki 文档显然有点过时了,这是个危险的信号。

键入 premakec5 gmake 之后,成功地建立了一个 build 文件夹,里面是几个模块的 Makefile:

1
2
3
4
5
6
7
~/Public/ygopro/build$ tree
.
├── clzma.make
├── cspmemvfs.make
├── Makefile
├── ocgcore.make
└── ygopro.make

那就简单了,直接 make

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
~/Public/ygopro/build$ make
==== Building ocgcore (debug) ====
Creating ../obj/Debug/ocgcore
card.cpp
duel.cpp
effect.cpp
field.cpp
group.cpp
interpreter.cpp
../ocgcore/interpreter.cpp: 在成员函数‘int32 interpreter::call_coroutine(int32, uint32, uint32*, uint16)’中:
../ocgcore/interpreter.cpp:553:51: 错误:too few arguments to function ‘int lua_resume(lua_State*, lua_State*, int, int*)’
553 | int32 result = lua_resume(rthread, 0, param_count);
| ^
In file included from ../ocgcore/interpreter.h:11,
from ../ocgcore/scriptlib.h:12,
from ../ocgcore/interpreter.cpp:12:
/usr/include/lua.h:300:15: 附注:在此声明
300 | LUA_API int (lua_resume) (lua_State *L, lua_State *from, int narg,
| ^~~~~~~~~~
make[1]: *** [ocgcore.make:160:../obj/Debug/ocgcore/interpreter.o] 错误 1
make: *** [Makefile:33:ocgcore] 错误 2

嗯?怎么可能会提示函数的参数过少?/usr/include/lua.h 提供的函数签名与 interpreter.cpp 所使用的不一致,说明 Lua 软件包提供头文件的版本有问题。行吧,看一下上面 Windows 的相关编译说明:

download source code of dependences:
Libevent: https://github.com/libevent/libevent/archive/release-2.1.11-stable.zip
Freetype: https://www.freetype.org/download.html
Sqlite: https://www.sqlite.org/download.html
Irrlicht: http://irrlicht.sourceforge.net/?page_id=10
Lua: http://www.lua.org/ftp/lua-5.3.5.tar.gz

原来这个 Lua 指明道姓了要 5.3 版本的 Lua,而目前我用 Pacman 下载的是 5.4 版本的 Lua,大版本之间接口有差别很正常。那么怎么安装 5.3 版本的 Lua 呢?用 Zsh 的自动补全可以推出 sudo pacman -S lua53,Manjaro 提供的官方库里既然有旧版本,那就应尽量用官方库来装。重新生成 Makefile 后继续编译,却得到了一个链接错误:

1
2
3
4
5
6
7
8
9
10
11
~/Public/ygopro$ premake5 gmake
...
~/Public/ygopro/build$ make
==== Building ocgcore (debug) ====
sound_manager.cpp
...
Linking ygopro
/usr/bin/ld: 找不到 -llua5.3-c++
collect2: 错误:ld 返回 1
make[1]: *** [ygopro.make:102:../bin/debug/ygopro] 错误 1
make: *** [Makefile:51:ygopro] 错误 2

看来是少了编译好的二进制库文件,它的名字肯定类似于 liblua5.3-c++.so 或是 liblua5.3-c++.a,那就看看我们用 Pacman 都装了什么吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ pacman -Ql lua53
lua53 /usr/bin/lua5.3
lua53 /usr/bin/luac5.3
lua53 /usr/include/lua5.3/lauxlib.h
lua53 /usr/include/lua5.3/lua.h
lua53 /usr/include/lua5.3/lua.hpp
lua53 /usr/include/lua5.3/luaconf.h
lua53 /usr/include/lua5.3/lualib.h
lua53 /usr/lib/liblua.so.5.3
lua53 /usr/lib/liblua.so.5.3.6
lua53 /usr/lib/liblua5.3.so
lua53 /usr/lib/liblua5.3.so.5.3
lua53 /usr/lib/liblua5.3.so.5.3.6
lua53 /usr/lib/pkgconfig/...
lua53 /usr/share/doc/...
lua53 /usr/share/licenses/...
lua53 /usr/share/man/...

怪了,不管是静态的还是动态的,库文件里就没有名字带 C++ 的。去 Lua 官网看了一下,Lua 是用纯标准 C 语言写的,那看来 Manjaro 的仓库里可能就不提供 C++ 编译的 Lua 库。如果改变 YGOPro 的工程配置,将 liblua5.3-c++.so 的配置改成 liblua5.3.so 的话也很不靠谱,因为 C 和 C++ 生成的符号名称不一样,不用试也知道到时候肯定会报一堆 undefined xxx 的链接错误。好在我们有 Lua 的源码,可以把它编成库,名字改成 liblua5.3-c++,编译完成后链接到我们的工程当中。那用动态链接还是静态链接呢?如果动态链接,那就必须把库文件放进 /usr/lib/ 目录或是指定运行时的动态库路径。考虑到尽量只使用 Pacman 管理 root 权限文件的原则,自己放在 /usr/lib/ 下的动态库指不定哪天会和 Pacman 的软件包冲突,再加上 Lua 十分精致小巧,所以用静态的方法直接将 Lua 塞进 YGOPro 的目标二进制文件中要更好一些。那我们就从官网上下载 Lua 5.3 的最新源码包吧:

1
2
3
4
5
6
7
$ wget https://www.lua.org/ftp/lua-5.3.6.tar.gz
$ tar -xzvf lua-5.3.6.tar.gz
lua-5.3.6/Makefile
lua-5.3.6/doc/...
lua-5.3.6/src/Makefile
lua-5.3.6/src/...
lua-5.3.6/README

有两个 Makefile,解读一下发现默认只提供了静态库的编译选项,如果想做成动态库还得费点功夫把里面的编译选项都改改。不过只修改编译器和标准库版本就很简单了,把 src/Makefile 开头的 CC= gcc -std=gnu99 改成 CC= g++ -std=c++14 就行。为什么选择 C++14?因为 YGOPro 用的就是这个,尽量先保持一致吧,否则更有可能出现不兼容的怪事。按照官方说明的 make linux && make local 后,去生成的 install/lib/ 找到文件 liblua.a,这就是我们需要的静态库文件了。改名后移到仓库目录的 build/ 下,等待链接。修改一下 premake4.lua 这个配置文件,在 configuration "linux" 条目下增加一行 libdirs { "build" },这样编译时就能将 build/ 作为链接库的搜索目录之一了。接着编译:

1
2
3
4
5
6
$ make
==== Building ocgcore (debug) ====
==== Building clzma (debug) ====
==== Building cspmemvfs (debug) ====
==== Building ygopro (debug) ====
Linking ygopro

好,链接成功了!我们成功获得了二进制文件 ygopro

接着是运行环境

YGOPro 的运行环境是“典型 Windows 绿色免安装式”的,用户私有配置、程序文件、素材和动态链接库都放到了同一个目录下,说实话挺蠢的。类 Unix 的文件管理方式更科学一些,不过这个话题以后再聊,现在应该让我们的二进制文件能跑起来。根据 Wiki 中的介绍,运行环境至少需要以下这些文件:

Running
to run the ygopro(.exe) at least the following files are necessary:

  • /textures
  • cards.cdb
  • lflist.conf
  • strings.conf
  • system.conf with the right path to fonts

the simplest way is to copy the executable and cards.cdb to the root directory of this repository
the full functionality of ygopro need the following in addition:

  • /pics
  • /pics/thumbnail
  • /script

这个倒不是什么难事,中国的 YGOPro 社区 还是很活跃的,在这里可以直接下载到素材。不过我在找的时候全是 Windows 的改版程序包,而且还是把安装程序和素材捆在一块的,真是逼死非 Windows 用户啊。没办法,只能用 Wine 把程序装到 PC 上,然后再把素材取出来了,这么做还会让人有种神奇的蛋疼感。除了其中的 WindBot,其他都无脑复制过来就行。另外,根据 Wiki 的说法,system.conf 是游戏的配置文件,如果想要运行游戏的话必须将其中的字体文件改对,我就随便选了文泉驿的字体,大概能行:

1
2
textfont = /usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc 14
numfont = /usr/share/fonts/wenquanyi/wqy-zenhei/wqy-zenhei.ttc

edit-deck

嗯,运行起来非常丝滑,没有什么问题。不过竟然没有声音,难道我聋了吗?

折腾音乐模块

再回到 GitHub 上,有这样一条合并分支的记录:Sound #2071。根据描述,irrKlang 可以用来提供音乐播放的功能,要在 premake4.lua 中添加 USE_IRRKLANG = true 这一行并提供它的库文件后编译就能使用了。为什么没默认开启?抱着这样的疑问,我用 Pacman 查找了一下,结果并没有发现这个包。官方库没有,那就去它官网看看吧,果不其然,是个非开源的库。其实声音相关的开源库一搜一大把,也不知道项目维护者怎么想的,非要用非开源的。并不是说这有什么道义上的问题,而是非开源的库不提供源代码,往往只提供动态库文件,所以没有办法直接塞进二进制文件中,必须得写个脚本配置一下动态库的加载路径才能启动游戏。都是些琐事,开始动手吧:

1
2
3
4
5
6
7
8
9
10
11
$ wget https://www.ambiera.at/downloads/irrKlang-64bit-1.6.0.zip
$ unzip irrKlang-64bit-1.6.0.zip
$ tree irrKlang-64bit-1.6.0/bin
bin/
├── dotnet-4-64/...
├── linux-gcc-64
│   ├── ikpFlac.so
│   ├── ikpMP3.so
│   └── libIrrKlang.so
├── macosx-gcc/...
└── winx64-visualStudio/...

真有你的,把库文件放在 bin/ 里……显然 ikpFlac.so ikpMP3.so libIrrKlang.so 这三个文件需要放在运行目录下,前两个是用于支持 flac 和 mp3 格式音频的插件,而后一个文件就是编译和运行项目要用到的动态库文件。为了成功编译,还要再把 libIrrKlang.so 和接口头文件 include/* 全复制到仓库目录的 build/ 下。

为了使得工程的编译素材全部集中在 build/ 中,重新编辑一下 premake4.lua 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@@ -1,7 +1,8 @@
solution "ygo"
location "build"
language "C++"
- objdir "obj"
+ objdir "build/obj"
+ USE_IRRKLANG = true
@@ -20,6 +21,8 @@ solution "ygo"
configuration "linux"
defines { "LUA_USE_LINUX" }
+ libdirs { "build/lib" }
+ includedirs { "build/include" }
@@ -34,7 +37,7 @@ solution "ygo"
configuration "Debug"
flags "Symbols"
defines "_DEBUG"
- targetdir "bin/debug"
+ targetdir "build/bin/debug"
@@ -43,7 +46,7 @@ solution "ygo"
configuration "Release"
flags { "OptimizeSpeed" }
- targetdir "bin/release"
+ targetdir "build/bin/release"

编译完了就能完美运行了,再写一个运行脚本 run.sh,方便启动:

1
2
3
4
#!/bin/sh
cd $(dirname $0)
export LD_LIBRARY_PATH=$(pwd)
./ygopro $@

游戏截图:

edit-deck

编排构建目录

贴一下我的 build/ 吧。你如果也想试着编译一下,可以照着下面这样编排文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
├── runtime/
│   ├── bot.conf
│   ├── cards.cdb
│   ├── deck/
│   ├── error.log (十分重要!如果闪退了就要查看这个文件)
│   ├── expansions/
│   ├── lflist.conf
│   ├── ikpFlac.so (flac 格式插件)
│   ├── ikpMP3.so (mp3 格式插件)
│   ├── libIrrKlang.so (音乐动态链接库)
│   ├── pics/
│   ├── replay/
│   ├── run.sh (启动脚本)
│   ├── script/
│   ├── single/
│   ├── sound/
│   ├── strings.conf
│   ├── system.conf (记得在配置中设置字体)
│   ├── textures/
│   └── ygopro (把二进制文件复制到这里就能跑了)
├── bin/
│   └── release/
│   ├── libclzma.a
│   ├── libcspmemvfs.a
│   ├── libocgcore.a
│   └── ygopro (编译生成的二进制文件)
├── clzma.make
├── cspmemvfs.make
├── include/ (音乐库接口头文件)
│   ├── ik_ESoundEngineOptions.h
│   ├── ...
│   └── irrKlang.h
├── lib/ (编译时所需要的库)
│   ├── libIrrKlang.so
│   └── liblua5.3-c++.a
├── Makefile
├── obj
│   └── Release/
├── ocgcore.make
└── ygopro.make

关于单人游戏

之前提到了打包的 Windows 改版傻瓜包中有个名为 WindBot 的插件,它就是一个耍牌的 AI。YGOPro 使用 libevent 库对网络进行封装,那么其实联机功能就构建于 TCP 协议基础之上,而 WindBot 就是一个实现了对战接口的客户端程序。可惜 WindBot 是用 C# 写的,移植到类 Unix 系统上太过麻烦。兴许我有空了可以照着它的源码用 Rust 重写一遍试试。

贤者时间

游戏王是个很小的圈子,能凑齐几个爱好者一起玩是件挺不容易的事。但是不管是实体卡还是关于 YGOPro,都出过不少恼人的事,甚至还有坏比利用 YGOPro 盈利,跟某个晦气东西有得一拼。无论如何,游戏王是我高中的美好回忆,希望它能一直走下去。另外,我 fork 了官方仓库,在其中的 manjaro-note 分支里写了一点笔记,项目里有些配置我也稍改了一下,如果你还有疑惑的话可以翻阅看看,也欢迎联系我。其实用 Ubuntu 编译这个项目要简单得多,照着 Wiki 做就行了,因为 apt 仓库的思路是稳定的多版本共存,不会出现 Manjaro 或 ArchLinux 那样使用旧版本相对麻烦的情况。果然还是 Ubuntu 比较适合开发啊。

最后提一句:倒霉 K 社,什么时候出龙星的新卡啊!!!

生命在于折腾,这是第八片星星。