psp游戏文本提取经验分享

目录

前言

一、提取工具

二、提取步骤

1.找到文本位置

2.提取文本

总结


前言

        本人在大学期间接受过一个委托,要求我将一个PSP游戏中关于剧情的文本全部提取出来。由于本人没有相关经验,便在互联网中寻找相关教程。但网上的大多PSP资源提取教程都是关于影像和音频的,关于文本的却寥寥无几。最终从几篇远古文章中,找到思路完成了委托,于是有感而发写下这篇文章。

注意:不同psp游戏的文件结构可能不一样,该文章仅供参考。


一、提取工具

  1. Windows 11。Windows 10以上的电脑可直接打开PSP游戏文件——光盘映像文件 (.iso),否则得使用第三方软件。
  2. MadEdit。MadEdit是一款支持多种编码格式和语言的文件编辑器,用于查看文件内容。
  3. Python 3.10。这个无需多言,不会的话可用其他编程语言代替。
  4. PSP游戏文件。本文所用到的游戏为《加速世界-银翼的觉醒》。

二、提取步骤

1.找到文本位置

双击打开游戏文件

映入眼帘的是三个文件夹和三个文件。先看文件,png是图片,剩下那个1KB里面肯定没有什么。然后三个文件夹,第一印象INSDIR应该和内部(不知道是什么)有关,SYSDIR应该和系统有关,USRDIR应该和用户有关。三个文件夹我都仔细看过,直接说结论,最后在INSDIR里面找到的文本。


双击打开INSDIR文件夹

又是几个文件,这次我们直接锁定到RES.DAT这个文件。文件大小大概300MB,RES好像是resource的缩写,直接从这个文件入手。


使用MadEdit打开RES.DAT

打开一看头都大了,左边十六进制右边乱码加英文字母。这个英文也是好多.dat,第一印象就是一个文件里面又有很多子文件。我会在下面慢慢解释。

①文件魔数,是存储在文件头部的一组特定的字节序列,用于标识文件的格式和类型,这里的是GPDA。

②文件大小,0x12533800字节。

我们把编辑器拉到最底部,发现最后的地址就是0x12533800,如上图所示。

③子文件数量,0x12,也就是十进制18。.dat后缀英文的数量刚好是18个。

④子文件偏移地址。第二行开始为子文件信息,每个子文件信息占0x10字节,也就是一行。这里是第一个子文件偏移地址,为0x0800。

我们把编辑器拉到0x0800处,可以看到GPDA,说明这是一个.dat文件开头,如上图所示。

⑤子文件大小。这里是第一个子文件的大小,为0x03070800字节。

⑥子文件名称相关信息偏移地址。这里是第一个子文件名称相关信息偏移地址,为0x0130,所以第一个子文件名称信息的位置在0+0x0130=0x0130处,详情请看⑦。

⑦子文件名称相关信息。这里为第一个子文件名称相关信息,前4个字节为子文件名称的长度,这里为0x0B个字节长度,文件名结尾固定为0x20。

总的来说,就是一个文件里又有许多子文件,文件头里存储着这些子文件的信息。


把文件结构搞清楚了就可以着手拆包了,Python代码如下。 

#dat_unpack.py
import os
import struct
import fnmatch
from tkinter import filedialog


#获取文件夹中所有后缀为.dat的文件位置
def getfn(adr):
    flist = []
    for root, dirs, files in os.walk(adr):
        for name in files:
            if fnmatch.fnmatch(name, '*.dat') or fnmatch.fnmatch(name, '*.DAT') :
                adr_list = os.path.join(root, name)
                flist.append(adr_list)
    return flist


# 获取文件夹路径
folder_path = filedialog.askdirectory()
list = getfn(folder_path)

for fn in list:
    with open(fn, 'rb') as f:
        sig = f.read(4)  # 魔数
        baseOffset = 0  # 基础地址
        f.seek(0x0C)
        num = struct.unpack('I', f.read(4))[0]  # 子文件个数
        if not os.path.isdir(('%s\\' % (fn + '_unpack'))):
            os.makedirs(('%s\\' % (fn + '_unpack')))  # 创建个解包文件夹
        for i in range(num):  # 写个循环
            f.seek(0x10 + 0x10 * i)
            childOffset =  struct.unpack('I', f.read(4))[0] #子文件偏移地址

            f.read(4)   #跳4个字节
            childSize = struct.unpack('I', f.read(4))[0] #子文件大小
            childNameOffset = struct.unpack('I', f.read(4))[0] #子文件名称偏移地址
            f.seek(baseOffset + childNameOffset)
            childNameLength = struct.unpack('I', f.read(4))[0] #子文件名称长度
            name = f.read(childNameLength).split()[0].decode('utf-8')  # 读取文件名

            f.seek(childOffset + baseOffset)
            data = f.read(childSize)

            new_file_name = '%s_unpack\\%s' % (fn, name)

            dest = open(new_file_name, 'wb')  # 输出文件
            dest.write(data)
            dest.close()

这个程序可以把指定目录下的所有.dat(.DAT)文件解包。


运行代码,选择RES.DAT所在文件夹。 

得到了18个.dat文件,但这个script.dat文件引起了我的注意(实际上这些文件我全都看了一遍)。 


我们使用MadEdit打开script.dat文件。

多少?0x0376个子文件。


再解!运行程序,选择script.dat所在文件夹。

又得到了好多.dat文件,有886个。随便找一个用MadEdit查看,发现又是嵌套文件,又有子文件。


再解!运行程序,选择script.dat_unpack文件夹,把这个文件夹的所有.dat文件都解开。

解出来八百多个文件夹。


我们随便打开一个文件夹(这里我打开第二个)。

又是三个文件。我用上帝视角告诉大家第一个.dat文件里没有要找的东西,不信可以运行程序再解。


使用MadEdit打开第二个文件。

又是左边十六进制右边乱码,如上图所示。头又大了,好像无从下手。把最开头的1F8B08放网上一搜,原来是用gzip压缩了的啊。


写个代码解压缩,Python代码如下。

import gzip
import re

fn = r''#嫌麻烦偷个懒,这里填要解压缩的文件路径

f=gzip.open(fn,'rb')
dat=f.read()

result = re.search("(.*)\.(.*)", fn)
dest = open(result.group(1) + '_unzip.' + result.group(2), 'wb')
dest.write(dat)
f.close()
dest.close()

这个程序能把指定gzip压缩文件给解压缩。


对这两个.gz后缀的文件使用解压缩程序。

多出两个文件。其中.dat_unzip.gz其实又是个嵌套文件,它的子文件都是wav.vol后缀的不是存储文本的文件,这里不再展开。


使用MadEdit打开.obj_unzip.gz后缀文件。

又是乱码,好像折腾这么久半个文本没找着,全是乱码。


试试看换个编码,在MadEdit菜单点击查看,再把编码换成UTF-16LE。

终于看到日文了。


再往下翻。

这些不正是对话框里的文本吗,如上图所示。文本位置算是找着了。


2.提取文本

先来分析一下这个剧情文本是怎么存储的

①每段文本前都有一个小写字母d(0x64)。

②d过后的第14个字节,就是文本长度。

③每段文本中必有"「"和"」"。

没找到其他信息了,就只能使用笨方法了,Python代码如下。

import os
import struct
import fnmatch
from tkinter import filedialog
import re

def getfn(adr): #获取当前目录以及子目录下的所有.obj_unzip.gz后缀文件路径
    flist = []
    for root, dirs, files in os.walk(adr):

        for name in files:
            if fnmatch.fnmatch(name, '*.obj_unzip.gz')  :
                adr_list = os.path.join(root, name)
                flist.append(adr_list)

    return flist

folder = filedialog.askdirectory()
files = getfn(folder)

for fn in files:

    size = os.stat(fn).st_size
    name = re.search("(.*)\.dat_unpack\\\(.*?).obj_unzip",fn).group(2)
    with open(fn,'rb') as f:
        dest = open("text/"+name+'.txt', 'wb')
        offset = 0x04

        n = 1
        while size - offset >= 4:
            f.seek(offset)
            if struct.unpack('I', f.read(4))[0] == 0x64:
                f.seek(offset + 0x0E)
                length = struct.unpack('I', f.read(4))[0]
                f.seek(offset + 0x0E +0x04)

                text = f.read(length*2).decode('utf-16')+"\n\n"#实际长度要*2
                if length <100 and re.match(".*「(.*)」",text):

                    dest.write(("## "+str(n)+ " ##\n\n").encode())
                    dest.write(text.encode())
                    offset = (offset + length * 2 - 1) // 0x10 * 0x10 + 0x04
                    n += 1

            offset += 0x10

这里我直接说逻辑,多的注释我就懒得写了。逻辑就是四个四个字节地读文件,如果读出"0x64"就说明可能有文本在下方。然后读出长度,根据长度再把文本读出。然后再判断读出的文本是否真的是文本,判断逻辑是长度短且包含"「"和"」"字符且"」"是最后一个字符。最后保存至.txt文件。


运行程序,最终结果(只是其中一部分)如下。


总结

以上就是今天要讲的内容,本文介绍本人提取psp游戏文本的过程。本人认为在所有步骤中,最困难的要属找文本位置,所花费时间占时间的95%。如果各位有其他见解,请在评论区各抒己见。

RPGViewer图片资源提取工具 作者:Van 说明: 下载RPGViewer之后,不要忘记同时下载RPGViewerSupportFile。解压RPGViewer后,将RPGViewerSupportFile拷贝到解压之后的目录,然后选择“解压到当前文件夹”即可。 有需要的朋友可以做为研究之用 郑重声明 本程序提供的导入导出功能仅供个人学习研究之用,图像之版权属相关公司所有,请勿将提取的图片或导入修改后的图象文件用于其它用途 功能简介 一、浏览 执行文件菜单中的打开。如果你是第一次查看这个游戏,RPGViewer会弹出对话框让你选择游戏的路径,一般选择游戏主程序所在目录即可 之后就可以用浏览菜单或者浏览工具栏查看游戏中的图片了。 如果遇到不支持的图片格式或者读取图片时发生错误,RPGViewer会在状态栏显示相关的出错信息。 二、搜索 可以根据文件名查找图片。支持查找的游戏列表详见附录1 搜索支持模糊查找、区分大小写和正则表达式,正则表达式的具体定义参见附录3 三、导出(支持bmp、jpg、png和mng格式,mng格式的说明参见附录2。另外支持导出为三国群英传的SHP格式) RPGViewer提供三种导出功能: 1、当前帧 导出当前显示的图片 2、当前图片 当图片仅有一帧时,和导出“当前帧”相同。否则将导出该图片的所有帧。 3、所有图片 导出所有图片(!注意:如果图片有多帧,那么此功能会导出所有帧!) 注:有些导出图片的高度是负的,可能在某些图片浏览器中不能正常显示。建议使用Irfanview或PhotoShop进行查看和编辑 全部导出功能会将所有的图片导出到目标文件夹中,文件名依次为1-1.bmp,2-1.bmp... 四、导入(支持bmp、gif、jpg和png格式) 你可以导入bmp或者png(支持透明色和alpha通道)格式的图片(注:“导入”操作只是引入了一个替换的标记,此时未进行实际的替换) 如果想取消对当前图片的替换可以使用“导入”菜单中的“还原” 全部图像都替换完之后,执行“导入”菜单中的“保存”,所作的替换就会生效 五、压缩包操作 可以提取游戏数据包中的所有文件,支持部分游戏的文件替换。支持解包和替换的游戏列表详见附录1 附录1: 浏览和导出支持以下游戏: 大宇:轩辕剑系列(2代、3代、4代以及它们的外传)、轩辕伏魔录,仙剑1(DOS&WIN;版)、新仙剑、仙剑2、仙剑3和仙剑3外传 智冠:金庸群侠传(光盘&硬盘版)、武林群侠传、三国群侠传、天龙八部(部分) 奥汀:三国群英传1~6、幻世录1~2 宇峻:绝代双骄1~3、幻想三国志1、2 弘煜:风色幻想1、SP&2、3、4 光谱:富甲天下3、富甲天下4 汉堂:阿玛迪斯战记、天地劫-神魔至尊传、天地劫序传-幽城幻剑录、天地劫外章-寰神结 新瑞狮:吕布与貂蝉、反三国志、天河传说 目标:秦殇、秦殇前传-复活 金山:新剑侠情缘、月影传说、剑侠情缘2、地雷战、决战朝鲜 KOEI:三国志1~5(头像文件)、三国志6~11、SanInternet、SanBattleField、英杰传系列、 太阁4~5,信长之野望3Win版、信长之野望6(头像文件)、信长之野望7~12、 真三国无双3、 水浒传天命之誓&天导108星、成吉思汗4、王国兴亡录 TGL:神奇传说——远征奥德赛1&2、神奇传说1~3、战国美少女1&2 Falcom:伊苏1、伊苏2(部分图片调色板不对)、英雄传说6(部分)、失落的羽翼、圣界的奇迹、绯苍幻想曲 ego:圣魔大战、新圣魔大战、艾伦希亚战记、红泪、苍月、魔法少年、永远的羁绊、我的美丽天使、秘境传说、乱世奇缘 KEY:Kanon、AIR KID:梦之翼、Never7、Ever17、秋之回忆1~3、秋之回忆-想君 ELF:龙骑士4、同级生2(DOS&Win;版)、下级生 Illusion:欲望的血液4、尾行2、尾行3、BattleRaper2、人工少女2、波动少女1.5、波动少女2 SoftMax:西风狂诗曲 NWC:英雄无敌2、英雄无敌3 EIDOS:盟军敢死队-使命召唤&深入敌后 UBI:英雄无敌5测试版 其它:郑问之三国志 字体:三国志2~5、San9&10;(存为png格式可以保留alpha通道)、MagicWin 导入支持以下游戏: 三1~5头像,三6~10,三11的头像,英杰传系列,大航海4,信长7、8、11、12,成吉思汗4,王国兴亡录 地雷战 三国群英传1~3的PAK文件(仅限于其中的SHP格式)(注:未经严格测试,替换前请一定要备份,以避免不必要的损失) 部分支持信长12和三国志11的bin文件的导入 注: 曹操传的meff不支持导入 查找和解包支持以下游戏: 大宇:CPK(仙剑3和仙剑3外传) 奥汀:PAK(三国群英传1~3、幻世录1、幻世录2)、PCK(三国群英传4&5) 宇峻:*Combat.dat、*Man.dat、*Role.dat(幻想三国志1、2) 弘煜:BMP、FACE、MANBMP、MAPBMP(风色幻想1&SP;)、PAK(风色幻想2)、JBF(风色幻想3&4) 汉堂:DAT(幽城幻剑录、寰神结) TGL:PAC(神奇传说3、远征奥德赛1&2)、PAK(战国美少女2) Falcom:YS(伊苏1)、ED6_DT??.dat(英雄传说6) ego:DAT、TPF(乱世奇缘) KID:DAT ELF:ARC illusion:PP SoftMax:ZMK(西风狂诗曲) NWC:AGG(英雄无敌2)、LOD(英雄无敌3) EIDOS:DIR(盟军敢死队) UBI:PAK(英雄无敌5测试版) 替换支持以下游戏: 奥汀:PAK(三国群英传1~3) 附录2: mng导出格式简介 此格式对应多帧的PNG图片,效果类似于GIF动画,但支持RGB+alpha通道且无损压缩。可以使用IrfanView、XnView查看,GIMP编辑(相关支持软件可以去http://www.libpng.com/pub/mng/mngapps.html查看) MNG IE插件:http://entropymine.com/jason/mng4ie/(可以到http://free.ys168.com/?pigspy下载,里面提供了注册文件reg.dat和卸载文件unreg.dat) 装了该插件之后可以直接用IE打开mng文件 注1、此导出格式仅适用于生成动画(具体包括:轩辕剑的tsw图片,绝代双骄2&3、幻想三国志1&2的战斗和法术图片,金山的ASF.PAK,西风狂诗曲的OBC文件) 注2、如果用这种格式保存帧数太多、图像范围太大的图片(比如全屏幕的法术),可能需要花较多的时间生成,同时生成的图片也可能会比较大) 注3、你可以使用相关工具进一步减少mng的尺寸(比如使用delta-PNG方式压缩) 注4、当提取当前帧时,会自动保存为png格式 附录3: 正则表达式 具体参见http://msdn2.microsoft.com/en-us/library/k3zs4axe(en-us,VS.80).aspx中的“Regular Expression Syntax” 一些正则表达式的例子: 严格匹配face:^face$ 模糊查找face:face 查找mFace???.shp的文件:mFace...\.shp$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值