森yyds 2026-05-05 13:17 采纳率: 0%
浏览 6

解压AssetBundle块数据时遇到问题

问题描述

LZMA解压失败

Traceback (most recent call last):
  File "/data/data/com.termux/files/home/bin/python/unity/test.py", line 3, in <module>
    ur = UnityRes.load("data.unity3d")
  File "/data/data/com.termux/files/home/bin/python/unity/UnityRes/env.py", line 6, in load
    return cls(path)
  File "/data/data/com.termux/files/home/bin/python/unity/UnityRes/env.py", line 9, in __init__
    AssetBundle.parse_header(path)
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/data/data/com.termux/files/home/bin/python/unity/UnityRes/AssetBundle/AssetBundle.py", line 48, in parse_header
    self.parse_block()
    ~~~~~~~~~~~~~~~~^^
  File "/data/data/com.termux/files/home/bin/python/unity/UnityRes/AssetBundle/AssetBundle.py", line 73, in parse_block
    blockInfo_data = CompressionHelper.decompress_lz4(blockInfo_bytes, self.uncompressed_size)
  File "/data/data/com.termux/files/usr/lib/python3.13/site-packages/UnityPy/helpers/CompressionHelper.py", line 94, in decompress_lz4
    return lz4.block.decompress(data, uncompressed_size)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
_block.LZ4BlockError: Decompression failed: corrupt input or insufficient space in destination buffer. Error code: 5

推测

pos位置错了 当前位置不是压缩块数据的位置
报错代码

# TODO
from UnityPy.streams import EndianBinaryReader
from typing import Literal
from ..exception import UnknownHeaderMagicError

TYPE = Literal["UnityFS", "UnityWeb", "UnityRaw"]
BILOC = Literal["AtTheEnd", "Combined", "LZMA", "LZ4"]

class AssetBundle:
    def __init__(self):
        self.type: TYPE = None
        self.flagType: BILOC = None
        self.blocks_info = []
        self.dirs_info = []
    def parse_header(self, path):
        self.reader = EndianBinaryReader(path, endian='>')
        self.sig = self.reader.read(8)
        if self.sig == b"UnityFS\x00":
            self.type = "UnityFS"
        elif self.sig == b"UnityWeb":
            self.type = "UnityWeb"
        elif self.sig == b"UnityRaw":
            self.type = "UnityRaw"
        else:
            raise UnknownHeaderMagicError(str(self.sig))
        self.format_version = self.reader.read_u_int()
        self.unity_version = self.reader.read_string_to_null()
        self.unity_revision = self.reader.read_string_to_null()
        # WARNING: 去掉下面这行的注释的时候一定要把下面的seek删了!
        # self.file_size = self.reader.read_long()
        self.reader.seek(8, 1)
        self.compressed_size = self.reader.read_u_int()
        self.uncompressed_size = self.reader.read_u_int()
        self.flag = self.reader.read_u_int()
        self.compression_type = None
        self.block_info_location = None
        if self.flag & 0x01:
            self.compression_type = "LZMA"
        elif self.flag & 0x02 or self.flag & 0x03:
            self.compression_type = "LZ4"
        else:
            self.compression_type = "None"

        if self.flag & 0x40:
            self.block_info_location = "AtTheEnd"
        else:
            self.block_info_location = "Combined"
        self.parse_block()
    def parse_block(self):
        current_pos = self.reader.tell()
    
        if self.block_info_location == "AtTheEnd":
            self.reader.seek(self.reader.Length - self.compressed_size)
    
        blockInfo_bytes = self.reader.read_bytes(self.compressed_size)
        
        # 导入压缩辅助函数
        from UnityPy.helpers import CompressionHelper
    
        # 获取压缩类型标志 (低6位)
        comp_flag = self.flag & 0x3F
    
        print(f"comp_flag: {comp_flag}")
        print(f"compressed_size: {self.compressed_size}")
        print(f"uncompressed_size: {self.uncompressed_size}")
    
        # 根据标志选择解压方法
        if comp_flag == 0:  # None
            blockInfo_data = blockInfo_bytes
        elif comp_flag == 1:  # LZMA
            blockInfo_data = CompressionHelper.decompress_lzma(blockInfo_bytes, self.uncompressed_size)
        elif comp_flag in (2, 3):  # LZ4 or LZ4HC
            blockInfo_data = CompressionHelper.decompress_lz4(blockInfo_bytes, self.uncompressed_size)
        else:
            raise ValueError(f"Unknown compression flag: {comp_flag}")
    
        print(f"Decompressed block info size: {len(blockInfo_data)}")
    
        info_reader = EndianBinaryReader(blockInfo_data, endian=self.reader.endian)
    
        # 读16字节hash
        info_reader.read_bytes(16)
    
        # block count
        block_count = info_reader.read_int()
        print(f"block_count: {block_count}")
    
        self.blocks_info = []
        for i in range(block_count):
            uncompressed_size = info_reader.read_u_int()
            compressed_size = info_reader.read_u_int()
            flags = info_reader.read_u_short()
            self.blocks_info.append((uncompressed_size, compressed_size, flags))
            print(f"  Block{i}: uncomp={uncompressed_size}, comp={compressed_size}, flags={hex(flags)}")
    
        nodes_count = info_reader.read_int()
        print(f"nodes_count: {nodes_count}")
    
        self.dirs_info = []
        for i in range(nodes_count):
            offset = info_reader.read_long()
            size = info_reader.read_long()
            flags = info_reader.read_u_int()
            path = info_reader.read_string_to_null()
            self.dirs_info.append((offset, size, flags, path))
            if i < 5:
                print(f"  Node{i}: {path}")
    
        self.reader.seek(current_pos)
  • 写回答

1条回答 默认 最新

  • 檀越@新空间 2026-05-05 13:21
    关注

    下午好🌅🌅🌅
    本答案参考通义千问

    你遇到的 LZMA 解压失败 问题,主要原因是 解压时的数据位置错误或数据本身损坏。从你的代码和 Traceback 可以看出,错误发生在 CompressionHelper.decompress_lz4 函数中,具体是:

    _block.LZ4BlockError: Decompression failed: corrupt input or insufficient space in destination buffer. Error code: 5
    

    这说明在解压过程中,读取的数据不完整或不是 LZ4 压缩块格式,或者 目标缓冲区大小不足


    ✅ 问题分析

    1. LZ4 解压失败原因

    • 压缩数据与解压逻辑不匹配:例如,实际数据是 LZMA 而不是 LZ4。
    • 文件头/块信息解析错误blockInfo_bytes 的起始位置不正确,导致读取的是非压缩数据。
    • 文件损坏或不完整:可能 .unity3d 文件本身存在问题。
    • 解压函数参数不正确:如 uncompressed_size 不准确。

    🔧 详细解决方案

    ✅ 步骤一:检查 blockInfo_bytes 的读取位置是否正确

    当前代码中:

    blockInfo_bytes = self.reader.read_bytes(self.compressed_size)
    

    但是这段代码是在 parse_block() 中执行的,而 self.reader.tell() 在之前已经移动过。

    ❗ 问题点:

    • self.reader.seek(self.reader.Length - self.compressed_size) 会把指针定位到文件末尾前 compressed_size 处。
    • 但如果你的 blockInfo_bytes 实际上并不是在文件末尾,而是位于某个偏移处,那就会读错数据。

    ✅ 修改建议:

    确保 blockInfo_bytes 的读取位置是正确的。可以添加调试信息来确认当前位置和数据内容。

    print(f"Current position before reading block info: {self.reader.tell()}")
    blockInfo_bytes = self.reader.read_bytes(self.compressed_size)
    print(f"Read {len(blockInfo_bytes)} bytes for block info")
    print(f"First 16 bytes of block info: {blockInfo_bytes[:16].hex()}")
    

    ✅ 步骤二:验证压缩类型是否匹配

    你目前的代码中根据 comp_flag 来选择解压方式,但有时可能由于解析错误,导致实际数据类型与 comp_flag 不一致。

    ✅ 修改建议:

    在解压前打印 comp_flagcompression_type,确保它们与实际数据匹配。

    print(f"Compression flag: {comp_flag}, Compression type: {self.compression_type}")
    

    ✅ 步骤三:确保 uncompressed_size 正确

    LZ4 解压需要知道目标缓冲区的大小,如果这个值不正确,会导致解压失败。

    ✅ 修改建议:

    在调用 decompress_lz4 之前,先验证 uncompressed_size 是否合理。

    if self.uncompressed_size <= 0:
        raise ValueError("Invalid uncompressed size: 0 or negative.")
    

    ✅ 步骤四:处理 LZMA 和 LZ4 混合压缩的情况

    Unity 的 AssetBundle 可能使用不同的压缩方式(如 LZMA 或 LZ4),甚至混合压缩。因此,你需要更精确地判断压缩类型。

    ✅ 修改建议:

    你可以尝试通过读取压缩块的前几个字节来判断压缩类型。

    # 判断压缩类型
    magic = blockInfo_bytes[:4]
    if magic == b'LZMA':
        blockInfo_data = CompressionHelper.decompress_lzma(blockInfo_bytes, self.uncompressed_size)
    elif magic == b'LZ4':
        blockInfo_data = CompressionHelper.decompress_lz4(blockInfo_bytes, self.uncompressed_size)
    else:
        raise ValueError("Unknown compression format")
    

    ✅ 步骤五:检查文件是否损坏或不完整

    ✅ 修改建议:

    在加载文件前,先检查文件是否存在、大小是否合理。

    import os
    
    if not os.path.exists(path):
        raise FileNotFoundError(f"File not found: {path}")
    if os.path.getsize(path) < 1024:
        raise ValueError("File is too small to be a valid AssetBundle.")
    

    🛠️ 修改后的代码示例

    以下是修改后的 parse_block 方法,包含了上述改进:

    def parse_block(self):
        current_pos = self.reader.tell()
        
        if self.block_info_location == "AtTheEnd":
            self.reader.seek(self.reader.Length - self.compressed_size)
        
        blockInfo_bytes = self.reader.read_bytes(self.compressed_size)
        
        # 添加调试信息
        print(f"Current position before reading block info: {self.reader.tell()}")
        print(f"Read {len(blockInfo_bytes)} bytes for block info")
        print(f"First 16 bytes of block info: {blockInfo_bytes[:16].hex()}")
        
        from UnityPy.helpers import CompressionHelper
        
        comp_flag = self.flag & 0x3F
        print(f"Compression flag: {comp_flag}, Compression type: {self.compression_type}")
        
        if self.uncompressed_size <= 0:
            raise ValueError("Invalid uncompressed size: 0 or negative.")
        
        # 根据压缩类型解压
        if comp_flag == 0:
            blockInfo_data = blockInfo_bytes
        elif comp_flag == 1:
            blockInfo_data = CompressionHelper.decompress_lzma(blockInfo_bytes, self.uncompressed_size)
        elif comp_flag in (2, 3):
            # 检查是否是 LZ4 压缩
            magic = blockInfo_bytes[:4]
            if magic == b'LZ4':
                blockInfo_data = CompressionHelper.decompress_lz4(blockInfo_bytes, self.uncompressed_size)
            else:
                raise ValueError("Unexpected compression format")
        else:
            raise ValueError(f"Unknown compression flag: {comp_flag}")
    

    📌 总结

    | 问题 | 原因 | 解决方案 | |------|------|----------| | LZMA/LZ4 解压失败 | 数据不完整或压缩类型错误 | 检查 blockInfo_bytes 的读取位置和压缩类型 | | 文件损坏或不完整 | 文件路径错误或文件损坏 | 检查文件是否存在、大小是否合理 | | 压缩类型不匹配 | comp_flag 与实际数据不一致 | 添加校验逻辑,判断压缩类型 | | 解压目标缓冲区不足 | uncompressed_size 错误 | 验证 uncompressed_size 是否合理 |


    如果你能提供具体的 .unity3d 文件或更多上下文信息,我可以进一步帮助你定位问题。

    评论

报告相同问题?

问题事件

  • 创建了问题 5月5日