admin管理员组

文章数量:1532656

开篇

近日沉迷于VenusBlood系列不能自拔,某天突发奇想的想来挑战一下把VenusBlood Abyss(以下简称VBA)从吉里吉里2引擎移植到吉里吉里Z。

由于吉里吉里2过于古老,仅支持本地字符集,也就是日文开发的游戏只能在日文内码下运行。因此想在非日文系统上跑吉里吉里2游戏总启动就报错,非要转内码才能玩。

而吉里吉里2的后继者吉里吉里Z解决了这个问题,默认使用UTF-8字符集。

然而吉里吉里Z并不直接兼容吉里吉里2,尽管它们有99%的相似性,但是你把吉里吉里2游戏的xp3直接扔给吉里吉里Z跑,那是肯定跑不起来的(吉里吉里Z启动时支持指定字符集,但即使用-readencoding=Shift_JIS选项还是跑不起来,本文会解释原因)。

本工程的目的是让VBA可以在非日文内码下也可以正常运行,且:

  1. 全面移植至吉里吉里Z引擎
  2. 尽可能还原与官方版本一致的表现
  3. 尽量不阉割原有功能
  4. 尽可能少量的改动游戏源码(脚本)
  5. 仅对应最新游戏版本(v1.08)
  6. 由于是全新Release,将patch.xp3里的文件全部移到本应放置的位置(这意味着最终成品中不再包含patch.xp3)
  7. 由于官方版本是32位的,因此只移植至吉里吉里Z的32位版本(因为缺少64位的部分插件)


准备工作

本工程涉及以下工具或工程:

  1. 吉里吉里2最终Release版 v2.32 revision 2 - https://krkrz.github.io/download/kr2_232r2.zip
  2. 吉里吉里Z最终Release版 v1.4.0 - https://github/krkrz/krkrz/releases/download/1.4.0r2/krkrz_20171225r2.7z
  3. Krkr2Compat项目 - GitHub - krkrz/Krkr2Compat: 吉里吉里2互換機能追加スクリプト
  4. Resource Hacker - Resource Hacker
  5. Kikiriki - 这玩意我还真找不到官方下载,我也忘了我手上的是从哪儿下的,总之给大家共享出来吧
  6. Cvt2Utf8 - 我自己写的帮忙将日文内码的文本文件批量转码为UTF-8格式的工具,文中会提到

文本编码转换与文件整理

首先是文本的编码转换工作。需要先将官方版本的所有xp3解包,然后把里面所有文本文件都转码成UTF-8(理论上包不包含BOM都可以,我选择都包含BOM的)。

使用kikiriki将官方版本下所有xp3解压,每个xp3对应一个同名目录,可使用命令:(请预先把xp3文件都复制到自己新建的工程目录,避免直接在游戏目录下操作)

kikiriki -e -i character.xp3 -o character
kikiriki -e -i data.xp3 -o data
kikiriki -e -i graphics.xp3 -o graphics
kikiriki -e -i interface.xp3 -o interface
kikiriki -e -i patch.xp3 -o patch
kikiriki -e -i sound.xp3 -o sound
kikiriki -e -i voice.xp3 -o voice

完毕后删除xp3文件,将会得到以下结构的文件目录:

character    对应character.xp3文件,包含各角色立绘、图标、表情等图像文件
data         对应data.xp3文件,包含游戏脚本文件,也包含了少量其他资源,如图像和配置文件
graphics     对应graphics.xp3文件,包含除UI和角色资源以外的所有图像文件,如背景、CG、特效等
interface    对应interface.xp3文件,包含UI资源文件,大部分为图像,也包含了少量其他资源,甚至脚本
patch        对应patch.xp3文件,包含补丁文件
sound        对应sound.xp3文件,包含视频和除语音以外的音频文件,如音乐、音效等
voice        对应voice.xp3文件,包含语音文件

UTF-8转码工作枯燥乏味,所以我用C#写了个工具程序Cvt2Utf8来做这个工作。同时Cvt2Utf8不仅做了编码转换,还顺便替我们把所有文件都替换成patch.xp3中最新版本的(如果存在)。
Cvt2Utf8具体用法见后面的附录A。

另外patch还会多出来几个文件,这几个文件是补丁新增的,其原始路径我也不知道,只能靠猜,不过没关系即使和实际不一样也不影响移植效果,因为吉里吉里只看文件名不看路径的。

处理完这些文件之后patch目录应该空了,删除掉。

接下来执行下面的命令,把除了data目录以外的目录重新打包成xp3文件。(data目录因为还要修改,放到最后打包,而其他目录不打包的话执行起来会报错)

kikiriki -c -i character -o character.xp3
kikiriki -c -i graphics -o graphics.xp3
kikiriki -c -i interface -o interface.xp3
kikiriki -c -i sound -o sound.xp3
kikiriki -c -i voice -o voice.xp3

注意,需要在日本内码下执行这些命令,因为命令行本身是ASCII的,会不认某些日文文件名而失败。(不知道为什么即使用chcp转码也不行,必须修改操作系统的内码,需要重启系统的)

不过如果你是中文内码,还可以偷个懒(因为中文字符集中包含了大部分日文字符),那就是不打包sound.xp3,直接使用官方版本sound.xp3,其他xp3直接在中文内码下打包即可。

这是因为仅sound.xp3中包含了中文字符集不支持的日文字符文件名(会打包失败),而凑巧sound.xp3下面没有任何需要变更的文件(patch和UTF-8转码文件都没有),所以直接用官方版本即可。

然后删除已经打过包的目录(character/graphics/interface/sound/voice),最后再把吉里吉里Z引擎的tvpwin32.exe复制到工程目录下。

到此为止,至此基本的开发环境就有了。

Krkr2Compat接入

开发环境是做好了,但是还跑不起来。你可以尝试执行tvpwin32.exe(请用命令行执行,否则看不到日志信息),会报错:

==== An exception occured at menus.tjs(307)[(class) KAGMenuItem], VM ip = 8 ====
#(307) class KAGMenuItem extends MenuItem
スクリプトで例外が発生しました
メンバ "MenuItem" が見つかりません

这就是上面说的吉里吉里Z不直接兼容吉里吉里2的地方了,比如这个MenuItem类,在吉里吉里2中是原生支持,到吉里吉里Z中改成插件支持且不推荐使用了。(不推荐的原因是跨平台兼容性问题,如果仅是Windows系统的话还是可以用的)

除了MenuItem以外,还有一些功能也被删除或者改动。

官方文档给出了一个被删除功能列表:https://krkrz.github.io/documents/TJS2/deleted.html

你会发现大部分功能通过导入Krkr2Compat都可以解决,于是我们来试着导入Krkr2Compat。

打开https://github/krkrz/Krkr2Compat,你会发现Krkr2Compat没有release,无奈之下只能git clone它的master。

拿到代码之后只有一个目录是我们需要的:data/k2compat。把它整个复制到我们的data/k2compat下。

然后打开data/startup.tjs并编辑,在文件的最开头(必须位于语句Scripts.execStorage("kag/system/Initialize.tjs")之前)加入:

@set (K2COMPAT_PURGE_MENU = 0)          // MenuItem类,  0表示开启兼容模式,1表示关闭兼容模式,如开启兼容,需要plugin/menu.dll
@set (K2COMPAT_PURGE_KAGPARSER = 0)     // KagParser类, 0表示开启兼容模式,1表示关闭兼容模式,如开启兼容,需要plugin/KAGParser.dll
@set (K2COMPAT_PURGE_FONTSELECT = 0)    // Font.doUserSelect方法,0表示开启兼容模式,1表示关闭兼容模式, 如开启兼容,需要plugin/win32dialog.dll
@set (K2COMPAT_PURGE_INPUTSTRING = 0)   // System.inputString方法,0表示开启兼容模式,1表示关闭兼容模式, 如开启兼容,需要plugin/win32dialog.dll
@set (K2COMPAT_PURGE_WINDOWPROP = 0)    // Window.innerSunken/Window.showScrollBars属性,0表示开启兼容模式,1表示关闭兼容模式, 实际设置了并没有效果,只是为了脚本不报错
@set (K2COMPAT_PURGE_PTDRAWDEVICE = 0)  // Window.PassThroughDrawDevice类,0表示开启兼容模式,1表示关闭兼容模式
@set (K2COMPAT_PURGE_PAD = 0)           // Pad类,0表示开启兼容模式,1表示关闭兼容模式, 如开启兼容,需要plugin/win32dialog.dll
@set (K2COMPAT_PURGE_DEBUG = 0)         // Debug.console/Debug.controller/Debug.scripted/Debug.watchexp属性, 0表示开启兼容模式,1表示关闭兼容模式, 部分功能为空实装, 如开启兼容,需要plugin/win32dialog.dll
@set (K2COMPAT_SPEC_DESKTOPINFO = -1)   // System.desktopLeft/System.desktopTop/System.desktopRight/System.desktopBottom属性的式样,0表示关闭兼容模式,非0表示开启兼容模式,其中1表示总使用主显示器的桌面区域尺寸,-1表示总是用主窗口所在显示器的桌面区域尺寸, 如开启兼容,需要plugin/windowEx.dll
@set (K2COMPAT_SPEC_SCREENINFO = 0)     // System.screenWidth/System.screenHeight属性的式样,0表示关闭兼容模式(优先使用主窗口所在显示器的全屏尺寸,没有主窗口的话则使用主显示器的全屏尺寸),1表示开启兼容模式, 此时总使用主显示器的全屏尺寸, 如开启兼容,需要plugin/windowEx.dll
@set (K2COMPAT_VERBOSE = 1)             // Krkr2Compat的日志输出开关,0表示关闭,1表示开启,随意

@if (kirikiriz)
{
    var base = "k2compat/"; // System.exePath + "k2compat.xp3>";
    Scripts.execStorage(base+"k2compat.tjs");
    Krkr2CompatUtils.scriptBase = base;
}
@endif

这样就完成了Krkr2Compat的接入了。不过这时还跑不起来,因为还缺少对应吉里吉里Z的插件文件。

首先把官方版本的plugin目录整个复制到工程目录,然后再把吉里吉里Z中自带的以下几个插件文件也全部复制进去(注意是32位版本):

plugin/extrans.dll         覆盖掉官方版本的extrans.dll 
plugin/KAGParser.dll       新文件,Krkr2Compat使用
plugin/menu.dll            新文件,Krkr2Compat使用
plugin/wuvorbis.dll        覆盖掉官方版本的wuvorbis.dll 

不过由于吉里吉里Z的最后Release是2017年的1.4.0,里面的插件不全,缺少Krkr2Compat要用到的win32dialog.dll和windowEx.dll。

正统的方法是从https://github/wtnbgo/win32dialog和https://github/wtnbgo/windowEx下载源代码自己编译,不过这太麻烦了,我直接从VBFI(或者其他任何基于吉里吉里Z的32位游戏都行)的plugin里复制来这两个文件也能用。

P.S. VBFI是指VenusBlood Frontier International, 它是VBA续作VenusBlood Frontier(VBF)的官方吉里吉里Z移植版(VBF也是吉里吉里2引擎写的),因此这两者是我们重要的参照物。当然了,VBA没有官方吉里吉里Z移植版,否则我也不用干这个事情了……

令人期待的时刻来了,这时候再执行tvpwin32.exe……
结果还是报错……-_-|||
怎么回事捏,只好看报错日志输出:

==== An exception occured at afterinit.tjs(149)[(top level script) global], VM ip = 614 ====
#(149)         kag.autoModeMenuItemBase.add(new MenuItem(this, "-"),4);
スクリプトで例外が発生しました
Window クラスのオブジェクトを指定してください

原来是传给MenuItem构造函数的第一个参数类型不是Window类型,肿么回事?

打开kag/system/afterinit.tjs,从代码上下文来看这个this确实很不明确,不知道指向的到底是啥,很奇怪吉里吉里2下为什么跑得起来。

不管那么多,相应的几十处this都改成kag就OK了。

P.S. kag变量是游戏的主窗口对象,类型为KAGWindow(继承于Window),在kag/system/mainwindow.tjs中定义。

然后再执行tvpwin32.exe……这回终于跑起来了!

[quake]标签功能异常

终于跑起来啦!我满心欢喜的新建游戏,开始开篇剧情,结果没看几句就报错了:

==== An exception occured at mainwindow.tjs(3858)[(function) onQuakeTimerInterval], VM ip = 303 ====
#(3858) :              setLayerPos(x, y);
スクリプトで例外が発生しました
メンバ "setLayerPos" が見つかりません

翻了下网上的资料,说是Window.setLayerPos方法被吉里吉里Z删了,而且似乎吉里吉里2中这个方法在Win8系统上还有BUG……

这里其实是KAG(粗糙的理解就是.ks脚本)的[quake]标签功能间接调用到了setLayerPos,用来移动背景图层从而实现屏幕抖动效果的。

最粗暴的方法是直接注掉对setLayerPos的调用代码,这么一来虽然不报错了,但抖动效果也没了(VBFI就是这么对应的TToTT)

既然咱们的目标是尽可能还原与官方版本一致的表现, 那就不能这么敷衍了事。如果Window.setLayerPos不能用,咱用Layer.setPos行不行呢?

亲测后发现也不行,因为吉里吉里Z其实不仅仅是删掉了setLayerPos一个方法这么简单,而是直接禁止了对Window.primaryLayer的移动操作,强行setPos还是会报错。

苦恼之中发现网上有人提到KAGEX的quake的实现没有使用Window.setLayerPos,怎么回事赶紧去看一看。原来KAGEX是在原先的primaryLayer上又创建了一个层(Layer),以这个层作为base再去创建和管理其他层,那么移动该层以外的层(如fore.base和back.base)并不违反吉里吉里Z不能移动primaryLayer的原则。

OK,让我们按照这个思路如法炮制一番,这个问题也算解决了。主要代码如下:

// kag/system/mainwindow.tjs:

class KAGWindow extends Window
{
     ...

     function KAGWindow(ismain = true, width = 0, height = 0)
     {
          ...

          // 创建自己的base层
          var base = new Layer(this, null);                  // 第二个参数空表示这个层作为primaryLayer
          base.setSize(scWidth, scHeight);                   // 大小设为游戏屏幕大小
          base.type = ltOpaque;
          base.fillRect(0,0,scWidth,scHeight,0);             // 背景初始化涂黑
          add(base);                                         // 别忘了要加到主窗口里

          // 背景レイヤの作成
          fore.messages = [];
          back.messages = [];
          fore.layers = [];
          back.layers = [];
          fore.base = new BaseLayer(this, base, "表-背景");  // 这里fore.base的父层要指定为base层(fore.base是曾经的primaryLayer,要改掉)
          add(fore.base);
          fore.base.setImageSize(scWidth, scHeight);
          fore.base.setSizeToImageSize();
          fore.base.visible = true;                          // 这句很重要!!!primaryLayer是会自动visible的但普通层不会,因此这里要手动visible,不然会看不见
          back.base = new BaseLayer(this, fore.base, "裏-背景");
          add(back.base);
          back.base.setImageSize(scWidth, scHeight);
          back.base.setSizeToImageSize();
          fore.base.setCompLayer(back.base);
          back.base.setCompLayer(fore.base);
          fore.base.freeImage();
          back.base.freeImage();
          
          ...
     }
     
     ...

     // 新增函数,用来模拟Window.setLayerPos
     function setLayerPos(x, y) {
        fore.base.setPos(x, y);
        back.base.setPos(x, y);
    }
    
    ...
}

我并没有直接使用KAGEX的代码,因为KAGEX除了干了这事以外还弄出了自己的一套层级管理系统,太庞杂而且我们也用不上,因此只是借鉴了思路。

仅这样还不够,进游戏会发现有些点击操作似乎被吃了,狂点鼠标也没反应(比如点击跳过OP会不好使)。

这是因为fore.base和back.base的类型BaseLayer会根据自己是否primaryLayer来决定是否传递点击操作(以及其它一些操作)。而我们添加了base层作为primaryLayer,那么fore.bas和back.base永远都不会是primaryLayer,因此永远都不会传递这些操作了。

看来需要改造BaseLayer,方法很简单,把该类中所有if(isPrimary)都改成if(isPrimary || parent.isPrimary)即可。

菜单栏显示异常

再跑跑游戏,会发现最上边的Menu菜单里多出来很多奇怪的横线(分割符),和官方版本显示效果不同。

一阵尝试之后发现这些横线是kag/system/menus.tjs里的

systemMenu.add(new MenuItem(this, "-"));

代码造成的。

但经我仔细调查后发现这事情有点复杂。

首先,所有的菜单和菜单项都是在kag/system/menus.tjs的KAGWindow_createMenus函数中创建的,里面甚至包括了最终不可见的菜单项,比如Debug专用菜单等。

我们看到的横线其实是后来在kag/system/config.tjs的Menu_visible_config函数里隐藏掉部分菜单项后遗留下来的分隔符(因为分隔符自身并没有设置visible=false)。

很奇怪吉里吉里2为什么会帮你隐藏掉这些没用的分隔符,但显然吉里吉里Z没这么智能,你只要不显式设置visible为false,它就会一直显示在那。

要解决这个问题有很多种办法,比如粗暴的把所有最终不打算显示的菜单项和分隔符一开始就不add进去(注释掉add代码)。(我们的VBFI就是这么对应的XD)

但这不够优雅,而且改动也比较大,要同时修改menus.tjs和config.tjs且要保持一致性。

我最终选择了这么一个方案,重载了KAGMenuItem的visible属性,让它在被设定为false时智能的隐藏掉其后无用的分隔符,代码如下:

// kag/system/menus.tjs

class KAGMenuItem extends MenuItem
{
    ...

    property visible
    {
        getter { return super.visible; }
        setter(x) {

            if (super.visible == x)                      // 可见状态没变化,什么也不用做
                return;

            super.visible = x;

            if (x || parent == void || caption == "-")   // 可见状态变为true,或者该菜单项自身就是分隔符则不继续处理
                return;

            if (index == parent.children.count - 1)      // 如果已经是最后一个菜单项,其后已经不可能有分隔符了,因此不需要继续处理
                return;

            // 从上一个菜单项向前遍历
            for (var i = index - 1; i >= 0; i--) {
                var prev = parent.children[i];
                if (prev.caption == "-")                 // 在前面没有遇到任何可见菜单项时先遇到了分隔符,表示该分组里从开始到当前菜单项都不可见, 退出循环检查后面的菜单项
                    break;
                if (prev.visible)                        // 如果先遇到了可见菜单项,则该分组里还是有可见菜单项的,不用隐藏后面的分隔符了
                    return;
            }

            // 从下一个菜单向后遍历
            for (var i = index + 1; i < parent.children.count; i++) {
               var next = parent.children[index + 1];
               if (next.caption == "-") {                // 如果在没有遇到任何可见菜单项时先遇到了分隔符,则分组里所有的菜单项都变得不可见,隐藏后面的分隔符
                    next.visible = false;
                    break;
               }
               if (next.visible)                         // 如果先遇到了可见菜单项,则该分组里还是有可见菜单项的,不用隐藏后面的分隔符了
                    return;
            }
        }
    }

    ...
}

这个方法虽然谈不上不完善,但是菜单问题也算是被解决了。

hint显示不出来

很快又发现另一个问题,hint不显示了。

hint即Layer.hint属性,就是所谓的鼠标Tips,当鼠标移到目标(如按钮)上时显示出来的文字提示框。

官方文档对此有专门说明:https://krkrz.github.io/documents/TJS2/tooltip.html

大致意思是说吉里吉里2的hint是用VCL的自带机制实现的(科普一下,吉里吉里2是用很古老的C++ Builder编译器编译的,VCL是Borland公司设计的GUI库,是Delphi也用的一个上古神兽),而吉里吉里Z立志跨平台,怎么能接受用VCL的式样来决定显示效果呢?(这理由我竟无法反驳)

于是吉里吉里Z使用了更通用的方式来支持这一功能,提供了新的Window.onHintChanged事件来处理hint状态变化响应。

但是,它仅提供了个事件,没有任何描画,所有描画需要你自己在这个事件响应函数里自己实现。还好上述URL里提供了实现代码,可以直接拿来用。

不过要注意还需要在onHintChanged函数的最后加一句

hintLayer.bringToFront();

不然这个hint提示界面有可能被别的东西挡住……

这样一来hint是画出来了,但是和官方版本一对比发现还是有区别的。

首先hint左上角的位置,官方版本比上述方法更靠下,这样鼠标光标不会挡住字。所以我们还需要在onHintChanged函数的开头给y坐标加一个偏移:

y += 20;

然后字体略有不同,这个问题我最终也没找到解决办法,毕竟VCL是怎么实现的我们不知道。

但很明显不是等宽字体,所以我加了一句

hintLayer.font.face = 'MS Pゴシック';

但即使这样字体看上去还是有少许差异。但毕竟这玩意没有绝对对错,就只能先这样了。

日文字体显示不正常

说到字体,如果你的系统内码不是日文的,那进入游戏后你可能会发现一些文本的排版会乱掉,比如兵种的属性数值等等。而日文内码却无此问题。

研究了半天发现是Font.face的问题。

VBA的脚本中使用了四种日文字体,原名称分别是MS ゴシック、MS Pゴシック、MS 明朝、MS P明朝。我发现在非日文内码下,其实这些字体名并没有生效,最终会使用默认的系统字体。但如果把它们分别改成MS Gothic、MS PGothic、MS Mincho、MS PMincho的话,就可以正常显示了。

说实话为什么会这样我也不明白,理论上英文名称和日文名称都应该可以的,但实测下来就是不行,看来字体的英文名称和日文名称还是有一些我不知道的区别的。

那怎么改呢,一种办法自然是全部替换,但这要改的地方就太多了,不仅.tjs文件里有,.ks文件里更是多。且还不能只替换,我确认了吉里吉里Z的源代码,Font.face的默认值就是MS ゴシック,所以你还得手动初始化一遍,很容易漏。

而且更要命的是,这种写法(使用英文字体名)在日文内码下反而不认,所以还得能根据操作系统内码动态切换,全局替换显然不靠谱。

于是我想了一个馊主意,重载Font.face属性行不行?

经过一系列的实测,我发现TJS2中,类的成员方法是可以通过直接付与新值来强行替换的,甚至连构造函数都可以。但是成员变量和属性却不行,成员变量是改了没反应,而属性则是没有任何一种语法可以篡改。

于是我调整方案为“重载”Layer.drawText方法,让它在描画文字之前,把字体名给替掉。代码如下:

var lang = System.readRegValue('HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Nls\\Language\\Default');  // 从注册表获得当前系统内码
var layerDrawText = Layer.drawText;    // 先保存原始函数。注意:TJS2的语法要求闭包参数必须写在外层,如果这句写到if语句内会引用不到

if (lang != '0411')                    // 如果是日文内码则什么也不用做
{
	var wrapper = function()           // 不能写成function wrapper(), TJS2的语法里只有成员方法和匿名函数支持闭包, 普通函数不行
	{
		switch (font.face)             // 篡改font.face
		{
			case 'MS ゴシック':
				font.face = 'MS Gothic';
				break;
			case 'MS Pゴシック':
				font.face = 'MS PGothic';
				break;
			case 'MS 明朝':
				font.face = 'MS Mincho';
				break;
			case 'MS P明朝':
				font.face = 'MS PMincho';
				break;
		}

		layerDrawText(...);            // 调用闭包里的原函数,...是全参数直传语法
	};

	Layer.drawText = wrapper;          // 强行更改Layer.drawText方法。注意不能写成Layer.drawText = function () { ... },会报语法错误
}

这段代码要放在所有实际文字描画的时机之前,最简单当然是放在startup.tjs里。

但是考虑到startup.tjs不支持patch,我最终选择把它放在kag/system/kaglayer.tjs的最前面。这样一来能确保在所有描画之前,又能支持patch,且文件名和代码的语义也算有联系。

功能性移植完成

至此游戏自身的所有功能性移植就都完成了。如果你只满足于能让游戏正常玩的起来,那看到这里就够了。你只需要再执行:

kikiriki -c -i data -o data.xp3

把data也打包,最后删除data目录就算是结束了。

不过接下来我还会尝试处理一些修饰和外围功能的对应。比如显示区别于官方版本的游戏名称、存档加载目录、生成带图标的可执行文件等等,有兴趣的可以继续往下看。

游戏名称显示修改

经确认有两处相关代码:

  • game_system/adv_core/first.ks
    [eval exp="sf.patch_set_name = 'VenusBlood-ABYSS-'"]
  • kag/system/config.tjs
    ;System.title = "VenusBlood-ABYSS-";

两处的"VenusBlood-ABYSS-"都改掉即可,我改成了"VenusBlood-ABYSS- for KrkrZ"。

注意:名字太长的话在About界面会看不到后面的版本号。

版本号也在这附近,不过为了让用户明确知道这是移植至1.08版,就不改了。

游戏存档目录修改

关于游戏存档,尽管http://keepcreating.g2.xrea/DojinDOC/Migration_to_KRKRZ.html说移植前后存档格式不兼容,但是我试过至少这个游戏按我的移植方法是兼容的。

既然兼容,就让我们的Z版和原版读写同一个存档目录吧,这样用起来也方便。

如果你不放心非要改,那就改动kag/system/mainwindow.tjs的:

var saveDataLocation = System.personalPath + 'game_savefiles\\Venus_Blood_ABYSS';

即可。不过要注意,后面会提到的-userconf选项也会用到该目录路径,如果你真的要改,那么.cf文件也需要改成一样的,具体后面会说。

打包data.xp3

到此为止所有的脚本处理就彻底结束了,可以打包data.xp3了:

kikiriki -c -i data -o data.xp3

然后删除data目录。

制作发布版.exe文件

首先我们需要提取官方版本的图标文件,可以使用Resource Hacker软件来进行,我这里就不赘述了。假设我们从官方版本的ABYSS.exe中提取出来了图标文件,命名为ABYSS.ico,后面会用到。

对于吉里吉里2,官方提供了一个名为krkrrel.exe的发布工具可以用来打包xp3,同时也可以生成自己的.exe。

这里有用法介绍:https://krkrz.github.io/krkr2doc/kr2doc/contents/Releaser.html

注意:该软件较古老,非日文内码系统下会显示乱码,但不影响使用,可以凑合用。

因为我们都用kikiriki.exe打的xp3, 打包功能就不用它了(要用也OK的,格式是一样的),只用它来制作.exe。

直接用是不行的,这个工具原本是用来生成吉里吉里2可执行文件的,直接用打出来也只能跑吉里吉里2脚本,我们需要做一点改造。

解压吉里吉里2(kr2_232r2.zip)后目录结构大概长这样子:

--+-- [kag3]                     ; KAG3脚本目录,和我们这次无关
  |
  +-- [kirikiri2]                ; 吉里吉里2主目录
  |       |
  |       +-- [tools]            ; 工具集目录
  |       |      |
  |       |      +-- krkrrel.exe ; 我们要用的发布工具
  |       |      |
  |       |     ...              ; 还有其他工具,目前用不上
  |       |
  |       +-- krkr.eXe           ; 吉里吉里2可执行文件,krkrrel.exe发布exe文件时使用它作为模板文件
  |       |
  |      ...                     ; 其他文件目录,暂时和我们无关
  |
 ...                             ; 其他文件目录,暂时和我们无关

我们要做的就是用吉里吉里Z的tvpwin32.exe替换掉krkr.eXe作为模板。但是直接替掉还不行,krkrrel.exe会检查krkr.eXe是不是正确的吉里吉里2可执行文件,所以我们得绕过它的检查。

首先先别急着替换krkr.eXe,先启动krkrrel.exe,它会让你选择目录,随便选个就行,反正用不上。

然后会显示出来krkrrel.exe的主界面。在主界面显示出来之后我们就可以替换krkr.eXe了,将原krkr.eXe删除,把tvpwin32.exe复制到原来krkr.eXe的位置并改名为krkr.eXe。

然后回到已经打开的krkrrel.exe,选择导出为exe格式,再来最后一个页签选择图标文件为我们上面得到的ABYSS.ico,最后点制作就能得到我们要的exe文件了。

至于其他选项看着办,我也不知道是不是对吉里吉里Z也有效。

注意:上面点击制作后会让你写输出文件名,请填“VBAZ”,这样会生成VBAZ.exe和VBAZ.cf两个文件。尽管不一定非要叫VBAZ.exe,但请不要叫和官方版本一样的ABYSS.exe,其理由与后面的-userconf选项有关,下面就会解释。

“エンジン設定.exe”与-userconf选项

官方版本游戏目录里有个“エンジン設定.exe”可以用来对游戏引擎进行配置,直接把这个exe文件复制到我们的工程目录下是不能用的。如果你想要保留这个功能应该怎么办呢?

其实吉里吉里Z已经将这个功能集成到tvpwin32.exe里了(即我们的VBAZ.exe),只需要用-userconf参数执行这个exe文件就能打开引擎配置界面。

然后我们立刻尝试一下VBAZ.exe -userconf,结果界面是出来了,但里面内容是空的!对,有界面没选项。肿么回事?

网上搜了一圈,结果令人大跌眼镜。并不是吉里吉里Z偷懒没实现,也不是我们的移植工作有什么问题,而是吉里吉里Z发布版的BUG。

该BUG修正的commit记录在这里:https://github/krkrz/krkrz/commit/7d10837ae39bbe0a6b993a4ff84a0f068a76ab2f

原因很简单,内部配置文件option_desc_ja.json语法错误,多打了一个逗号导致JSON解析失败,于是就显示成了空白……

糟糕的是该BUG的修正时间是2019年,而吉里吉里Z最后一版Release是2017年的……

我在网上找到的参考做法是自己编译master……我看了看源代码觉得还是算了,太麻烦了,我只是要改个逗号而已……

于是我又开始寻思旁门左道,看了吉里吉里Z的VC工程文件,确定了这个option_desc_ja.json是作为Windows资源被打进exe的。那么自然而然方案就有了,我直接去改VBAZ.exe的对应资源不就得了?

再次请出Resource Hacker,打开VBAZ.exe,会在TEXT资源里找到这个json文件的内容,ID为141,语言为日语(1041)。

不过不能直接在Resource Hacker改,因为至少我用的这版最新的Resource Hacker v5.2.7,它会自动给这个文本资源加上UTF-8 BOM,然后程序就不认了,依然会解析失败。只能用先把该资源导出,用其他文本编辑器编辑后再导入的方式才行。

导出后删掉第901行:

{ "value":"no", "desc":"制限しない" },

最后面的逗号,保存(记得确认该文件编码必须是UTF-8无BOM的),再用Resource Hacker导入覆盖掉原资源即可。

再加-userconf启动VBAZ.exe,终于可以看到配置项了,似乎和吉里吉里2的エンジン設定.exe没啥区别。(其实我看到很多正规游戏这个选项也都还是BUG着的,比如VBFI……)

最后还有一步,把官方版本的ABYSS.cf复制过来,替代掉VBAZ.cf。到此为止,我们的移植工作就全部完成了。

最后解释一下最后这个.cf起什么作用,以及为什么不建议使用ABYSS.exe作为程序文件名,还有如果你想改变存档位置,还需要做什么工作。这些其实都和-userconf有关。

不论是-userconf选项或者エンジン設定.exe,如果你使用它们调整了引擎参数,就会在“存档目录”生成一个.ucf文件。如果你的可执行文件名是VBAZ.exe,生成的文件名就是VBAZ.ucf。

“存档目录”我打了引号,因为该目录在VBA官方版本中确实就是游戏存档目录如C:\Users\<用户名>\Document\game_savefiles\Venus_Blood_ABYSS\下。

但是与上面提到的kag/system/mainwindow.tjs中的saveDataLocation不同,该路径是单独配置到.cf文件里的。如果你的可执行文件名是VBAZ.exe,那对应的配置文件就是VBAZ.cf。

用文本编辑器打开VBAZ.cf(原ABYSS.cf), 你会看到里面有这样的内容:

datapath="\x24\x28\x70\x65\x72\x73\x6F\x6E\x61\x6C\x70\x61\x74\x68\x29\x5C\x5C\x67\x61\x6D\x65\x5F\x73\x61\x76\x65\x66\x69\x6C\x65\x73\x5C\x5C\x56\x65\x6E\x75\x73\x5F\x42\x6C\x6F\x6F\x64\x5F\x41\x42\x59\x53\x53"

很明显是十六进制ASCII码,解码后等价于:

$(personalpath)\\game_savefiles\\Venus_Blood_ABYSS

这不就是我们的存档目录嘛。所以如果你改了存档目录saveDataLocation,你还需要把VBAZ.cf也改了,否则.ucf文件会存歪地方。

而且上面你也看到了,.ucf文件的文件名前缀是跟着可执行文件的名字走的,如果你还用ABYSS.exe,就会和官方版本的配置冲突。换一个名字就可以两者兼容,何乐而不为。当然你要是不用和官方版本相同的存档路径的话就无所谓。

结语

以上就是所有移植的内容了,里面很多知识和代码都是可以用于其他基于吉里吉里2引擎的游戏的,不仅限于VBA。

希望能给所有想把吉里吉里2游戏移植到吉里吉里Z的人起到参考作用。

再进一步,希望能提高全网吉里吉里Z移植的品质,降低移植不完整带来的遗憾。

附录A:Cvt2Utf8的使用方法

执行以下命令即可:(<path>是工程目录, 即xp3解包的根目录)

Cvt2Utf8 <path>

Cvt2Utf8在转码完成后会用patch目录下存在的文件去覆盖其他目录中存在的同名文件,且patch下存在的原文件会被自动删除。

附录B:kikiriki使用方法

  • 解包.xp3命令:
    kikiriki -e -i xxx.xp3 -o xxx
  • 打包.xp3命令:
    kikiriki -c -i xxx -o xxx.xp3

主要参考资料

  • 吉里吉里Z官方主页 - https://krkrz.github.io/
  • 网友写的吉里吉里Z移植手册(成文较老,当时可能还没有Krkr2Compat这样的利器) - http://keepcreating.g2.xrea/DojinDOC/Migration_to_KRKRZ.html
  • 网友提到KAGEX实现[quake]并不使用setLayerPos - http://keepcreating.g2.xrea/DojinDOC/kirikiriSmallTips.html#win8quake
  • 网友给出的-userconf选项显示空白的解决方案 - https://red-souls.jp/ichounoki/rnote/software/20190210_162253605864.htm

本文标签: 实战吉里吉里到吉里吉里