首页 > 编程语言 >ACV1引擎脚本处理(C++)

ACV1引擎脚本处理(C++)

时间:2022-11-17 00:03:50浏览次数:72  
标签:std 编码 字节 01A C++ 引擎 ACV1 文本 我们

推荐在Github进行阅读:ACV1引擎脚本处理(C++)

ACV1引擎脚本处理(C++)

0x00 知识背景

开始处理脚本之前希望大家熟悉或至少用过以下知识内容。

1、C 基础语法

2、C++ 面向对象基础

3、std::string

4、std::vector

5、std::fstream

文章附带样本和源码:ACV1TextEditor

相关知识背景:C++文本处理基础知识 , 初步了解C++对数据封装的设计思维

0x01 处理效果与目的

本次处理的效果是对明文脚本进行代码和文本分离

生成便于对文本进行修改的格式

并且能够将修改的文本进行导入

0x02 处理原则

该处理仅为效果展示,并未实际运用到项目中

游戏脚本中存在大量并未在游戏中实际出现的文本

这些文本可能关联音频,可能关联文件名

这部分我们都暂时不去处理,以免对游戏正常运行造成影响

当遇到需要提取的时候

可以按照已经确定作用并且需要修改的部分,进行相应的过滤提取

唯一原则就是,尽量不去提取未知作用的文本

未知作用的文本尽量保存原状

0x03 声明

该文章仅供交流学习之用

切勿用于任何商业牟利用途

我们不支持任何形式的非法翻译活动(包括机器翻译)

我们反对任何形式以非法翻译来进行牟利的活动(包括某些所谓的官方中文)

对于任何不符合相关法律法规的活动我们都不会提供任何支持与帮助

任何与上述不符的组织或个人不得使用我们提供的工具及教程

0x04 载入文件

我们先定义一个DumpText函数,它的返回值是bool,也就是 是否提取成功

等下我们所有的提取操作都在这个函数里进行。

bool DumpText()
{
    
}

之前我们已经了解过了fstream(引入头文件和名称空间的问题这里不再谈及)

那么要处理脚本,也就是处理一个文件,第一步自然是打开文件。

由于打开文件需要文件名

文件名现在我们还不知道,但肯定是外面传进来的,故定义为函数的参数。

由于打开的是SJIS编码的脚本,这里就不用特意设置打开格式了。

bool DumpText(std::string strFileName)
{
    std::ifstream iScript(strFileName);
	if (iScript.is_open())
	{
		//To Do
		iScript.close();
	}
}

0x5 循环读取行

借助getline();函数加for循环

我们可以以行为单位轻松从fstream中读取脚本(需要头文件)

当然为了以后方便导入我们还要定义一个变量来记录读取的是哪一行。

我们在To Do的地方写如下代码(为了节省篇幅之后也是这样不再赘述)

size_t lineCount = 0;
for (std::string perLine; std::getline(iScript, perLine); lineCount++)
{
	//To Do
}

这样每循环一次,perLine里面存的就是脚本中一行的文本

getline();会自动获取下一行,循环结束时,即为脚本已读取完毕

lineConut会记录当前读取的是哪一行,基准是从0开始

如果你想和sublime一样的从1开始的行号,把初始化的lineCount = 0改成lineCount = 1即可

0x06 筛选行基础知识

现在我们已经在for循环里一行行读取脚本了

也就是每循环一次,就会读进来脚本的一行

所以现在我们可以来筛选需要的行,也就是保留文本行剔除代码行。

此处的筛选依据不同的游戏脚本有不同的方法

通常来说这种明文游戏脚本,

每一行开头都有明显的特征可以判断是代码行还是文本行,

可以有各种方法来判断需要的文本行,我们这只是以其中一种方法来演示

我们以后还会讲解其它的游戏脚本,大家看多了自然就知道要么筛选了。

*01A_0609_0


CHAP 01

DAY 2015, 6, 9



BGM BGM_102

BG BG_200_00_0, 500@1

WA 2000,,0

SE0 , 250
ST2 S005_1AB1AA_001_M, 0, 0, 0
BG BG_205_00_1, 500@1

DATA 午前

【イヴ,S005_01A_0015】「みなさん、おはようございます![n]今日もとっても良いお天気ですね♪」

生徒会長であるイヴ先輩の声が講堂内に響き渡る。

俺達ルミナス学園の全校生徒は講堂へと集められていた。

BG BG_205_00_0, 0
SC.FD 250,,0

【虎春】「臨時の全校集会って……最近、なんかあったっけ?」

ST2 S011_1AA1AA_060_M, 250, 0, 0

观察这个脚本每一行的开头

可以发现,如果是文本行,开头肯定是一个双字节的字符

比如 【 <-这个括号其实是双字节的,生,俺,这两字也是双字节的

那么再说明白一点,其实这三个字符都是SJIS编码的

那么SJIS编码有啥特点呢?最直观的,大家以GBK读取这个脚本

会发现这几行都乱码了,但是其它的似乎没问题

之前也说过GBK和SJIS是很像的

为了解GBK和SJIS这种编码的方式,得先了解ASCII

ASCII这种编码方式,其实就是上面显示的那些英文部分

即使这个文本是SJIS编码的,你读取成GBK其实这部分也不会乱码

从这里我们知道,SJIS编码和GBK编码这部分应该是相同的

不然应该也会乱码才对,是的,其实SJIS和GBK都是从ASCII扩展出来的。

ASCII的编码宽度是一个char,也就是一个字节0x00-0xFF

大家可以网上找一下这个ASCII编码的表,可以发现

这个表到0x7F就没了。也就是说这一个字节还有一半都没用到

不过就算这一个字节全部用来表示中文,也编码不下这么多的汉字

所以我们如果要给汉字编码,也就是给每一个汉字一个编号的话

不得不引入第二个字节,因为一个字节存不下嘛

由于之前那一个字节前面一半已经用掉了,而且这些字符也是常用的

故GBK和SJIS都保留了这些,并以此为基础新增加一个字节

在第一个字节的后一半和第二个字节来组合编码相应的字符

了解完上述的知识,

我们虽然不知道SJIS的编码范围

也不知道GBK的编码范围,实际上我们也不用知道他们的范围

但是我们可以知道,如果读入一行

这一行的第一个字节如果是小于7F的

那么这一行的开头必然就不是汉字或日文字符(仅限窄字节编码)

回来看我们的游戏脚本,可以发现,只要是文本的行

开头的第一个字符必然是日文的相关字符。

当然也有意外,比如:

{電影領域戦闘/スクリーンバトル}システム」

有些时候是这样一行,那么开头这个 { 符号其实是ASCII范围内的,

也就是小于7F的,那么将会被过滤掉

比如:

SELECT "チュートリアルを見る,*72A_0002_0", "チュートリアルを見ない,*72A_0002_1"

SELECT后这段文字其实是游戏内的选项部分,这个是需要翻译的

这一行由于开头也是一个ASCII范围内的字符,肯定也是筛选不出来的

而且比之前 { 这个符号更难筛选,S 这个开头的字符太多了

{ 虽然是ASCII编码范围内的,但这个字符对应0x7B

我们完全可以筛选0x7B以上的字符,和0x7F差不多

对于第二种情况的问题

我们的原则还是宁愿漏掉,也不去筛出过多不需要的行

因为漏掉的,我们后面可以慢慢修正

比如第二种情况,完全可以加一个过滤的条件

用字符串查找功能,查找这一行开头有没 SELECT "这个字符串

0x07 筛选行

在筛选行之前,我们先创建一个文件来写出我们筛出来的行

std::ofstream oText(strFileName + ".txt");
std::ifstream iScript(strFileName);
if (iScript.is_open() && oText.is_open())
oText.flush();
oText.close();
iScript.close();

在之前打开文件的地方稍微改一下

好的,回到for循环这来。

首先如果读进来的是空行的话,我们就跳过

for (std::string perLine; std::getline(iScript, perLine); lineCount++)
{
	if (perLine.size() <= 0)
	{
		continue;
	}
}

之后读进来的行,肯定就不是空的了

我们来判断一下,这行的第一个字节

for (std::string perLine; std::getline(iScript, perLine); lineCount++)
{
	if (perLine.size() <= 0)
	{
		continue;
	}

	if (perLine[0] >= 0x7B)
	{
		//To Do
	}
}

这样写可以吗?

我们之前已经说过了

perLine[0]取出来的是一个char,这里是字符串的第一个字节(窄字节编码)

如果大家有认真学习C语言的基础就可以知道

char是一个有符号的类型,也就是0x00-7F为正数,0x80-0xFF为负数

举个例子,char a = -1; 那么a其实是0xFF

如果按照之前说的,读入的是一行,开头第一个字符的日文的

那么可知这个字符必然大于7F,也就是说

perLine[0] 会表示成一个负数

那么必然不可能进入if里面,这也就是我们不希望发生的情况。

所以我们修正一下

if (((unsigned char)perLine[0] >= (unsigned char)0x7B))
{
	//To Do
}

让它当成无符号的char处理即可。

当然你还可以增加筛选条件,只需要用两条竖线隔开即可

if (((unsigned char)mLine[0] >= (unsigned char)0x7B) || 
	mLine.find("CS \"") != string::npos ||
	mLine.find("SELECT \"") != string::npos
	)

可以看到这有三个条件,任何一个满足都会进入if

"SELECT \""这个其实就是筛选 "SELECT "的行

多一条斜线其实是C语言定义字符串的问题,不然会和外面的引号混起来。

当然这段写的不是很好,find是会整行查找的,不限于开头几个字符

大家可以去思考一下,如何修正这一点。

好了,如果你试着在调试模式调用一下这个函数,并给他传递

我们样本的文件名,记得把样本放到.cpp同目录下()

由于我们的DumpText()函数需要一个bool类型的返回值

我们在函数末尾加一个 return true; 就好了

具体要返回什么,在什么地方返回,就由大家自己判断了。

现在可以发现,进入这个if的在vs上看起来都是乱码的一堆字符

其实那就是SJIS编码的日文,也就是游戏文本部分,只不过是VS的问题显示不出来

现在我们把筛选出来的行,写入到输出的文件里

oText << perLine << std::endl;

执行完毕后会发现多了一个.txt文件

用SJIS编码打开,发现是这样的

【イヴ,S005_01A_0015】「みなさん、おはようございます![n]今日もとっても良いお天気ですね♪」
生徒会長であるイヴ先輩の声が講堂内に響き渡る。
俺達ルミナス学園の全校生徒は講堂へと集められていた。
【虎春】「臨時の全校集会って……最近、なんかあったっけ?」
【有希,S011_01A_0042】「それが僕も何も聞いてないんだよ。[n]今朝、急にやることが決まって」
生徒会役員であるユキが聞いてないってことは、[n]本当に緊急で決まったんだろうな。
【有希,S011_01A_0043】「なんか理事長がボヤいてたよ。[n]あそこはいつも無茶を押し付けてくるって」
【虎春】「あそこ?」
【有希,S011_01A_0044】「うん。どこからかはわからないけど、[n]何か急に頼み事されたんじゃないかな」
【虎春】「頼み事かあ。イヴ先輩の様子を見るに、[n]悪い報告では無さそうだけど、何なんだろうな」
【有希,S011_01A_0045】「さあ? まるで思いつかないよね」
周囲の生徒たちも、俺達と同じような反応だ。

其实已经成功了一半了,现在只需要稍微排版一下,然后加一些辅助我们导入文本的信息

稍微排版一下,并且加上行信息,方便日后插入

oText << "LineCount:" << lineCount << '\n';
oText << "Raw:" << perLine << '\n';
oText << "Tra:" << '\n' << '\n';
LineCount:21
Raw:【イヴ,S005_01A_0015】「みなさん、おはようございます![n]今日もとっても良いお天気ですね♪」
Tra:

LineCount:23
Raw:生徒会長であるイヴ先輩の声が講堂内に響き渡る。
Tra:

LineCount:25
Raw:俺達ルミナス学園の全校生徒は講堂へと集められていた。
Tra:

LineCount:30
Raw:【虎春】「臨時の全校集会って……最近、なんかあったっけ?」
Tra:

LineCount:34
Raw:【有希,S011_01A_0042】「それが僕も何も聞いてないんだよ。[n]今朝、急にやることが決まって」
Tra:

现在看着就有模有样了,仔细观察的话,行数其实和sublime差了一行

因为我们是以0为基准开始计数的,如果改成1就和sublime的行号一致了

如果你需要双行文本的话,也很简单

这里稍微改一下即可

oText << "Tra:" << perLine << '\n' << '\n';
LineCount:21
Raw:【イヴ,S005_01A_0015】「みなさん、おはようございます![n]今日もとっても良いお天気ですね♪」
Tra:【イヴ,S005_01A_0015】「みなさん、おはようございます![n]今日もとっても良いお天気ですね♪」

LineCount:23
Raw:生徒会長であるイヴ先輩の声が講堂内に響き渡る。
Tra:生徒会長であるイヴ先輩の声が講堂内に響き渡る。

LineCount:25
Raw:俺達ルミナス学園の全校生徒は講堂へと集められていた。
Tra:俺達ルミナス学園の全校生徒は講堂へと集められていた。

LineCount:30
Raw:【虎春】「臨時の全校集会って……最近、なんかあったっけ?」
Tra:【虎春】「臨時の全校集会って……最近、なんかあったっけ?」

LineCount:34
Raw:【有希,S011_01A_0042】「それが僕も何も聞いてないんだよ。[n]今朝、急にやることが決まって」
Tra:【有希,S011_01A_0042】「それが僕も何も聞いてないんだよ。[n]今朝、急にやることが決まって」

这里我们也把人物名,一起提取出来的

因为我们是筛选行,所以没对行内的数据进行处理

如果不希望提取出人物名,可以用之前介绍的 substr 进行处理

去判断这个 括号字符即可。

0x08 导出文本结束语

好了,导出文本到这就结束的

虽然我们还有些事情没干,比如文本的编码还没进行转换

导出文本本质上是很简单的

我们这篇幅较长的原因是进行了一些背景的铺垫。

下一节我们将讲解编码的转换和文本导入

标签:std,编码,字节,01A,C++,引擎,ACV1,文本,我们
From: https://www.cnblogs.com/Dir-A/p/16898014.html

相关文章

  • c++定义二维数组
     vector<vector<int>>vec;        //定义行列未知的二维数组vector<vector<int>>array(5);     //定义行为5的二维数组vector<vector<......
  • C++:将char*指针强制转换成一个指向结构体的指针
    在使用Socket与雷达进行通信采集数据时,会遇到“打包与解包”的问题,在打包和解包过程中,会涉及到结构体指针与字符指针间的强制转换。如下:打包就是将包头与信息封装成一定大......
  • Linux环境下配置vscode的C/C++ 的make编译环境(编写makefile方式)代码Demo版
    以前写过同样话题下的图文版的,这里给出一个代码Demo版本,上一个图文版本参见:​​Linux环境下配置vscode的C/C++的make编译环境(编写makefile方式)​​  ===================......
  • tensorflow1.x——如何在C++多线程中调用同一个session会话tensorflow1.x
     =================================================  从前文​​tensorflow1.x——如何在python多线程中调用同一个session会话​​可以知道,使用python多线程调用同一......
  • windows--cmake与c++的使用教程(9)
    1概述本文基于前文环境本节目标:动态库和Exe项目修改CMake默认输出路径2准备工作目录结构.│CMakeLists.txt└─srcmain.cc2.1代码准备main......
  • C++Primer 第2章
    第2章变量和基本类型数据类型是程序的基础:它告诉我们数据的意义以及我们能在数据上执行的操作。2.1基本内置类型C++定义了一套包括算术类型(arithmetictype)和空类型(vo......
  • C++学习------cstdint头文件的源码学习01---类型定义
    引言cstdint头文件是C++对stdint头文件的封装,这个头文件定义了一系列特定长度的类型别名,一系列值的上下限,以及一系列类型转换的宏。我们一起来看看它的内部实现。代码参考:......
  • C++并发编程实战 第二版 电子书 pdf
    作者:安东尼·威廉姆斯(AnthonyWilliams)出版社:人民邮电出版社出品方:异步图书原作名:C++ConcurrencyinAction,SecondEdition 链接:C++并发编程实战第二版 ......
  • 01-C++ 程序的编译和运行
    第一种方式将编译和链接分成两步。#编译,生成.o文件g++-chello.cpp#链接,生成可执行文件a.outg++hello.o#链接,生成可执行文件hellog++hello.o-ohello......
  • C++走向远洋——67(项目二、洗牌)
    */*Copyright(c)2016,烟台大学计算机与控制工程学院*Allrightsreserved.*文件名:text.cpp*作者:常轩*微信公众号:Worldhello*完成日期:2016年6月9日*版本号:V1.......