RGSSAD解密程序(基于RgssadUnpacker改进以支持RGSS3A)
2014-01-23 11:38
330 查看
近来玩小游戏需要解密Rgssad,发现网上已有人公开了Rgssad/Rgss2a的解密代码(RgssadUnpacker):
http://blog.csdn.net/rgss2ad/archive/2011/02/16/6187475.aspx
但我遇到的是Rgss3a,随手google一下可以看到如下算法描述(原出处不明):
于是结合前人的代码改了一版RgssadUnpacker,从中也发现一些问题,在此对上述描述做一下补足:
1.magickey为四字节长,文件名为不定长,需要将文件名4字节为一组,与magickey做异或运算才能得到正确的结果
2.最后一个文件的文件头的偏移量为0,文件头中其他数据(12个字节)都没有意义,不要去读而直接结束解密即可
最后附上代码(RgssadUnpacker.c):
记得保存为带BOM的UTF-8文本。
P.S.:新增了对VC的支持,注意:VC6靠边站!VS2003没测试过但可能可以,VS2005及以上都应该没问题
会用命令行的用命令行,不会用的新建工程,关掉预编译头,强制使用C++编译即可。
http://blog.csdn.net/rgss2ad/archive/2011/02/16/6187475.aspx
但我遇到的是Rgss3a,随手google一下可以看到如下算法描述(原出处不明):
rmvx ace的加密包相对于XP加密并没有太大改进,无非是magickey不再固定,并且一共有2个magickey. rgss3a格式的文件,前8字节为字符串["R""G""S""S""A""D",0x00,0x03],用于判断是否为标准加密包. 紧接着4个字节,为此加密包的基础key. 以变量读入之后,key*9+3可以得到结构信息magickey.这是重点. 接着开始就是文件信息,文件头占16字节,每4字节为一个信息 将这些数据全部与magickey异或之后可以得到解密的文件信息. 第一个四字节为本段数据的偏移量。 第二个为数据长度。 第三个为本数据magickey, 第四个为此文件名长度. 接下来的若干字节长度为上面第四个四字节取到的长度,为文件名 与magickey异或之后可以得到解密的文件名. 再紧接着16字节为下一个文件的文件头,如此循环,直到文件头储存的偏移量为0. 数据部分,逐四字节与本数据magickey异或运算,每异或一次,magickey=magickey*7+3 直到文件末尾.
于是结合前人的代码改了一版RgssadUnpacker,从中也发现一些问题,在此对上述描述做一下补足:
1.magickey为四字节长,文件名为不定长,需要将文件名4字节为一组,与magickey做异或运算才能得到正确的结果
2.最后一个文件的文件头的偏移量为0,文件头中其他数据(12个字节)都没有意义,不要去读而直接结束解密即可
最后附上代码(RgssadUnpacker.c):
/******************************************************************************* *编译方法: gcc -std=c99 -O2 -s -o RgssadUnpacker RgssadUnpacker.c * cl /TP /O2 /DNDEBUG /FeRgssadUnpacker RgssadUnpacker.c ******************************************************************************/ #ifndef __RU_STRING__ #define __RU_STRING__ static char RUS_INFO[] = "===============================================================================\n" "RgssadUpacker - 命令行的Rgssad/Rgss2a/Rgss3a文件解包器 - Version %s\n" "更多信息请见 http://blog.csdn.net/rgss2ad/archive/2011/02/16/6187475.aspx\n" "===============================================================================\n"; static char RUS_HELP[] = "用法:\n" " RgssadUnpacker <Rgssad文件名> [<可选参数>]\n" "\n" "可选参数(大小写不敏感):\n" " -pw=<密码>, --Password=<密码>\n" " 强制使用<密码>当作密码来解包, *不推荐使用*, 16进制使用 '0x' 前缀\n" " -od=<输出目录>, --OutDir=<输出目录>\n" " 将解包后文件输出至目录<输出目录>而不是目录<Rgssad文件名>_Unpacked\n" " -lf=[<记录文件名>], --LogFile=[<记录文件名>]\n" " 生成解包记录文件<记录文件名>或<Rgssad文件名>_Unpacked.log\n" " -rn, --Rename\n" " 使用文件序号代替解包后文件原名, 仅当输出文件名异常时才应使用这个参数\n" "\n" "例子:\n" " RgssadUnpacker Game.rgssad\n" " RgssadUnpacker Game.rgss2a\n" " RgssadUnpacker Game.rgssad -lf -rn\n" " RgssadUnpacker Game.rgssad -pw=0xDEADCAFE -od=C:\\Temp\\ -lf=Game.log\n"; static char RUS_E_FILE_OPEN[] = "无法打开文件 \"%s\"\n"; static char RUS_E_RGSSAD_SIGN[] = "无法识别的rgssad标识, 请确定这是一个rgssad/rgss2a/rgss3a文件\n"; static char RUS_E_ARGUMENT[] = "未知参数 \"%s\"\n"; static char RUS_E_DIR_LOCATE[] = "无法定位到目录 \"%s\"\n"; static char RUS_E_CALC_PASSWORD[] = "无法计算出密码, 可能是文件损坏或采取了不支持的加密方法\n"; static char RUS_E_UNPACK[] = "解包失败, 未知错误 0x%08X\n"; static char RUS_WAIT_UNPACK[] = "正在解包, 请稍候....\n"; static char RUS_WAIT_CACL_PASSWORD[] = "正在计算密码, 请稍候....\n"; static char RUS_SUCCESS[] = "没有检测到错误, 解包很可能成功了\n"; static const char *RUS_LOG_HEADER = "文件序号\x9""文件长度\x9""文件名\x9状态\n"; static const char *RUS_LOG_PASSWORD = "密码: 0x%08X\n"; static const char *RUS_LOG_FILE_SUCCESS = "成功\n"; static const char *RUS_LOG_FILE_FAIL = "失败\n"; #endif //__RU_STRING__ static const char *VERSION = "0.3"; #include <stdio.h> #include <string.h> #include <ctype.h> #ifdef _MSC_VER #include <direct.h> #include <io.h> #pragma warning(disable:4258) #pragma warning(disable:4996) #else #include <unistd.h> #include <stdbool.h> #endif static const int MAX_PATH = 260; static const int RGSSAD_SIGN_LENGTH = 8; static const char *RGSSAD_SIGN = "RGSSAD\x0\x1"; static const char *RGSSAD3_SIGN = "RGSSAD\x0\x3"; static const int BUF_SIZE = 0x10000; static bool rgssad3 = false; //错误 enum ruErrors { E_NONE = 0, //没有错误, 一切正常 E_RGSSAD_SIGN, //rgssasd文件标识 不符合 E_CALC_PASSWORD, //无法得到正确的密码 E_FILE_TOO_LONG, //文件过大(超过 2GB) E_FILE_OPEN, //打开(创建)文件失败 E_FILE_READ, //读取文件失败(多数情况下为 读取长度 超出 被读取文件长度) E_FILENAME_TOO_LONG,//文件名过长(超过 MAX_PATH - 1) E_ARGUMENT, //存在未知参数 E_DIR_LOCATE, //无法定位到输出目录 E_UNPACK //解包失败 }; ///使用 MultiByteToWideChar() 和 WideCharToMultiByte() #include <windows.h> /******************************************************************************* * 字符串编码转换 UTF-8 -> ANSI * str: 需转换的字符串 * 返回值: str ******************************************************************************/ static inline char *ruUtf8ToAnsi(char *str) { int l = strlen(str) + 1; wchar_t *wstr = (wchar_t *)malloc(l * sizeof(wchar_t)); MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, l * 2); WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, l, "_", NULL); free(wstr); return str; } /******************************************************************************* * 直接创建目录 * dirs: 目标目录字符串 * pos: 从dirs中的pos位置开始创建目录, pos位置之前的目录必须存在 * 返回值: 成功时返回 0, 失败时返回 非0 ******************************************************************************/ static inline int ruCreateDirs(const char *dirs, int pos) { int result = 0; char buf[MAX_PATH]; int l = strlen(dirs); if (l >= MAX_PATH) return -1; memcpy(buf, dirs, l + 1); for (int i = pos; i < l; ++i) if (buf[i] == '\\') { buf[i] = 0; result |= mkdir(buf); buf[i] = '\\'; } return result; } /******************************************************************************* * 检查并创建目录 * dirs: 目标目录字符串 * 返回值: 成功时返回 0, 失败时返回 非0 ******************************************************************************/ static inline int ruCheckDirs(const char *dirs) { char buf[MAX_PATH]; int l = strlen(dirs); if (l >= MAX_PATH) return -1; memcpy(buf, dirs, l + 1); for (int i = l - 1; i >= 0; --i) if (buf[i] == '\\') { buf[i] = 0; if (access(buf, 0) == 0) { buf[i] = '\\'; return ruCreateDirs(buf, i + 1); } buf[i] = '\\'; } return ruCreateDirs(buf, 0); } /******************************************************************************* * 检查 rgssad文件标识 是否符合 * rgssadFile: 以 "rb" 方式打开的 rgssad文件 * 返回值: rgssad文件标识 符合时返回 E_NONE, 不符合或失败时返回 其它ruErrors ******************************************************************************/ int ruCheckRgssadSign(FILE* rgssadFile) { FILE *rf = rgssadFile; int pos_bak = ftell(rf); rewind(rf); char rgssadSign[RGSSAD_SIGN_LENGTH]; fread(rgssadSign, RGSSAD_SIGN_LENGTH, 1, rf); fseek(rf, pos_bak, SEEK_SET); if (memcmp(rgssadSign, RGSSAD_SIGN, RGSSAD_SIGN_LENGTH) == 0) { rgssad3 = false; return E_NONE; } if (memcmp(rgssadSign, RGSSAD3_SIGN, RGSSAD_SIGN_LENGTH) == 0) { rgssad3 = true; return E_NONE; } return E_RGSSAD_SIGN; } /******************************************************************************* * 检查 密码 是否正确 * rgssadFile: 以 "rb" 方式打开的 rgssad文件 * password: 密码 * 返回值: 密码 正确时返回 E_NONE, 不正确或失败时返回 其它ruErrors ******************************************************************************/ int ruCheckPassword(FILE *rgssadFile, int password) { FILE *rf = rgssadFile; rewind(rf); //获取 rgssad文件长度 fseek(rf, 0, SEEK_END); int rgssadFileLength = ftell(rf); fseek(rf, RGSSAD_SIGN_LENGTH, SEEK_SET); //判断 rgssad文件长度 是否有效 if (rgssadFileLength < 0) return E_FILE_TOO_LONG; while (ftell(rf) < rgssadFileLength) { //判断 文件名长度在rgssad文件中的位置 是否有效 if ((ftell(rf) + 4 < 0) || (ftell(rf) + 4 > rgssadFileLength)) return E_FILE_READ; //获取 文件名长度 int fileNameLength; fread(&fileNameLength, 4, 1, rf); fileNameLength ^= password; //判断 文件名长度 是否有效 if ((fileNameLength < 0) || (fileNameLength > MAX_PATH - 1)) return E_FILENAME_TOO_LONG; //判断 文件名在rgssad文件中的位置 是否有效 if ((ftell(rf) + fileNameLength < 0) || (ftell(rf) + fileNameLength > rgssadFileLength)) return E_FILE_READ; //跳过 文件名 fseek(rf, fileNameLength, SEEK_CUR); //判断 文件长度在rgssad文件中的位置 是否有效 if ((ftell(rf) + 4 < 0) || (ftell(rf) + 4 > rgssadFileLength)) return E_FILE_READ; //获取 文件长度 int fileLength; fread(&fileLength, 4, 1, rf); for (int i = -1; i < fileNameLength; ++i) password = password * 7 + 3; fileLength ^= password; password = password * 7 + 3; //判断 文件在rgssad文件中的位置 是否有效 if ((fileLength < 0) || (ftell(rf) + fileLength < 0) || (ftell(rf) + fileLength > rgssadFileLength)) return E_FILE_READ; //跳过 文件 fseek(rf, fileLength, SEEK_CUR); } return E_NONE; } /******************************************************************************* * 计算 密码 * rgssadFile: 以 "rb" 方式打开的 rgssad文件 * password: 计算出的 密码 * 返回值: 得到 密码 时返回 E_NONE, 未得到或失败时返回 其它ruErrors ******************************************************************************/ int ruCalcPassword(FILE *rgssadFile, int *password) { FILE *rf = rgssadFile; rewind(rf); //跳过 rgssad文件标识 fseek(rf, RGSSAD_SIGN_LENGTH, SEEK_SET); if (rgssad3) { fread(password, 4, 1, rf); *password = *password * 9 + 3; return E_NONE; } int fileNameLength; fread(&fileNameLength, 4, 1, rf); for (int i = 0; i < MAX_PATH; ++i) if (ruCheckPassword(rf, fileNameLength ^ i) == 0) { *password = fileNameLength ^ i; return E_NONE; } return E_CALC_PASSWORD; } /******************************************************************************* * 定位文件数据 * rgssadFile: 以 "rb" 方式打开的 rgssad文件,文件指针应该处于文件信息的起始处 * fileName: 输出文件名缓冲,必须分配满 MAX_PATH个字节 * fileLength: 输出文件大小 * nextFile: 下一个文件信息的起始偏移 * password: 密码 * dataPassword: 数据密码 * 返回值: 文件存在返回 true,已经没有文件返回 false ******************************************************************************/ bool ruQueryFile(FILE *rgssadFile, char *fileName, int *fileLength, int *nextFile, int *password, int *dataPassword) { if (rgssad3) { //获取 文件数据偏移 int offset; fread(&offset, 4, 1, rgssadFile); offset ^= *password; //已经没有文件了 if (offset == 0) return false; //获取 文件长度 fread(fileLength, 4, 1, rgssadFile); *fileLength ^= *password; //获取 数据解密密码 fread(dataPassword, 4, 1, rgssadFile); *dataPassword ^= *password; //获取 文件名长度 int fileNameLength; fread(&fileNameLength, 4, 1, rgssadFile); fileNameLength ^= *password; //获取 文件名 fread(fileName, fileNameLength, 1, rgssadFile); for (int i = 0; i < fileNameLength; ++i) fileName[i] ^= *password >> (i % 4) * 8; fileName[fileNameLength] = 0; *nextFile = ftell(rgssadFile); //移动偏移到文件数据起始处 fseek(rgssadFile, offset, SEEK_SET); } else { //获取 文件名长度 int fileNameLength; fread(&fileNameLength, 4, 1, rgssadFile); fileNameLength ^= *password; *password = *password * 7 + 3; //获取 文件名 fread(fileName, fileNameLength, 1, rgssadFile); for (int i = 0; i < fileNameLength; ++i) { fileName[i] ^= *password; *password = *password * 7 + 3; } fileName[fileNameLength] = 0; //获取 文件长度 fread(fileLength, 4, 1, rgssadFile); *fileLength ^= *password; *password = *password * 7 + 3; *nextFile = ftell(rgssadFile) + *fileLength; //数据解密密码同信息密码 *dataPassword = *password; } return true; } /******************************************************************************* * 写输出文件 * rgssadFile: 以 "rb" 方式打开的 rgssad文件,文件指针应该处于文件数据的起始处 * fileName: 输出文件名 * fileLength: 输出文件大小 * password: 密码 * lofFile: 以 "w" 方式打开的 记录文件, 值为 NULL 时不创建 记录文件 * 返回值: 解包成功时返回 E_NONE, 失败时返回 E_FILE_OPEN ******************************************************************************/ int ruUnpackFile(FILE *rgssadFile, const char *fileName, int fileLength, int password, FILE *logFile) { //输出 文件 FILE *uf = fopen(fileName, "wb"); if (uf == NULL) { if (logFile != NULL) fprintf(logFile, RUS_LOG_FILE_FAIL); return E_FILE_OPEN; } int buf[BUF_SIZE >> 2]; while (fileLength > 0) { int length = fileLength < BUF_SIZE ? fileLength : BUF_SIZE; fread(buf, length, 1, rgssadFile); for (int i = 0; i < ((length + 3) >> 2); ++i) { buf[i] ^= password; password = password * 7 + 3; } fwrite(buf, length, 1, uf); fileLength -= length; } fclose(uf); if (logFile != NULL) fprintf(logFile, RUS_LOG_FILE_SUCCESS); return E_NONE; } /******************************************************************************* * 直接以password解包文件 (ruUnpack省略对多种错误的检测, 请先用ruCheckPassword检测) * rgssadFile: 以 "rb" 方式打开的 rgssad文件 * password: 密码 * isRenameFile: 是否更改文件名为文件序号(避免因为文件名非法无法解包出文件) * lofFile: 以 "w" 方式打开的 记录文件, 值为 NULL 时不创建 记录文件 * 返回值: 解包成功时返回 E_NONE, 失败时返回 其它ruErrors ******************************************************************************/ int ruUnpack(FILE *rgssadFile, int password, bool isRenameFile, FILE *logFile) { FILE *rf = rgssadFile; rewind(rf); int result = E_NONE; //文件序号 int fileNum = 0; //获取 rgssad文件长度 fseek(rf, 0, SEEK_END); int rgssadFileLength = ftell(rf); fseek(rf, RGSSAD_SIGN_LENGTH, SEEK_SET); //RGSSAD3需要跳过magickey的4字节 if (rgssad3) fseek(rf, 4, SEEK_CUR); //输出 logFile 表头 if (logFile != NULL) { fprintf(logFile, RUS_LOG_PASSWORD, password); fprintf(logFile, RUS_LOG_HEADER); } while (ftell(rf) < rgssadFileLength) { ++fileNum; //获得文件名 文件大小 文件数据解密密码 并定位文件指针到文件数据起始处 char fileName[MAX_PATH]; int fileLength, nextFile, dataPassword; if (!ruQueryFile(rf, fileName, &fileLength, &nextFile, &password, &dataPassword)) break; //输出 logFile 内容 if (logFile != NULL) fprintf(logFile, "%08d\x9% 8d\x9%s\x9", fileNum, fileLength, fileName); //生成 文件名 if (isRenameFile) sprintf(fileName, "%08X", fileNum); //转换 文件名编码 (UTF-8 -> ANSI) ruUtf8ToAnsi(fileName); //创建 目录 if (ruCheckDirs(fileName) != 0) result = E_FILE_OPEN; //写输出文件 if (ruUnpackFile(rf, fileName, fileLength, dataPassword, logFile) != E_NONE) result = E_FILE_OPEN; //继续处理下一个文件 fseek(rf, nextFile, SEEK_SET); } return result; } /******************************************************************************* * 主函数 >_< * 返回值: 成功时返回 0, 失败时返回 非0 ******************************************************************************/ int main(int argc, char **argv) { printf(RUS_INFO, VERSION); //无参数, 显示帮助 if (argc < 2) { printf(RUS_HELP); return 0; } FILE *rgssadFile = fopen(argv[1], "rb"); if (rgssadFile == NULL) { printf(RUS_E_FILE_OPEN, argv[1]); return E_FILE_OPEN; } //检查 rgssad文件标识 if (ruCheckRgssadSign(rgssadFile) != E_NONE) { printf(RUS_E_RGSSAD_SIGN); fclose(rgssadFile); return E_RGSSAD_SIGN; } int password = 0; bool isForcePassword = false; bool isLogFile = false; bool isRenameFile = false; char outDir[MAX_PATH]; //默认输出目录 sprintf(outDir, "%s_Unpacked\\", argv[1]); char logFileName[MAX_PATH]; //默认记录文件名 sprintf(logFileName, "%s_Unpacked.log", argv[1]); //解析 可选参数 for (int i = 2; i < argc; ++i) { if ((argv[i][0] == '-') && (argv[i][1] == '-')) { char tmp[8]; memcpy(tmp, &argv[i][2], 8); for (int i = 0; i < 8; ++i) tmp[i] = tolower(tmp[i]); if (strncmp(tmp, "password", 8) == 0) { if ((argv[i][11] == '0') || (argv[i][12] == 'x')) sscanf(&argv[i][11], "%x", &password); else sscanf(&argv[i][11], "%d", &password); isForcePassword = true; continue; } if (strncmp(tmp, "outdir", 6) == 0) { sscanf(&argv[i][9], "%s", outDir); continue; } if (strncmp(tmp, "logfile", 7) == 0) { isLogFile = true; if (strlen(argv[i]) > 9) sscanf(&argv[i][10], "%s", logFileName); continue; } if (strncmp(tmp, "rename", 6) == 0) { isRenameFile = true; continue; } } if (argv[i][0] == '-') { char tmp[2]; memcpy(tmp, &argv[i][1], 2); for (int i = 0; i < 2; ++i) tmp[i] = tolower(tmp[i]); if (strncmp(tmp, "pw", 2) == 0) { if ((argv[i][4] == '0') || (argv[i][5] == 'x')) sscanf(&argv[i][4], "%x", &password); else sscanf(&argv[i][4], "%d", &password); isForcePassword = true; continue; } if (strncmp(tmp, "od", 2) == 0) { sscanf(&argv[i][4], "%s", outDir); continue; } if (strncmp(tmp, "lf", 2) == 0) { isLogFile = true; if (strlen(argv[i]) > 3) sscanf(&argv[i][4], "%s", logFileName); continue; } if (strncmp(tmp, "rn", 2) == 0) { isRenameFile = true; continue; } } printf(RUS_E_ARGUMENT, argv[i]); fclose(rgssadFile); return E_ARGUMENT; } char oldDir[MAX_PATH]; getcwd(oldDir, MAX_PATH); //定位到输出目录 if (outDir[strlen(outDir) - 1] != '\\') { outDir[strlen(outDir) + 1] = 0; outDir[strlen(outDir)] = '\\'; } ruCheckDirs(outDir); if (chdir(outDir) != 0) { printf(RUS_E_DIR_LOCATE, outDir); fclose(rgssadFile); return E_DIR_LOCATE; } //计算密码 if (!isForcePassword) { printf(RUS_WAIT_CACL_PASSWORD); if (ruCalcPassword(rgssadFile, &password) != E_NONE) { printf(RUS_E_CALC_PASSWORD); return E_CALC_PASSWORD; }; } FILE *logFile = NULL; if (isLogFile) { chdir(oldDir); logFile = fopen(logFileName, "w"); chdir(outDir); } //解包 printf(RUS_WAIT_UNPACK); int result = ruUnpack(rgssadFile, password, isRenameFile, logFile); fclose(rgssadFile); if (logFile != NULL) fclose(logFile); chdir(oldDir); if (result == E_NONE) { printf(RUS_SUCCESS); return E_NONE; } else { printf(RUS_E_UNPACK); return E_UNPACK; } }
记得保存为带BOM的UTF-8文本。
P.S.:新增了对VC的支持,注意:VC6靠边站!VS2003没测试过但可能可以,VS2005及以上都应该没问题
会用命令行的用命令行,不会用的新建工程,关掉预编译头,强制使用C++编译即可。
相关文章推荐
- 四则运算生成程序——GUI支持和部分功能改进
- “四则运算生成程序——GUI支持和部分功能改进”链接
- [VC]基于对话框程序,自定义工具栏(支持真彩色图标,可添加文字)
- 基于状态机思想的按键扫描程序支持单按,连续按,长按模式。
- 基于正则的INI读写工具类,支持加密解密
- 某壳对.Net程序加密的原理及解密探讨五(元数据还原以及IL解码的改进)
- 基于浙大mooc给出的改进快排程序
- Maxtocode对.Net程序加密的原理及解密探讨五(元数据还原以及IL解码的改进)
- 基于python的图形化邮件发送程序(支持添加附件)
- 基于 EntityFramework 的数据库主从读写分离架构(2)- 改进配置和添加事务支持
- 讨论:一种基于状态位图的SPIHT改进算法(2)——编解码程序
- HTML5学习笔记:HTML5基于本地存储SQLite的每日工作任务清单程序.[只支持chrome]
- C#写文本日志帮助类(支持多线程)改进版(不适用于ASP.NET程序)
- 分享:http-watcher更新,改进对动态web程序的支持
- CookIM - 基于akka的分布式w 3ff8 ebsocket聊天程序,服务端支持多节点间消息通讯
- 基于DES加密的Java socket程序(加解密算、算法)
- HTML5学习笔记:HTML5基于本地存储SQLite的每日工作任务清单程序.[只支持chrome]
- 基于统计方法的二字词发掘程序(改进)
- 基于正则的INI读写工具类,支持加密解密
- 基于对话框程序,自定义工具栏(支持真彩色图标,可添加文字)