10. BelleIsle Map目录说明 & dgp格式解析

关于地图目录:

在客户端安装的原始目录中,有14个目录,每个目录中大概如下的.dat文件:

这些文件也叫.dat,但是和前面的.dat文件不是一回事。 其实就是标准的lt地图文件。 我们可以用lithtech demo工程或者用CrossFire Demo等网上流传项目替换里面的第一个地图文件为这里面的dat文件进行加载。可以看出来大部分能加载成功。 但这地图大多只有网格等信息,没有贴图等材料,这些材料在前面提取FPSF 和 dat文件时候解压出来了很多东西,目录就是在Map目录下。 解压完成后的目录如下。多了一些文件夹和文件出来。

其中 object目录中主要是放了一些地图上物品的模型和材质。

如一些花草树木等。

Texture目录中 主要防止的是各种材质/皮,或者说地图地面和天空 河流 建筑等地方用到的各种瓷砖。 全是dtx格式。

Map目录 随便点开一个 \Map\World\001 查看。

发现原来的基础上多解压出来了两种类型文件 ,csv可以打开查看:

都只有一排数字,我估计是一些地图初始化天气或者传送门位置等配置。

剩下这种dgp格式

又是一种自研格式。

因为前面的客户端没法进入所以整个解析过程中没有碰到过这个文件的解析流程。

只能盲猜了,还好根据ue查看,下面很多dxg字样,而且是多个,说明这里面存了多份没有加密的dxg,那么我们可以理解为是一个dxg的打包文件, 为什么要用dxg打包呢, 是因为地图上用到很多小模型文件,很多装饰类的小模型,文件太小数量又多,不如打成一个文件,一般这样的情况我们可能用个zip gzip等,但是游戏里面早期都是自定义一个打包,然后自己解析。

我们既然前面知道了dxg的格式,那么我手动从ue里面找到一段,从dxg的开头数字节,到一个dxg完整结束拷贝出来,做一个新的dxg,然后用dxg解析工具解析为lta等查看是否正常,经过验证,果然是可以正常解析出来一些模型的,比如一个酒桶,一个马车等。

那么我找在这些dgp中找到最小的文件进行分析,看剩下的是什么, 绝对包含了文件个数,目录名字,长度等信息。把dxg的大小等信息转成16进制和这些字节对比,这样逐步分析出来了dgp的头,和段与段的中间那些字节部分的意思。

所以dgp是一种打包格式,类似zip,只是没有压缩仅仅打包,然后就是写个程序进行解压。

c++ 复制代码
// DGPExtrator.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <string>
#include <fstream>
#include <io.h>
#include<iostream>
#include <assert.h>
#include <direct.h>
#include<vector>
#include <sstream>
#include <iomanip> 
#include <string.h>

using namespace std;

static int createDirectory(string path)
{
	int len = path.length();
	char tmpDirPath[256] = { 0 };

	for (int i = 0; i < len; i++)
	{
		tmpDirPath[i] = path[i];
		if (tmpDirPath[i] == '\\' || tmpDirPath[i] == '/')
		{
			if (_access(tmpDirPath, 0) == -1)
			{
				int ret = _mkdir(tmpDirPath);
				if (ret == -1) return ret;
			}
		}
	}
	return 0;
}

void findAllFile(const char * path, vector<string> &files ,const char * format)
{
	char newpath[200];
	strcpy(newpath, path);
	strcat(newpath, "\\*.*");    // 在目录后面加上"\\*.*"进行第一次搜索
	int handle;
	_finddata_t findData;
	handle = _findfirst(newpath, &findData);
	if (handle == -1)        // 检查是否成功
		return;
	while (_findnext(handle, &findData) == 0){
		if (findData.attrib & _A_SUBDIR){
			if (strcmp(findData.name, ".") == 0 || strcmp(findData.name, "..") == 0)
				continue;
			strcpy(newpath, path);
			strcat(newpath, "\\");
			strcat(newpath, findData.name);
			findAllFile(newpath, files, format);
		}
		else{
			if(strstr( findData.name,format)){     //判断是不是txt文件
				cout << findData.name << "\t" << path <<"\t"<< findData.size << " bytes.\n";
				string fpath(path);
				fpath.append("\\");
				fpath.append(findData.name);
				files.push_back(fpath);
			}
		}
	}
	_findclose(handle);    // 关闭搜索句柄
}

int _tmain(int argc, _TCHAR* argv[])
{
	//string dgpPath = "E:\\FPSFExtractor\\testdata\\decrypt\\Map\\World\\001\\w_001_01_001_hitcl.dgp";

	string filePath = "E:\\FPSFExtractor\\testdata\\decrypt\\Map\\World"; 
	vector<string> files;
	findAllFile(filePath.c_str(), files, ".dgp");
	int size = files.size(); 
	for (int i = 0;i<size;i++)  
	{
		string dgpPath = files[i];
		ifstream m_Stream;
		m_Stream.open((char*)dgpPath.c_str(), ios::in | ios:: binary);

		m_Stream.seekg(32,ios::cur); 
		int flag1;
		m_Stream.read((char*)&flag1,4); // 0x127F xx, xx 有 1,5,6 三个值
		int version1 = flag1&0xff;
		int dxgCount;  //dxg的个数
		m_Stream.read((char*)&dxgCount,4);
		if(version1 == 1) // 偏移一个字节是第一个dxg开始
		{  
			m_Stream.seekg(1,ios::cur); 
		} 
		else if (version1 == 5) //偏移1个 4096 [0x1000]
		{
			m_Stream.seekg(0x1000 ,ios::cur); 
		}
		else if (version1 == 6) //偏移8个 4096 [0x8000]
		{
			m_Stream.seekg(0x8000 ,ios::cur); 
		}
		else
		{
			assert(0);
		}
		for(int i=0;i<dxgCount;i++) {
			cout<<"dxg start pos: "<<  hex << m_Stream.tellg() << endl;

			char index[10]; 
			int length = sprintf(index, "%08d", i); 

			int curPos = (int)m_Stream.tellg(); //记住当前位置, 然后去读长度,计算总长度

			int dxgFlag;
			m_Stream.read((char*)&dxgFlag,4); //判断下头是不是DXG
			if(dxgFlag!=0x20475844){
				assert(0);
			}
			m_Stream.seekg(12 ,ios::cur);  //dxg的长度在12个字节处
			int dxgOffset;
			m_Stream.read((char*)&dxgOffset,4);
			int dxgFileSize = dxgOffset + 0x14; //头占用了20个字节
			m_Stream.seekg(curPos, ios::beg);   //开始拷贝整个dxg

			int pos = dgpPath.find_last_of('\\');
			std::string path(dgpPath.substr(0, pos));
			std::string allname(dgpPath.substr(pos + 1));
			pos = allname.find_last_of('.');
			std::string name(allname.substr(0, pos));
			string saveFile = path + "\\" + name + "\\" + index + ".dxg";

			char * buffer = new char[dxgFileSize];
			m_Stream.read(buffer,dxgFileSize);

			createDirectory(saveFile); //如果子目录不存在创建子目录
			fstream file_output;    
			file_output.open(saveFile, fstream::out | fstream::binary);
			file_output.write(buffer , dxgFileSize);
			delete [] buffer;

			file_output.close();
			cout<<"dxg end pos: "<<  hex << m_Stream.tellg() << endl;
		}
		cout<<"end pos: "<<  hex << m_Stream.tellg() << endl;
		m_Stream.close();
	}
	system("PAUSE");
	return 0;
}

解压后:

每个dgp可能解压出来数千个dxg模型。这些模型可能在地图中引用,可能通过网络触发或者根据时间 天气等变化时候会被用到。 其中每个模型没有自己的名字,为什么用index和8位的长度,是在解析 地图 dat文件中看到有相关目录和这样的名字的引用。 所以分析应该是这样去按照顺序命名的。

至此其实整个游戏的资源全部解析成功,再没有无法识别的文件格式, 如果用lithtech 引擎去复刻的话是可以复刻出来了。

相关推荐
Thomas游戏开发3 天前
Unity3D 图形渲染(Graphics & Rendering)详解
前端·unity3d·游戏开发
Thomas游戏开发3 天前
Unity3D 光栅化 vs 光线追踪:技术详解
前端框架·unity3d·游戏开发
Thomas游戏开发4 天前
Unity3D 多线程与协程优化详解
前端框架·unity3d·游戏开发
Thomas_YXQ8 天前
Unity3D Cinemachine 高级应用详解
数码相机·unity·面试·职场和发展·unity3d·游戏开发
Uzuki9 天前
Vulkan环境配置 | vscode+msvc 解决方案
vscode·游戏开发·图形学·c/c++
非衣居士9 天前
游戏编程模式(28种编程模式)
数据结构·游戏开发
Kapaseker14 天前
Bevy ECS
rust·游戏开发
Thomas游戏开发18 天前
Unity3D 使用 ILRuntime 时的性能问题详解
前端·unity3d·游戏开发
Thomas游戏开发1 个月前
Unity3D游戏排行榜制作与优化技术详解
前端框架·unity3d·游戏开发
山东布谷科技官方1 个月前
小游戏源码开发之可跨app软件对接是如何设计和开发的
游戏开发·游戏源码·小游戏开发·小游戏源码·直播间小游戏·语音房小游戏