如何使用Python将JSON文件转换回Wasteland 3适用的.bundle资源包
废土3(Wasteland 3)语言包.bundle文件处理说明
现有解包代码功能
以下代码可实现将未压缩的Unity .bundle格式语言文件解析导出为JSON格式,但不包含JSON反向打包回.bundle格式的逻辑,目前可正常读取.bundle内容,但无法将修改后的JSON重新打包为游戏可识别的.bundle文件。
JSON反向打包.bundle实现方案
反向打包本质是将解包流程逆序实现,核心操作逻辑如下:
- 保留原始未压缩.bundle文件的固定头部元数据,包括UnityFS魔数、版本号、节点信息、区块偏移量等无需修改的字段,不需要重新生成
- 读取修改后的JSON文件,按解包时的字段顺序逐一序列化回二进制流:
- 依次写入
m_GameObject的两个int32字段、4字节占位符、m_Enabled字段、m_Script的两个字段 - 写入
m_Name的长度、UTF-8编码的字符串内容,随后补充对齐字节到4字节边界 - 遍历
stringTables数组,依次写入每个表的Filename长度、内容、对齐字节,接着写入entryID数组的长度和所有int32格式的entryID,再写入femaleTexts数组的长度、每个文本的长度+内容+对齐字节,最后写入defaultTexts数组的长度、每个文本的长度+内容+对齐字节
- 依次写入
- 用新生成的二进制内容替换原始.bundle解析出的对应语言数据区块,重新计算各区块的大小、偏移量并写入对应字段
- 按原始结构拼接所有区块后导出,即得到可被游戏识别的.bundle文件
注意事项
- 所有操作前必须先将原始.bundle文件用UABE解压为无压缩状态,打包完成后也可通过UABE重新压缩
- 数字序列化时的字节序要和读取时严格对应:小端(<)、大端(>)不能混淆
- 所有变长字段写入后必须补0对齐到4字节边界,否则会导致游戏读取崩溃
现有.bundle转JSON解包代码
import struct, sys def calculate_padding_len(ilen): res = (ilen % 4) if res == 0: return 0 else: res = 4 - res return res def int32(reader, endian=">"): return struct.unpack(endian + "i", reader.read(4))[0] def int64(reader, endian=">"): return struct.unpack(endian + "q", reader.read(8))[0] class Node(): pass def ReadtoNull(reader): savepos = reader.tell() stre = reader.read().split(b"\x00")[0] reader.seek(savepos+len(stre) + 1) return stre def export(inf): import os #os.makedirs(out, exist_ok=True) inf = open(inf, "rb") magic = ReadtoNull(inf) if magic != b"UnityFS": return 0 version = int32(inf) ReadtoNull(inf) ReadtoNull(inf) size = int64(inf) compressed = int32(inf) inf.read(4) flags = int32(inf) if flags != 64: #raise ValueError(f"This is not decompressed\nDecompress file with UABE first") DD = flags & 0x3F match DD: case 1: raise ValueError("LZMA Compression isn't supported, please decompress file with UABE first.") case 2: raise ValueError("LZ4 Compression isn't supported, please decompress file with UABE first.") case 3: raise ValueError("LZ4HC Compression isn't supported, please decompress file with UABE first.") case default: raise ValueError("Unknown Compression") dataHash = inf.read(16) unkcount = int32(inf) topl = 0 for i in range(unkcount): a = int32(inf, ">") topl += a inf.read(6) nodes_count = int32(inf) Nodes = [] import io for i in range(nodes_count): newNode = Node() newNode.offset = int64(inf) newNode.size = int64(inf) newNode.flags = int32(inf) newNode.path = ReadtoNull(inf) Nodes.append(newNode) allCab = io.BytesIO(inf.read(topl)) MyNode = Nodes[0] allCab.seek(MyNode.offset) MyCab = io.BytesIO(allCab.read(MyNode.size)) MyCab.seek(12) dataStart = int32(MyCab) MyCab.seek(36) fileCount = int32(MyCab, "<") MyCab.seek(dataStart - 84) fileSizes = [int32(MyCab, "<"), int32(MyCab, "<")] MyCab.read(16) fileSizes.append(int32(MyCab, "<")) MyCab.seek(dataStart) memories = [] for i in fileSizes: memories.append(MyCab.read(i)) fileNameListbutMemory = io.BytesIO(memories[-1]) fileList = [] #fileNameListbutMemory.read(4) for i in range(fileCount): size = int32(fileNameListbutMemory, "<") name = fileNameListbutMemory.read(size) fileNameListbutMemory.read(calculate_padding_len(size)) fileList.append(name) savepos = fileNameListbutMemory.tell() spl = fileNameListbutMemory.read().split(b"\x01\x00\x00\x00")[0] fileNameListbutMemory.seek(savepos + len(spl) + 4) for name,data in zip(fileList, memories): if b"/" in name: basename = b".".join(name[::-1].split(b".")[1:]).split(b"/")[0][::-1] + b".json" data = io.BytesIO(data) out = {} out["m_GameObject"] = {} out["m_GameObject"]["m_FileID"] = int32(data, "<") out["m_GameObject"]["m_PathID"] = int32(data, "<") data.read(4) out["m_Enabled"] = int32(data, "<") out["m_Script"] = {} out["m_Script"]["m_FileID"] = int32(data, "<") out["m_Script"]["m_PathID"] = int64(data, "<") namesize = int32(data, "<") out["m_Name"] = data.read(namesize).decode() data.read(calculate_padding_len(namesize)) string_table_length = int32(data, "<") out["stringTables"] = [] for i in range(string_table_length): Filename_length = int32(data, "<") newdict = {} newdict["Filename"] = data.read(Filename_length).decode() data.read(calculate_padding_len(Filename_length)) lenentryid = int32(data, "<") newdict["entryIDs"] = [] for a in range(lenentryid): newdict["entryIDs"].append(int32(data, "<")) lengfemaleTexts = int32(data, "<") newdict["femaleTexts"] = [] for a in range(lengfemaleTexts): size = int32(data, "<") newdict["femaleTexts"].append(data.read(size).decode()) data.read(calculate_padding_len(size)) newdict["defaultTexts"] = [] lendef = int32(data, "<") for a in range(lendef): size = int32(data, "<") newdict["defaultTexts"].append(data.read(size).decode());data.read(calculate_padding_len(size)) out["stringTables"].append(newdict) import json with open(basename, "w", encoding="utf8") as fp: json.dump(out,fp, indent=2) if not len(sys.argv) == 1: export(sys.argv[1]) else: print("Usage:") print("\tbundle.py %s" % "[BUNDLE]")
内容的提问来源于stack exchange,提问作者Syfity
相关产品推荐
相关产品推荐

