《植物大战僵尸》游戏资源打包文件“main.pak”解密解包器
向阳叶_B站
编辑于 2022年01月13日 02:00
收录于文集
共10篇

/*

 *《植物大战僵尸》游戏资源打包文件“main.pak”解密解包器

 *

 *作者:向阳叶(QQ:914286415)

 *编程语言:纯C语言

 *最后修订日期:2022.1.13

 *

 *Copyright (c) 2022 向阳叶. All rights reserved.

 *

 *【使用方法】

 *1)创建一空文件夹。

 *2)将编译后的本程序放入,并将游戏文件夹内的“main.pak”复制一份到本文件夹中(此时该文件夹中仅有这两个文件)。

 *3)运行本程序,即可解包该打包文件到本文件夹中。

 *4)删除该文件夹中的本程序以及“main.pak”两文件,此时本文件夹中全是解包后的文件。

 *【注意事项】

 *1)请一定要保证“main.pak”是有效的文件,否则我也不知道会发生什么。

 *2)正常运行结束时会看到“===Success!===”字样,如果运行时发生ERROR,请将本文件夹中本程序生成的文件全部删除,再尝试重新运行本程序。

*/

代码块
clike
自动换行
复制代码
#include <stdio.h>
#include <stdlib.h>
#include <io.h>	//_access()
#include <direct.h>	//_mkdir()

typedef unsigned char byte_t;	//“字节”数据类型

//链表节点,用于存储文件信息
struct FileInfoNode;
typedef struct FileInfoNode FileInfoNode;
struct FileInfoNode{
	char * pPathname;	//文件名(含路径)指针
	unsigned int fileSize;	//文件长度(需要保证编译器上的sizeof (unsigned int) == 4)
	FileInfoNode * pNext;	//后节点的指针
};

int main(void){
	//程序运行开始
	printf("===Welcome!===\n");

	//尝试打开“main.pak”
	FILE * fpMainPak = fopen("main.pak", "rb");
	if (NULL == fpMainPak){
		fprintf(stderr, "ERROR : Failed to open file \"main.pak\".\n");
		exit(EXIT_FAILURE);
	}

	//成功打开,获取文件长度
	fseek(fpMainPak, 0, SEEK_END);
	long sizeMainPak = ftell(fpMainPak);
	rewind(fpMainPak);

	//文件长度必须为正整数
	if ( !(sizeMainPak > 0) ){
		fprintf(stderr, "ERROR : Size of file \"main.pak\" must be greater than zero.\n");
		exit(EXIT_FAILURE);
	}

	//将“main.pak”读入内存
	printf("Loading \"main.pak\"...");
	byte_t * pMainPak = (byte_t *)malloc(sizeMainPak);
	if (NULL == pMainPak){
		fprintf(stderr, "ERROR : Failed to allocate memory for the memory image of file \"main.pak\".\n");
		exit(EXIT_FAILURE);
	}
	if (fread(pMainPak, sizeof (byte_t), sizeMainPak, fpMainPak) != sizeMainPak){
		fprintf(stderr, "ERROR : Failed to load file \"main.pak\" to memory.\n");
		exit(EXIT_FAILURE);
	}
	printf("[Finished!]\n");

	//已将“main.pak”读入内存,关闭该文件
	fclose(fpMainPak);
	fpMainPak = NULL;

	//从这里开始,使用pMainPak(首字节指针)和sizeMainPak(字节数)操作

	//打包文件逐字节使用密钥0xF7通过异或法做了加密,需要先进行解密
	printf("Decrypting data...");
	for (int i = 0; i < sizeMainPak; i += 1)
		pMainPak[i] ^= (byte_t)0xF7;
	printf("[Finished!]\n");

	//创建一条链表,用于存储打包文件中的文件信息
	FileInfoNode headNode;
	headNode.pNext = NULL;
	//创建链表节点的指针(初始时指向头节点),用于操作该链表
	FileInfoNode * pNode = &headNode;

	//创建字节索引,表示当前正在操作的字节号(初始时操作首字节)
	unsigned int byteIndex = 0;

	//读取打包文件中的文件信息(循环一次读取一个文件)
	printf("Scanning files...");
	while (1){
		//跳过前8个字节(这8个字节的作用尚不明确),指向第9个字节
		byteIndex += 8;
		//检测第9个字节是否为0,不是则说明读取结束
		if (0 != pMainPak[byteIndex]){
			//读取结束时,让索引指向其后一个字节,此处是数据段的开始
			byteIndex += 1;
			break;
		}

		//没有break,说明有新文件,为其创建一个新的链表节点
		pNode->pNext = (FileInfoNode *)malloc(sizeof (FileInfoNode));
		pNode = pNode->pNext;
		if (NULL == pNode){
			fprintf(stderr, "ERROR : Failed to allocate memory for new FileInfoNode.\n");
			exit(EXIT_FAILURE);
		}
		pNode->pNext = NULL;

		//索引移动到第10个字节,该字节表示文件名(含路径)的字符数
		byteIndex += 1;
		//为文件名分配内存(为'\0'额外分配1字节内存)
		pNode->pPathname = (char *)malloc(pMainPak[byteIndex] + 1);
		if (NULL == pNode->pPathname){
			fprintf(stderr, "ERROR : Failed to allocate memory for Pathname linked to FileInfoNode.\n");
			exit(EXIT_FAILURE);
		}
		//字符串'\0'封尾
		(pNode->pPathname)[pMainPak[byteIndex]] = '\0';
		//复制文件名
		for (int i = 0; i < pMainPak[byteIndex]; i += 1)
			(pNode->pPathname)[i] = pMainPak[byteIndex + 1 + i];

		//索引移动到文件名后的第1字节,该处的4个字节表示文件长度
		byteIndex += 1 + pMainPak[byteIndex];
		//读取文件长度
		pNode->fileSize = *((unsigned int *)(pMainPak + byteIndex));

		//索引移动到文件长度后的第1字节,此处是下一段数据的起点
		byteIndex += 4;
	}
	printf("[Finished!]\n");

	//此时,所有文件信息均已被读取到链表中

	//遍历链表,计算所有文件的长度和,并比较是否与数据段的真实长度一致(这样在导出文件时就不需要担心数组越界)
	{
		unsigned int totalSize = 0;
		for (pNode = headNode.pNext; pNode != NULL; pNode = pNode->pNext)
			totalSize += pNode->fileSize;
		if (totalSize != sizeMainPak - byteIndex){
			fprintf(stderr, "ERROR : Size of file \"main.pak\" is unexpected.\n");
			exit(EXIT_FAILURE);
		}
	}

	//所有准备就绪,开始导出文件!(每次循环导出一个)
	printf("Releasing files(Please wait patiently)...");
	for (pNode = headNode.pNext; pNode != NULL; pNode = pNode->pNext){
		//先确认路径有效性(也就是确认文件所在的文件夹已经创建好了,没有创建好则创建)
		{
			//指向反斜杠的指针(初始时指向第一个字符)
			char * pSlash = pNode->pPathname;
			while (1){
				//找到下一个'\\'或者'\0'
				while ( !(*pSlash == '\\' || *pSlash == '\0') )
					pSlash += 1;
				//如果是'\0',则路径有效性已得到确认,结束循环
				if ('\0' == *pSlash)
					break;

				//此时是'\\',需要进行路径有效性确认

				//先将'\\'换为'\0'
				*pSlash = '\0';
				//确认有效性
				if (-1 == _access(pNode->pPathname, F_OK))
					_mkdir(pNode->pPathname);
				//确认完毕,还原
				*pSlash = '\\';

				//因为本轮检测已经完成,所以跳过本次的'\\',进行下一轮
				pSlash += 1;
			}
		}

		//打开待写入的文件
		FILE * fpRelease = fopen(pNode->pPathname, "wb");
		if (NULL == fpRelease){
			fprintf(stderr, "ERROR : Failed to open file to be written.\n");
			exit(EXIT_FAILURE);
		}

		//打开成功,开始写入
		if (fwrite(pMainPak + byteIndex, sizeof (byte_t), pNode->fileSize, fpRelease) != pNode->fileSize){
			fprintf(stderr, "ERROR : An error occurred while writting.\n");
			exit(EXIT_FAILURE);
		}

		//写入结束,关闭文件
		fclose(fpRelease);
		fpRelease = NULL;

		//索引后移到下一个文件的首字节
		byteIndex += pNode->fileSize;
	}
	printf("[Finished!]\n");

	//链表使用结束,释放整条链表
	pNode = headNode.pNext;
	headNode.pNext = NULL;
	while (pNode != NULL){
		FileInfoNode * pNextNode = pNode->pNext;
		free(pNode->pPathname);
		free(pNode);
		pNode = pNextNode;
	}

	//释放“main.pak”的memory image
	free(pMainPak);
	pMainPak = NULL;

	//程序成功结束
	printf("===Success!===\n");
	return 0;
}
复制成功