首页 > 其他分享 >[PJADV] 文件系统分析

[PJADV] 文件系统分析

时间:2024-01-28 21:58:50浏览次数:30  
标签:分析 Map 封包 PJADV 文件系统 Node ArchiveManager DirectoryManager PAJ

[PJADV] 文件系统分析

前情

通过上一节[PJADV] 封包结构分析 我们已经分析了 PAJ::VFS::Open 函数的作用。

简单来说这个函数接受两个参数 [文件名路径] [封包路径]

函数过程:先尝试从游戏目录下读取文件,如果没有就打开封包,在封包中寻找目标文件,读取成功返回PAJ_VFS对象指针,PAJ_VFS对象记录了目标文件的句柄,大小,偏移,后续可以利用这些信息来读取文件数据

PAJ_VFS比较怪异的是没用this传递对象指针,不过也没什么太大差别,它的成员函数比较少,或是说我目前看到的就那么几个,无非就获取大小,读取数据,等待异步读取完成之类的。

当然PAJ_VFS相关的操作函数,只是一个处理底层系统API和封包的类,上层的文件系统对PAJ_VFS又进行了一次封装,我们可以把PAJ_VFS看成是一种自己封装了一遍的fopen,只不过这个fopen的文件来源又挂了一个自定义格式封包,或者说叫 PAJ_VFSPAJ_FStream更加合适?哎,不管怎么说吧,我也懒得改名了

struct PAJ_VFS
{
  uint32_t uiType;
  HANDLE hFile;
  uint32_t uiFOA;
  uint32_t uiFileSize;
  uint32_t uiFileSize_;
  uint32_t uiReadSize;
};

入手点

首先调试PAJ_VFS相关成员函数,因为这个是游戏读取封包的接口,就和前面说的相当于fopen,所以基本上打开封包都要经过PAJ::VFS::Open
不断调试可以发现调用PAJ::VFS::Open最密集最频繁的是PAJ::OpenFile,也就是说封包文件读取是调用PAJ::OpenFile

由交叉引用的结果也可知,调用 PAJ::VFS::Open 来打开文件的最多的莫过于PAJ::OpenFile,那么这个函数又长什么样呢?

其实它的参数和PAJ::VFS::Open一样,连返回值都是一样的,只不过多套了两个玩意

简单解释一下这个函数,首先还是传递文件路径和封包路径

  • 先从ArchiveManager里查找目标文件所有的封包路径,有的话PAJ::ArchiveManager::FindPath返回true,并把封包的路径写入到 pack_full_path 然后继续调用 PAJ::VFS::Open读取,这看起来是否略显弱智?确实有点弱智,也搞不懂立本人怎么想的,但这个主要是解决更新封包问题,就是相同的文件名,但在不同封包里,可以通过这个来返回最新的那个文件所在的封包路径

  • 如果前面失败就从PAJ::VFS::Open读取

  • 如果前面都失败,就从DirectoryManager里查找目标文件所在的路径,行为和ArchiveManager类似,但写入的是文件路径

其实讲到这,就会发现,这个引擎文件读取有点脑抽的感觉,PAJ::ArchiveManager::FindPath可以返回对应封包的路径,但却没存储相关的索引信息,比如偏移大小之类的,又要进PAJ::VFS::Open再次遍历文件

DirectoryManager 构造

因为DirectoryManager对象是全局的,很容易就可以追踪到其使用的位置,可以发现,其在main函数中构造

首先在main函数里会进行DirectoryManager初始化,DirectoryManager主要由一个set一个map组成,一个保存文件夹,一个保存文件名/路径键值对,由于map和set类型,我这里为了方便套入结构,就懒得区分了。

struct PAJ_DirectoryManager
{
  PAJ_DirectoryManager_Folder_Map *pFolderMap;
  PAJ_DirectoryManager_Path_Map *pPathMap;
};

struct PAJ_DirectoryManager_Folder_Map
{
  PAJ_DirectoryManager_Folder_Map_Node_Pair *pNode;
  PAJ_DirectoryManager_Folder_Map_Node_Pair *pHeader;
  uint32_t uiSize;
};

struct __declspec(align(4)) PAJ_DirectoryManager_Folder_Map_Node_Pair
{
  PAJ_DirectoryManager_Folder_Map_Node_Pair *_Left;
  PAJ_DirectoryManager_Folder_Map_Node_Pair *_Parent;
  PAJ_DirectoryManager_Folder_Map_Node_Pair *_Right;
  PAJ_STD_Str Value;
  char _Color;
  char _Isnil;
};

struct PAJ_DirectoryManager_Folder_Map_Ite
{
  PAJ_DirectoryManager_Folder_Map *pMap;
  PAJ_DirectoryManager_Folder_Map_Node_Pair *pPairs;
};

struct PAJ_DirectoryManager_Path_Map
{
  PAJ_DirectoryManager_Path_Map_Node_Pair *pNode;
  PAJ_DirectoryManager_Path_Map_Node_Pair *pHeader;
  uint32_t uiSize;
};

struct __declspec(align(4)) PAJ_DirectoryManager_Path_Map_Node_Pair
{
  PAJ_DirectoryManager_Path_Map_Node_Pair *_Left;
  PAJ_DirectoryManager_Path_Map_Node_Pair *_Parent;
  PAJ_DirectoryManager_Path_Map_Node_Pair *_Right;
  PAJ_STD_Str msFileName;
  PAJ_STD_Str msFilePath;
  char _Color;
  char _Isnil;
};

struct PAJ_ArchiveManager_Map_Ite
{
  PAJ_ArchiveManager_Map *pMap;
  PAJ_ArchiveManager_Map_Node_Pair *pPairs;
};

PAJ::DirectoryManager::Ctor中初始化,其内部先调用PAJ::DirectoryManager::Dtor()先清除,之后构造头节点

路径的map长这样

path_map["BGM_FC27DQ_intro.ogg"] = "C:\Lillian\ティンクル☆くるせいだーすPSS\SE\さっちん_新着メールが届いています.wav";

文件夹的map长这样,应该就只是个set

folder_map["SE"];
folder_map["BGM"];

添加一个文件夹,先把文件夹压入folder_set里,然后开始循环遍历文件夹,把所有的文件路径和文件名加到path_map

ArchiveManager 构造

DirectoryManager 同理 ArchiveManager 保存了一个全局指针,通过这个我们可以追踪到使用该对象的地方

ArchiveManager 通过 archive.ini 里的字段来获取封包文件名列表,还记得archive.ini里写的一堆封包文件名吗?

简单来说ArchiveManager里有个vector存储封包名,一个map存储[文件名/对应封包在vector的索引]的键值对

struct PAJ_ArchiveManager_Map_Node_Pair
{
  PAJ_ArchiveManager_Map_Node_Pair *_Left;
  PAJ_ArchiveManager_Map_Node_Pair *_Parent;
  PAJ_ArchiveManager_Map_Node_Pair *_Right;
  PAJ_STD_Str msFileName;
  uint32_t uiPackSeq;
  uint8_t _Color;
  uint8_t ucIsNull;
};

struct PAJ_ArchiveManager_Map
{
  PAJ_ArchiveManager_Map_Node_Pair *pNode;
  PAJ_ArchiveManager_Map_Node_Pair *pHeader;
  uint32_t uiSize;
};

struct PAJ_ArchiveManager_Map_Ite
{
  PAJ_ArchiveManager_Map *pMap;
  PAJ_ArchiveManager_Map_Node_Pair *pPairs;
};

struct PAJ_ArchiveManager_Vector
{
  uint32_t uiUn0;
  PAJ_STD_Str *pBeg;
  PAJ_STD_Str *pEnd;
  uint32_t *pReserve;
};

struct PAJ_ArchiveManager
{
  uint32_t *pVtable;
  PAJ_ArchiveManager_Vector Vector;
  PAJ_ArchiveManager_Map Map;
};

由于涉及到ini文件,还需要分析ini文件解析,这个引擎的ini文件解析结果是放在一个list里的

它解析的ini文件有个固定的key值,比如解析archive.ini目标key值是file,这些值有五十个,引擎的图片加载也用到了ini文件

解析结果对象结构如下

struct PAJ_INI_List
{
  PAJ_INI_List_Node *pNext;
  PAJ_INI_List_Node *pPrev;
  uint32_t uiSize;
};

struct PAJ_INI_List_Node
{
  PAJ_INI_List_Node *pNext;
  PAJ_INI_List_Node *pPrev;
  PAJ_STD_Str Value;
};

通过 PAJ::ArchiveManager::Init()来初始化

ini 解析 获取file

加载封包其实和PAJ::VFS::Open很像,也就是把封包的里面的文件名一个个加到map

DirectoryManager查找

PAJ::OpenFile 里找到这个函数,第一个参数是输入的文件路径,第二参数是写入该文件对应的路径的buffer,如果没有就不写入返回false

ArchiveManager查找

PAJ::OpenFile 里找到这个函数,第一个参数是输入的文件路径,第二参数是写入该文件对应的封包路径的buffer,如果没有就不写入返回false

总结

这个引擎的封包读取逻辑,我只想说,真是啥X。

不过我认为这个引擎的文件系统,还是挺不错的学习资料。

从封包上来看,结构简单,但基本上就是一个最小封包,或者是最普遍的封包结构。

从封包接口设计上来看,读取逻辑也比较简单,没有采用stl,甚至没有使用C库函数,连内存分配都直接调用系统API,整体代码清晰易读。

从封包接口对象内存结构来看,设计也是比较简单的,成员变量、成员函数较少,逻辑结构清晰。

从整体的文件系统上来说,在简单的封包接口上,又加入了 std::map std::set等缓存/更新机制,但整体不是很复杂,由此也比较适合学习真实环境下stl相关库和内存结构,还有解析ini,和list的使用,如果能仔细分析,那也能练习到了不少内容。

总得来说,这个引擎的文件系统从最底层到最上层,设计由简单到复杂,比较适合入门分析。

透过本文的分析,也是想让大家认识到逆向文件系统的意义,传统上来说,只关注封包的文件结构,然后想方设法写一个回封程序,其实是不必要的,因为你只要分析了引擎的文件系统,就可以知道其实大部分引擎都有免封包直接读取的设计,又或者,至少你可以找到关键点或者说最好的位置来hook直接读取文件,而不是尝试在一个调试过程中随机得到的位置,用很奇葩的方法来达到免封包的目的,实际上一般情况下你要是完整分析了文件系统,甚至都不需要写任何一行代码,就可以达到一个patch替换文件或者免封包替换文件的目的。

当然像这个引擎的话,读取逻辑太简单了,可能感知不强,但如果你至今都还未逆向过任何一个引擎的文件系统又或者说准备了解准备实操,那这个引擎确实是不错的选择。

标签:分析,Map,封包,PJADV,文件系统,Node,ArchiveManager,DirectoryManager,PAJ
From: https://www.cnblogs.com/Dir-A/p/17993476

相关文章

  • [PJADV] 封包结构分析
    [PJADV]封包结构分析目标游戏:ティンクル☆くるせいだーすPSSv.1.19这篇就详细写一下,后面就全当读者有相关背景知识,因为这样写实在很累(假设结构先正常安装游戏,并打上更新补丁(如果太乱可以先不打更新补丁,视情况而定)(是不是可以观察一下打补丁前后变化?补丁本身?)先浏览一遍游戏......
  • Spring整合jasypt原理分析
    前言在我们系统中,有很多敏感数据,如MySQL及Redis的账号密码信息等,jasypt可以帮我们加密这些信息,使系统更加的安全。使用添加maven依赖<dependency><groupId>com.github.ulisesbocchio</groupId><artifactId>jasypt-spring-boot-starter</artifactId><version>2......
  • Python Seaborn 基本数据排名分析
    ​ Python中使用Seaborn进行基本的数据排名分析通常涉及到可视化数据的分布和排名。Seaborn是一个基于Matplotlib的数据可视化库,提供了丰富的图表类型,使得数据分析更加直观。可以对数据进行初步的排名分析,了解数据的基本分布情况,从而为更深入的数据分析打下基础。1、条......
  • IBM java的分析工具(ga和ha)学习和整理
    IBMjava的分析工具(ga和ha)学习和整理背景前几天学习了整理了jca工具今天继续学习一下ga工具ga工具主要是分析gclog相关.可以很直观的进行gclog的分析和展示.除了mat之外还有一个比较轻量级的内存dump分析工具ha.想着一起学习和分析一下.ga工具的相关学习下载:https......
  • 记一次 .NET某工控自动化系统 崩溃分析
    一:背景1.讲故事前些天微信上有位朋友找到我,说他的程序偶发崩溃,分析了个把星期也没找到问题,耗费了不少人力物力,让我能不能帮他看一下,给我申请了经费,哈哈,遇到这样的朋友就是爽快,刚好周二晚上给调试训练营的朋友分享GC标记阶段相关知识,而这个dump所展示的问题是对这块知识的一个很......
  • 通达信【寻妖记】幅图选股公式 大概率捉妖 简洁浓缩 分析股票的强度指标 源码文件分享
    通达信【寻妖记】幅图选股公式简洁浓缩分析股票的强度指标源码文件分享原公式完全加密,时间限时,股海网解密源码文件分享,已经解除时间限制,可以永久使用,并新增选股公式一个本指标叠加了资金流动、筹码流动、股票情绪三个元素,是一个综合性的评价指标,可用于选股、操盘。这个指标......
  • 三级计算机网络大题60分——来自B站“吃饭不留名”(综合题4:sniffer抓包分析 10分)
    https://www.bilibili.com/video/BV1hE411x7RT?p=6&vd_source=2bddda168481f778f8f92561c7e55574方法技巧考点1考点2考点3考点4考点5考点6考点7考点8考点9考点9考点10考点11考点12考点13考点14考点15......
  • 【scikit-learn基础】--『回归模型评估』之准确率分析
    分类模型的评估和回归模型的评估侧重点不一样,回归模型一般针对连续型的数据,而分类模型一般针对的是离散的数据。所以,评估分类模型时,评估指标与回归模型也很不一样,比如,分类模型的评估指标通常包括准确率、精确率、召回率和F1分数等等。而回归模型的评估指标通常包括均方误差(MSE)、......
  • 通达信分析持股区间主图指标公式源码副图
    出水牛股:=92212129;买线:=Ema(CLOSE,2);卖线:=EMA(SLOPE(CLOSE,21)*20+CLOSE,42);突破:=REF(EMA(CLOSE,9),1);A1X:=(EMA(CLOSE,9)-突破)/突破*100;多:=IF(A1X>=0AND买线>=卖线,REF(EMA(CLOSE,10),BArslAst(crOSS(A1X,0))+1),DRAWNULL);空:=IF(A1X<0AND买线<卖线,REF(EM......
  • 三级计算机网络大题60分——来自B站“吃饭不留名”(综合题3:DHCP报文分析 10分)
    https://www.bilibili.com/video/BV1hE411x7RT?p=5&vd_source=2bddda168481f778f8f92561c7e55574考点1考点2(和考点3一起考察)考点3考点4知识总结真题演练1(考点1)真题演练2(考点2和考点3)真题演练3(考点2和考点3)真题演练4(考点4)......