あやかしランブル!(Ayakashi)解包求教

あやかしランブル! - DMM GAMES

上月游戏内资源还没被加密的,昨日维修后发现部分图片资源被加密无法用assetstudio打开。

通过查看文件内容发现文件头部还是“UnityFS”标识头。

请问有没有解密办法,谢谢

應該是UnityFS版本問題,舊的是2021.X.X,新的是6000.X.X,看能不能找到支援6000.X.X。

這個版本可以開

GitHub - Escartem/AnimeStudio: Updated AssetStudio, supports GI 6.0+, HSR 3.6+, ZZZ 2.2+ (and more), with improvements and new features (*゚∀゚*)

謝謝

借問一下,有人知道怎麼解密資源路徑嗎?

感覺是放是在work.dgs.rel.mas,但不曉得怎麼解…

大佬,麻烦问一下立绘在那个路径里面啊,没找到立绘,就找到个小人动画,不知道怎么组合 :sob:

这游戏进去不会下载数据你是怎么下载完整数据的?

eventdata0000103.evsc.rar (7.0 KB)游戏里面的剧本好像是加密的,不知道有没有大佬能看看

這個跟れじぇくろ一樣的,可以直接用UTF-8編碼看到劇情文本,只是其它部份就還是亂碼。

谢谢各位大佬了

又看了一下,读取就这样,这只是一个简单解析,不展示具体细节。

读取
import struct
import os

class EVSCCommandParam:
    """EVSC命令参数联合体"""
    def __init__(self):
        self.data = 0
        self.instruction = None  # EVSCCommandParamInstruction
        self.integer = None      # EVSCCommandParamInteger
        self.float_val = None    # EVSCCommandParamFloat
        self.string = None       # EVSCCommandParamString
        self.stack_variable = None  # EVSCCommandParamStackVariable

class EVSCCommandParamInstruction:
    """指令型参数"""
    def __init__(self):
        self.no = 0
        self.argument_count = 0

class EVSCCommandParamInteger:
    """整数型参数"""
    def __init__(self):
        self.value = 0

class EVSCCommandParamFloat:
    """浮点型参数"""
    def __init__(self):
        self.value = 0.0

class EVSCCommandParamString:
    """字符串型参数(偏移索引)"""
    def __init__(self):
        self.string_offset = 0

class EVSCCommandParamStackVariable:
    """栈变量型参数"""
    def __init__(self):
        self.variable_offset = 0

class EVSCCommand:
    """EVSC命令结构"""
    SIZE = 8  # 命令基础结构固定8字节
    
    def __init__(self):
        self.data_offset = 0
        self.type = 0
        self.debug_info_offset = 0
        self.param = EVSCCommandParam()

class EVSCHeader:
    """EVSC头部信息"""
    def __init__(self):
        self.file_id = ""
        self.version = ""
        self.command_offset = 0
        self.command_count = 0
        self.string_offset = 0
        self.string_size = 0
        self.debug_info_offset = 0
        self.debug_info_count = 0
        self.debug_string_offset = 0
        self.debug_string_size = 0
        self.request_stack_size = 0

class EVSCData:
    """EVSC数据解析主类"""
    EVSC_OFFSET_NONE = 4294967295
    EVSC_OFFSET_24BIT_NONE = 16777215
    
    def __init__(self):
        self.binary = None
        self.header = EVSCHeader()
        self.commands = []
        self.string_pool = b""  # 字符串池原始数据
    
    def parse(self, file_path):
        """解析EVSC文件"""
        try:
            with open(file_path, "rb") as f:
                self.binary = f.read()
            
            self._parse_header()
            self._parse_string_pool()
            self._parse_commands()
            
            return True
        except Exception as e:
            print(f"解析失败: {str(e)}")
            return False
    
    def _parse_header(self):
        """解析前44字节头部信息"""
        if len(self.binary) < 44:
            raise ValueError("文件太小,不包含完整的EVSC头部")
        
        header_data = self.binary[:44]
        self.header.file_id = header_data[0:4].decode('ascii').strip('\x00')
        self.header.version = header_data[4:8].decode('ascii').strip('\x00')
        self.header.command_offset = struct.unpack("<I", header_data[8:12])[0]
        self.header.command_count = struct.unpack("<I", header_data[12:16])[0]
        self.header.string_offset = struct.unpack("<I", header_data[16:20])[0]
        self.header.string_size = struct.unpack("<I", header_data[20:24])[0]
        self.header.debug_info_offset = struct.unpack("<I", header_data[24:28])[0]
        self.header.debug_info_count = struct.unpack("<I", header_data[28:32])[0]
        self.header.debug_string_offset = struct.unpack("<I", header_data[32:36])[0]
        self.header.debug_string_size = struct.unpack("<I", header_data[36:40])[0]
        self.header.request_stack_size = struct.unpack("<I", header_data[40:44])[0]
    
    def _parse_string_pool(self):
        """解析字符串池"""
        if self.header.string_offset == self.EVSC_OFFSET_NONE:
            return
            
        start = self.header.string_offset
        end = start + self.header.string_size
        
        if end > len(self.binary):
            raise ValueError("字符串池超出文件范围")
            
        self.string_pool = self.binary[start:end]
    
    def _get_string_from_pool(self, offset):
        """从字符串池获取字符串"""
        if not self.string_pool or offset >= len(self.string_pool):
            return ""
            
        end = offset
        while end < len(self.string_pool) and self.string_pool[end] != 0:
            end += 1
            
        return self.string_pool[offset:end].decode('utf-8', errors='replace')
    
    def _parse_commands(self):
        """解析命令列表"""
        if self.header.command_offset == self.EVSC_OFFSET_NONE:
            return
            
        start_offset = self.header.command_offset
        total_commands = self.header.command_count
        
        if start_offset + total_commands * EVSCCommand.SIZE > len(self.binary):
            raise ValueError("命令块超出文件范围")
        
        self.commands = []
        
        for i in range(total_commands):
            cmd = EVSCCommand()
            cmd_offset = start_offset + i * EVSCCommand.SIZE
            
            cmd.data_offset = cmd_offset
            first_4_bytes = struct.unpack("<I", self.binary[cmd_offset:cmd_offset+4])[0]
            cmd.type = first_4_bytes & 0xFF
            cmd.debug_info_offset = first_4_bytes >> 8
            
            param_data = struct.unpack("<I", self.binary[cmd_offset+4:cmd_offset+8])[0]
            self._parse_command_param(cmd, param_data)
            
            self.commands.append(cmd)
    
    def _parse_command_param(self, cmd, param_data):
        """根据命令类型解析参数"""
        cmd.param.data = param_data
        
        if cmd.type == 0:
            cmd.param.instruction = EVSCCommandParamInstruction()
            cmd.param.instruction.no = param_data & 0xFFFF
            cmd.param.instruction.argument_count = (param_data >> 16) & 0xFFFF
        elif cmd.type == 1:
            cmd.param.integer = EVSCCommandParamInteger()
            cmd.param.integer.value = struct.unpack("<i", struct.pack("<I", param_data))[0]
        elif cmd.type == 2:
            cmd.param.float_val = EVSCCommandParamFloat()
            cmd.param.float_val.value = struct.unpack("<f", struct.pack("<I", param_data))[0]
        elif cmd.type == 3:
            cmd.param.string = EVSCCommandParamString()
            cmd.param.string.string_offset = param_data
        elif cmd.type == 4:
            cmd.param.stack_variable = EVSCCommandParamStackVariable()
            cmd.param.stack_variable.variable_offset = struct.unpack("<i", struct.pack("<I", param_data))[0]

def print_header(header):
    """打印头部信息"""
    print("\n===== EVSC 头部信息 =====")
    print(f"文件ID: {header.file_id}")
    print(f"版本: {header.version}")
    print(f"命令偏移: 0x{header.command_offset:X} ({header.command_offset})")
    print(f"命令数量: {header.command_count}")
    print(f"字符串偏移: 0x{header.string_offset:X} ({header.string_offset})")
    print(f"字符串大小: {header.string_size} 字节")
    print(f"调试信息偏移: 0x{header.debug_info_offset:X} ({header.debug_info_offset})")
    print(f"调试信息数量: {header.debug_info_count}")
    print(f"调试字符串偏移: 0x{header.debug_string_offset:X} ({header.debug_string_offset})")
    print(f"调试字符串大小: {header.debug_string_size} 字节")
    print(f"请求栈大小: {header.request_stack_size}")

# 指令解释映射表(保持不变)
INSTRUCTION_EXPLANATIONS = {
}

def print_commands(commands, evsc_data):
    """打印命令列表(包含中文指令解释)"""
    if not commands:
        print("\n===== 没有命令 =====")
        return
        
    print("\n===== 命令列表 =====")
    print(f"共 {len(commands)} 条命令:")
    print("-" * 150)
    print(f"{'索引':<5} {'偏移':<10} {'类型':<5} {'调试偏移':<10} {'参数':<100}")
    print("-" * 150)
    
    for i, cmd in enumerate(commands):
        param_str = ""
        
        if cmd.param.instruction:
            # 关键修改:用指令编号减30作为字典的键
            mapped_key = cmd.param.instruction.no - 30
            explain = INSTRUCTION_EXPLANATIONS.get(
                mapped_key, 
                f"未知指令(原始编号: {cmd.param.instruction.no}, 映射键: {mapped_key})"
            )
            param_str = (f"指令(原始编号: {cmd.param.instruction.no}, 映射键: {mapped_key}, 解释: {explain}, "
                         f"参数数: {cmd.param.instruction.argument_count})")
        elif cmd.param.integer:
            param_str = f"整数: {cmd.param.integer.value}"
        elif cmd.param.float_val:
            param_str = f"浮点数: {cmd.param.float_val.value:.6f}"
        elif cmd.param.string:
            str_val = evsc_data._get_string_from_pool(cmd.param.string.string_offset)
            param_str = f"字符串(偏移: {cmd.param.string.string_offset}, 值: '{str_val}')"
        elif cmd.param.stack_variable:
            param_str = f"栈变量(偏移: {cmd.param.stack_variable.variable_offset})"
        else:
            param_str = f"原始数据: 0x{cmd.param.data:X}"
            
        print(f"{i:<5} 0x{cmd.data_offset:X}<{cmd.data_offset:>5} {cmd.type:<5} 0x{cmd.debug_info_offset:X}    {param_str}")

def main():
    print("=" * 60)
    print("          EVSC 文件解析器(带中文指令解释)")
    print("=" * 60)
    
    file_path = input("请输入EVSC文件路径: ").strip()
    
    if not os.path.exists(file_path):
        print(f"错误: 文件 '{file_path}' 不存在")
        return
        
    if not os.path.isfile(file_path):
        print(f"错误: '{file_path}' 不是一个文件")
        return
    
    evsc_data = EVSCData()
    if evsc_data.parse(file_path):
        print(f"\n成功解析文件: {os.path.abspath(file_path)}")
        print_header(evsc_data.header)
        print_commands(evsc_data.commands, evsc_data)
        print("\n" + "=" * 60)
        print("解析完成")
        print("=" * 60)
        input("按回车键退出...")

if __name__ == "__main__":
    main()
1 个赞

十分感谢大佬帮助,如果能还原相关操作就好了

28598519a/ayarabu_AssetDL: Use to download all Ayakashi Rumble resources
这位大佬做了能获取所有资源路径的脚本,完全不理解这是怎么做到的,太离谱了。

话说对于这些游戏有大佬知道怎么获取索引文件吗,比如都有哪些角色,名字是什么,对应的id,以及各种详细文本能得到吗。还有妖怪我发现他的动态效果都是用的sprite studio做的,不是普通的atlas json和png,然后又转成了一种二进制文件,只有ssfb(记不清具体啥后缀了)和png文件,这种该怎么播放呢。

他有个ab包存储了大部分索引。

Github網址底下有個 sh0wer1ee,我看他的github.io有分析思路。
照他方式確實有看到遊戲資料庫,但目前不知道要怎麼讀取。

你可以看源码,用的是unitypy读取的,但是不知道为什么能读

想說有工具能直接用最好。
用了一下UnityPy,但抓不到AssetBundleManifest的資料,應該是Unity版本問題。

那是一个ab包,加载出来就是清单。自动搞不定手动下载然后扔assetstudio就行了。

我知道那是ab包,但用assetstudio抓不到清單。
有哪個版本的可以抓?還是我弄錯方法了?