Bootstrap

植物大战僵尸杂交版自动备份存档

自动备份存档

应我室友请求,花半天时间搓了个自动备份存档的软件,额…之前说的那个字幕文件转换的玩意有点麻烦,还没搞完…
好,来看看这个玩意的原理

原理

玩了几局无尽,我发现在每次大波次结束时,就是打完两波镜头移动的时候,游戏的存档文件会短暂的出现,然后在镜头结束移动后存档文件消失,因此我的想法是用一个循环持续检测文件目录,如果有新文件出现,就保存下来。那么如何获取文件目录呢?

void searchFile()
{
	system("move /y #backup\\newFileList.txt #backup\\oldFileList.txt 1>NUL 2>NUL");

	char cmd[512];
	strcpy_s(cmd, "dir /b \"");
	strcat_s(cmd, strlen(cmd) + strlen(savePath) + 1, savePath);
	strcat_s(cmd, strlen(cmd) + 19, "\" | findstr \"[^.]*");
	strcat_s(cmd, strlen(cmd) + strlen(saveType) + 1, saveType);
	strcat_s(cmd, strlen(cmd) + 6, "$\" >\"");
	strcat_s(cmd, strlen(cmd) + strlen(localPath) + 1, localPath);
	strcat_s(cmd, strlen(cmd) + 26, "\\#backup\\newFileList.txt\"");
	system(cmd);
}

这个是我的解决方法,可以看到,用到了system()函数,实际上就是调用cmd,通过dir命令获取文件列表,再输出重定向到一个文本文件里。这个函数没有传参,是因为里面的变量都是全局变量,小程序懒得搞传参了。
其他的诸如拷贝之类的操作也全是靠system()实现的,学完了Linux才发现小黑框是何等强大。输出重定向也是在Linux里面学的,上面的move用到了,dir也用到了,其中dir还用到了管道,通过管道将文件列表传给findstr,由它筛选出存档文件,再重定向输出到newFileList.txt里,而move将结果重定向到了空,1>NUL的意思是将标准输出弃置,不显示,2>NUL是将标准错误弃置,不显示,两个在一起,就会让这个指令不输出任何信息,保证程序运行时的清爽界面。

完整代码

#include<iostream>
#include<regex>
#include<string>
#include<direct.h>
#include<time.h>
#include<Windows.h>

using namespace std;

void initialize();
void searchFile();
void copyFile(char* fileName);
void renameExistFile(char* fileName, int times);
void removeBlank(char* target);

char savePath[512];
char saveType[64];
int scanTime;
int saveNum;

//当前程序所在路径
char localPath[512];
int main() {
	_getcwd(localPath, 512);

	//初始化
	initialize();
	searchFile();
	searchFile();

	printf("初始化完成,开始运行\n");
	//正式运行
	while (1) {
		int existFlag = 0;
		searchFile();
		char newFileName[256];
		FILE* fpNew;
		errno_t err = fopen_s(&fpNew, "#backup\\newFileList.txt", "r");
		if (err) {
			printf("读取新文件列表失败!\n");
			printf("30秒后自动退出...");
			Sleep(30000);
			return 1;
		}
		char oldFileName[256];
		char invalid[4];
		while (fscanf_s(fpNew, "%[^\r\n]%c", newFileName, 256, invalid, 4) != EOF) {
			FILE* fpOld;
			err = fopen_s(&fpOld, "#backup\\oldFileList.txt", "r");
			if (err) {
				printf("读取旧文件列表失败!\n");
				printf("30秒后自动退出...");
				Sleep(30000);
				return 1;
			}
			while (fscanf_s(fpOld, "%[^\r\n]%c", oldFileName, 256, invalid, 4) != EOF) {
				if (strcmp(newFileName, oldFileName) == 0) {
					//在新文件列表中发现与旧文件列表中出现过的文件,忽略
					existFlag = 1;
					break;
				}
			}
			if (!existFlag) {
				//该文件在旧文件列表中没有,说明是新文件,进行备份
				time_t tms = time(0);
				struct tm ptm;
				localtime_s(&ptm, &tms);
				printf("[%02d:%02d:%02d]正在备份%s\n", ptm.tm_hour, ptm.tm_min, ptm.tm_sec, newFileName);
				renameExistFile(newFileName, 1);
				copyFile(newFileName);
			}
			existFlag = 0;
			fclose(fpOld);
		}
		fclose(fpNew);
		Sleep(scanTime);
	}
	printf("30秒后自动退出...");
	Sleep(30000);
	return 0;
}

void searchFile()
{
	system("move /y #backup\\newFileList.txt #backup\\oldFileList.txt 1>NUL 2>NUL");

	char cmd[512];
	strcpy_s(cmd, "dir /b \"");
	strcat_s(cmd, strlen(cmd) + strlen(savePath) + 1, savePath);
	strcat_s(cmd, strlen(cmd) + 19, "\" | findstr \"[^.]*");
	strcat_s(cmd, strlen(cmd) + strlen(saveType) + 1, saveType);
	strcat_s(cmd, strlen(cmd) + 6, "$\" >\"");
	strcat_s(cmd, strlen(cmd) + strlen(localPath) + 1, localPath);
	strcat_s(cmd, strlen(cmd) + 26, "\\#backup\\newFileList.txt\"");
	system(cmd);
}

void initialize() {
	//检测是否存在备份文件夹
	if (system("cd #backup")) {
		//返回值为1,不存在指定目录
		printf("未检测到备份目录,新建备份目录...\n");
		_mkdir("#backup");
		printf("创建配置文件...\n");
		FILE* fp;
		fopen_s(&fp, "#backup\\config.txt", "w+");
		fprintf_s(fp, "[配置文件]\n\
游戏存档目录=C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\pvzHE\\yourdata\n\
存档文件格式=dat\n\
检测周期(ms)=1000\n\
单个文件最大备份数量=1");
		fclose(fp);
	}
	else {
		if (system("type #backup\\config.txt 1>NUL 2>NUL")) {
			//返回值为1,不存在配置文件
			printf("未检测到备份目录,新建配置文件...\n");
			FILE* fp;
			fopen_s(&fp, "#backup\\config.txt", "w+");
			fprintf_s(fp, "[配置文件]\n\
游戏存档目录=C:\\ProgramData\\PopCap Games\\PlantsVsZombies\\pvzHE\\yourdata\n\
存档文件格式=dat\n\
检测周期(ms)=1000\n\
单个文件最大备份数量=1");
			fclose(fp);
		}
	}

	//读取配置文件
	printf("加载配置文件...\n");
	FILE* fp;
	errno_t err = fopen_s(&fp, "#backup\\config.txt", "r");
	if (err) {
		printf("加载配置文件失败!\n");
		return;
	}
	
	char tmp[512];
	char invalid[4];
	char key[128];
	char value[384];
	string data;
	int pos = 0;
	while (fscanf_s(fp, "%[^\r\n]%c", tmp, 512, invalid, 4) != EOF) {
		data = tmp;
		if ((pos = data.find("=")) != -1) {
			strcpy_s(key, data.substr(0, pos).c_str());
			strcpy_s(value, data.substr(pos + 1, 512).c_str());
			if (strcmp(key, "游戏存档目录") == 0) {
				strcpy_s(savePath, value);
				printf("加载游戏存档目录:%s\n", savePath);
			}
			else if (strcmp(key, "存档文件格式") == 0) {
				strcpy_s(saveType, value);
				printf("加载存档文件格式:%s\n", saveType);
			}
			else if (strcmp(key, "检测周期(ms)") == 0) {
				scanTime = atoi(value);
				printf("加载检测周期:%d ms\n", scanTime);
			}
			else if (strcmp(key, "单个文件最大备份数量") == 0) {
				saveNum = atoi(value);
				printf("加载单个文件最大备份数量:%d\n", saveNum);
			}
		}
	}
	fclose(fp);
	
}

void copyFile(char* fileName) {
	char cmd[512];
	strcpy_s(cmd, "copy \"");
	strcat_s(cmd, strlen(cmd) + strlen(savePath) + 1, savePath);
	strcat_s(cmd, strlen(cmd) + 2, "\\");
	strcat_s(cmd, strlen(cmd) + strlen(fileName) + 1, fileName);
	strcat_s(cmd, strlen(cmd) + 12, "\" \"#backup\\");
	strcat_s(cmd, strlen(cmd) + strlen(fileName) + 1, fileName);
	strcat_s(cmd, strlen(cmd) + 14, "\" 1>NUL 2>NUL");
	system(cmd);
}

void renameExistFile(char* fileName, int times) {
	char cmd[512];
	strcpy_s(cmd, "type \"#backup\\");
	strcat_s(cmd, fileName);
	if (times > 1) {
		strcat_s(cmd, strlen(cmd) + 5, ".bak");
		strcat_s(cmd, strlen(cmd) + strlen(to_string(times).c_str()) + 1, to_string(times).c_str());
	}
	strcat_s(cmd, "\" 1>NUL 2>NUL");
	if (!(system(cmd) || times >= saveNum)) {
		//还有备份文件存在,且当前未达到最大备份数量,继续搜索,递!
		renameExistFile(fileName, times + 1);
	}
	else {
		//不存在备份,或备份数量达到最大,停止搜索,归!
		return;
	}

	strcpy_s(cmd, "move /y \"#backup\\");
	strcat_s(cmd, strlen(cmd) + strlen(fileName) + 1, fileName);
	if (times > 1) {
		//递归次数大于1,表示有超过一个文件已存在
		strcat_s(cmd, strlen(cmd) + 5, ".bak");
		strcat_s(cmd, strlen(cmd) + strlen(to_string(times).c_str()) + 1, to_string(times).c_str());
		strcat_s(cmd, strlen(cmd) + 12, "\" \"#backup\\");
		strcat_s(cmd, strlen(cmd) + strlen(fileName) + 1, fileName);
		strcat_s(cmd, strlen(cmd) + 5, ".bak");
		strcat_s(cmd, strlen(cmd) + strlen(to_string(times + 1).c_str()) + 1, to_string(times + 1).c_str());
		}
	else {
		strcat_s(cmd, strlen(cmd) + 12, "\" \"#backup\\");
		strcat_s(cmd, strlen(cmd) + strlen(fileName) + 1, fileName);
		strcat_s(cmd, strlen(cmd) + 6, ".bak2");
	}
	strcat_s(cmd, "\" 1>NUL 2>NUL");
	system(cmd);
	
}

void removeBlank(char* target) {
	for (int i = 0; i < strlen(target); i++) {
		if (target[i] == ' ') {
			for (int j = i; j < strlen(target); j++)
				target[j] = target[j + 1];
			i--;
		}
	}
}

原代码在此,用的VS写的,有一堆它的独特的、安全的安全函数,所以想要编译的话,就必须用VS跑,我在这里附一个release程序,下载了就能直接用

链接:https://pan.baidu.com/s/18VdGIbgvg3WBXMQkLxOFqA?pwd=2333
提取码:2333

使用方法

首先,下载程序,或者你自已编译出来也行,然后运行以下,可以在任何地方运行,运行完后它会自动创建一个#backup文件夹,然后在里面生成一个config.txt文件,如下:
初次运行效果
#backup文件夹
现在先关闭程序,然后修改配置文件里面的游戏存档目录为你的杂交版游戏存档目录,现在的存档目录应该是这个:

C:\ProgramData\PopCap Games\PlantsVsZombies\pvzHE\yourdata

(额,我觉得好像有点麻烦,就把上面这个目录改成默认目录了,所以对于现在的版本应该不用修改了)
修改好了就可以直接运行了,然后把小黑窗口最小化就好,如果游戏崩了,存档没了,就把#backup里的最新的存档文件拽到上面那个游戏存档目录里就行,可以给这个文件夹创建个快捷方式放在备份程序那里,方便快速操作

然后解释下配置文件里的东西,存档目录说过了,那个存档文件格式是用来筛选文件列表的,文件列表会把包含.[存档文件格式]的文件名留下来,如果以后这游戏改了存档格式,就可以同步更改这里的格式,或者遇到其他存档方式类似的游戏也可以拿来用。
下面的检测周期就是检测的周期,如果不限制的话,虽然我感受不到,但我觉得还是会影响运行效率,就是个优化性能的限制,一秒绝对来得及备份的,你也可以根据需求更改。
最后那个单个文件最大备份数量,这个程序是支持同名文件多次备份的,旧的文件会以[文件名].bakxxx的格式命名,如下:
重名文件多次备份
数字越小文件越新,然后这个最大备份数量指的就是包含bak文件在内的所有同名备份文件的数量,如果要用最新的存档,在这里就是直接用第一个.dat结尾的就好,如果想用比较旧点的存档,就把其他的文件的.bakxxx后缀删了就行,比如我打无尽,打到某一波发现打崩了,但是它又没有彻底崩,就可以回到前几个存档逆天改命

;