lometsj / ccz_remake

0 stars 0 forks source link

剧本格式研究 #4

Open lometsj opened 2 years ago

lometsj commented 2 years ago

参考博雅张生的帖子 http://xycq.online/forum/viewthread.php?tid=79466&highlight=%B2%DC%B2%D9%B4%AB%BE%E7%B1%BE%B4%FA%C2%EB%CF%EA%BD%E2

lometsj commented 2 years ago

import os
import sys
from until import *

test_eex = 'D:/ccz_origin/三国志曹操传简体中文经典版 [水木清华特别版v4带全部动画].ccz.水木年华/test.eex'
magic_eex = b'\x45\x45\x58\x00\x01\x02\x00\x00\x00\x00'

class parser_eex():
    def read_until(self, end):
        ret = b''
        end_len = len(end)
        ret = ret + self.f.read(end_len)
        if ret == end:
            return ret
        while True:
            # input("read")
            # print(ret)
            ret = ret + self.f.read(1)
            if ret[-end_len:] == end:
                return ret

    def read_u32(self):
        return u32(self.f.read(4))

    def read_u16(self):
        return u16(self.f.read(2))

    def read_magic(self, magic):
        # print(magic)
        # print(hex(self.f.tell()))
        buf = self.f.read(len(magic))
        # print(buf)
        if buf != magic:
            raise RuntimeError('format error expect {} but {}'.format(magic, buf))

    def read_bool(self):
        bool_msg = self.f.read(2)
        if bool_msg == '\x01\x00':
            return True
        elif bool_msg == '\x00\x00':
            return False

    def __init__(self, eex_path):
        self.event_len = None
        self.section_state = None
        if not os.access(eex_path, os.F_OK | os.R_OK):
            raise RuntimeError("file not exist or permit denied")
        self.f = f = open(eex_path, 'rb')
        self.read_magic(magic_eex)
        self.scene_offset = []
        self.scene_offset.append(u32(f.read(4)))
        while f.tell() != self.scene_offset[0]:
            self.scene_offset.append(u32(f.read(4)))

        self.func_cmd = {
            0x0: self.cmd_event_end,
            0x1: self.cmd_child_event_start,
            0x2: self.cmd_inside_msg,
            0x3: self.cmd_else,
            0x4: self.cmd_ask_test,
            0x5: self.cmd_var_test,
            0x6: self.cmd_team_battle_set,
            0x7: self.cmd_battle_test,
            0x8: self.cmd_menu_control,
            0x9: self.cmd_sleep,
            0xa: self.cmd_init_part_var,
            0xb: self.cmd_var_set,
            0xc: self.cmd_end_section,
            0xd: self.cmd_end_scene,
            0xe: self.cmd_battle_failed,
            0xf: self.cmd_end_set

        }

    def get_scene_count(self):
        return len(self.scene_offset)

    def parse_section(self, idx):
        self.section_state = 'test'
        print('section {}'.format(idx))
        self.f.seek(self.scene_offset[idx])
        section_len = self.read_u16()
        while True:
            cmd_type = self.read_u16()
            # print('cmd_type: {} seek: {}'.format(hex(cmd_type), hex(self.f.tell())))
            if cmd_type in self.func_cmd.keys():
                func = self.func_cmd[cmd_type]
                ret = func()
                if ret == 'end_event':
                    break
            else:
                print('err type: {}'.format(hex(cmd_type)))
        print('section end')
        pass

    def parse_all(self):
        for scene_idx in range(self.get_scene_count()):
            self.parse_section(scene_idx)
        pass

    def cmd_event_end(self):
        print('enter {}'.format(self.f.tell()))
        if self.section_state == 'test':
            self.event_len = self.read_u16()
            self.section_state = 'event'
        elif self.section_state == 'event':
            return 'end_event'
        print('事件结束')
        pass
    def cmd_child_event_start(self):
        print('子事件设定')
        pass

    def cmd_inside_msg(self):
        self.read_magic(b'\x05\x00')
        msg = self.read_until(b'\x00')
        print('内部消息 : {}'.format(msg))

    def cmd_else(self):
        print('else')

    def cmd_ask_test(self):
        self.read_magic(b'\x26\x00')
        bool_msg = self.read_bool()
        if bool_msg:
            print("询问测试 是")
        else:
            print("询问测试 否")

    def cmd_var_test(self):
        self.read_magic(b'\x35\x00')
        true_var = []
        false_var = []
        true_var_count = u16(self.f.read(2))
        for i in range(true_var_count):
            true_var.append(u16(self.f.read(2)))
        self.read_magic(b'\x35\x00')
        false_var_count = u16(self.f.read(2))
        for i in range(false_var_count):
            false_var.append(u16(self.f.read(2)))
        print('变量测试 true: {} false: {}'.format(true_var, false_var))

    def cmd_team_battle_set(self):
        self.read_magic(b'\x2e\x00')
        bool_msg = self.read_bool()
        self.read_magic(b'\x04\x00')
        unit_num = self.read_u32()
        enforce_on_unit = []
        enforce_out_unit = []
        for i in range(5):
            self.read_magic(b'\x38\x00')
            enforce_on_unit.append(self.read_u16())
        for i in range(5):
            self.read_magic(b'\x39\x00')
            enforce_out_unit.append(self.read_u16())
        print('我军出场设定 强制{}人 强制出场: {} 强制不出场: {}'.format(unit_num, enforce_on_unit, enforce_out_unit))

    def cmd_battle_test(self):
        print('出战测试')

    def cmd_menu_control(self):
        self.read_magic(b'\x2e\x00')
        bool_msg = self.read_bool()
        print('菜单控制 {}'.format(bool_msg))

    def cmd_sleep(self):
        self.read_magic(b'\x04\x00')
        sec = 10*self.read_u32()
        print('延时 {}秒'.format(sec))

    def cmd_init_part_var(self):
        print('初始化局部变量')

    def cmd_var_set(self):
        self.read_magic(b'\x04\x00')
        var_idx = self.read_u32()
        self.read_magic(b'\x27\x00')
        bool_msg = self.read_bool()
        print("变量赋值 var_{} = {}".format(var_idx, bool_msg))

    def cmd_end_section(self):
        print('结束section')

    def cmd_end_scene(self):
        print('结束scene')

    def cmd_battle_failed(self):
        print('战斗失败')

    def cmd_end_set(self):
        self.read_magic(b'\x12\x00')
        end_type = self.read_u16()
        type_txt = {0: '红线', 1: '黄线', 2:'蓝线'}
        print('结局设定 {}'.format(type_txt[end_type]))

if __name__ == '__main__':
    p = parser_eex(test_eex)
    p.parse_all()
lometsj commented 2 years ago

先参考张生的帖子完成0xf以下命令解析

lometsj commented 2 years ago

0x13 case

13 00 04 00 xx xx xx xx case序号

lometsj commented 2 years ago

0x12 选择框

12 00 05 00 文字 00 02 00 xx xx(武将序号

lometsj commented 2 years ago

0x11 剧本跳转

11 00 37 00 xx xx(剧本序号 r s 顺序

lometsj commented 2 years ago

0x14 对话

14 00 05 00 文字 00

lometsj commented 2 years ago

0x15 对话2

15 00 02 00 xx xx yy yy (武将序号 两个 02 00 05 00 文字 00

lometsj commented 2 years ago

0x16 信息

16 00 05 00 文字 00

lometsj commented 2 years ago

0x17 场所名

17 00 05 00 文字 00

lometsj commented 2 years ago

0x18事件名称设定

18 00 05 00 文字 00

lometsj commented 2 years ago

0x19 胜利条件

19 00 05 00 文字 00

0x1a 胜利条件显示

1a 00 05 00 文字 00

0x1b 撤退信息是否显示设定

1b 00 02 00 xx xx 27 00 yy yy (x武将序号 y true/false

绘图

1c 00

调色板设定

1d 00

0x1e 武将重绘

1e 00

地图视点切换 0x1f

1f 00 04 00 xx xx xx xx 04 00 yy yy yy yy (x,y)坐标

0x20 武将头像状态设置

20 00 4a 00 xx xx 依次是


00 曹操 普通
01  惊讶
02 愤怒
03 惊喜
04 夏侯惇 蒙眼
05 孔明 邪恶
06 曹丕 称帝
07 夏侯惇 独眼
08 孔明 正常
lometsj commented 2 years ago

0x21 战场物体添加(其实只有放火

21 00 04 00 xx xx xx xx 04 00 yy yy yy yy 10 00 zz zz (放火 00 00 回复 01 00 26 00 00 00 26 00 00 00

0x22 播放动画(视频

22 00 1b 00 xx xx(视频序号

音效 0x23

23 00 1e 00 xx xx (音频序号 04 00 yy yy yy yy (00 无限循环 ff停止 其他次数


Se_e_00----蝉鸣、鸟叫
Se_e_01----蝉鸣
Se_e_02----夜晚蝈蝈叫、狼叫
Se_e_03----夜晚风呼号、狼吼
Se_e_04----电闪雷鸣
Se_e_05----树林鸟声瞅瞅
Se_e_06----孩子叫、马车声
Se_e_07----惨烈的人叫马嘶
Se_e_08----河水流动,浪涛拍岸
Se_m_00----纵火
Se_m_01----烈燃
Se_m_02----磨刀声
Se_m_03----狂风刮过
Se_m_04----消失
Se_m_05----狂风刮过
Se_m_06----落石
Se_m_07----居石落下
Se_m_08----放炮
Se_m_09----轰鸣
Se_m_10----水流
Se_m_11----急流
Se_m_12----呼啸声(朱雀)
Se_m_13----鬼怪声(青龙)
Se_m_14----野潴叫声(玄武)
Se_m_15----鬼怪出笼(白虎)
Se_m_16----惨叫声
Se_m_17----雷雨交加
Se_m_18----轰隆擦过
Se_m_19----骤风起
Se_m_20----咕噜冒泡
Se_m_21----蜂蛰
Se_m_22----车碾声
Se_m_23----抽取声
Se_m_24----扑扑声
Se_m_25----快吸取
Se_m_26----慢吸取
Se_m_27----充满
Se_m_28----整体充满
Se_m_29----中计陷于混乱
Se_m_30----快速走路声
Se_m_31----没有命中
Se_m_32----醒来,解除
Se_m_33----加固
Se_m_34----天空由阴转晴
Se_m_35----大水冲过
Se_m_36----北风呼号

Se00-----点击声
Se01-----取消
Se02-----装备
Se03-----选人
Se04-----战役开始音效
Se05-----士兵加油
Se06-----沙沙走路声
Se07-----挥武器声
Se08-----走路声
Se09-----开门声
Se10-----树林里行走
Se11-----用物品、吃果子
Se12-----等级上升
Se13-----道具升级、兵种升级
Se14-----拣到物品
Se15-----选取人物或者取消人物
Se16-----人物撤退
Se17-----刮风
Se18-----关闭、打开城门
Se19-----放火
Se20-----鸣罗收兵
Se21-----口哨声
Se22-----呜咽、死去
Se23-----步军走路声
Se24-----骑兵行军
Se25-----炮车行走
Se26-----急速行走
Se27-----走过草地、树林沙沙声
Se28-----沼泽行走声
Se29-----划船声
Se30-----轻格挡声1
Se31-----重格挡声2
Se32-----吹气声(非剑类攻击声)
Se33-----吹醒
Se34-----抽剑声
Se35-----抽打声
Se36-----被石头击中
Se37-----小门开(拉弓声)
Se38-----落石声
Se39-----呼啸声
Se40-----失败、哀乐
Se41-----玎玲声
Se42-----木人释放出来

```·
lometsj commented 2 years ago

0x24 音轨

24 00 09 00 xx xx (音轨序号

0x25 武将进入地点测试

25 00 02 00 zz zz 00 04 xx xx xx xx 00 04 yy yy yy yy (x,y) zz武将序号

26武将进入区域测试

26 00 02 00 zz zz 04 00 xx xx xx xx 04 00 yy yy yy yy 04 00 xx xx xx xx 04 00 yy yy yy yy

27场景

27 00 2d 00 qq qq(类别 0c 00 yy yy 1a 00 ff ff 1c 00 yy yy 15 00 zz zz x y z对应类别的编号

外场景
中国地图 中国地图只有一张所以是ff
内场景
战场地图
lometsj commented 2 years ago

地图头像显示29

29 00 02 00 zz zz 04 00 xx xx xx xx 04 00 yy yy yy yy (x,y)出现zz头像

地图头像移动2a

2a 00 其余同上

2b地图武将消失

2b 00 02 00 xx xx

2c地图文字显示

2c 00 05 00 文字 00 26 00 xx xx (换页 26 00 xx xx (不换行 26 00 xx xx (不等待

武将点击测试2d

2d 00 02 00 xx xx(武将序号

武将相邻测试2e

2e 00 02 00 xx xx 02 00 yy yy x y武将序号 26 00 zz zz (相邻 01 00 可攻击 00 00

lometsj commented 2 years ago

2f 清除人物

2f 00

30 武将出现

30 00 02 00 xx xx 武将序号 04 00 xx xx xx xx 04 00 yy yy yy yy 2b 00 xx xx(方向 北东南西递增 默认ffff 0d 00 xx xx (动作

lometsj commented 2 years ago

分析了0x30个了,简单总结下 曹操传的指令基本都是tv结构即tag value,除了子事件和section没有标识长度的字段,依靠00事件结束来标志终止 cmd_type依序递增 成员type value

武将消失 0x31

31 00 2c 00 xx xx (0单人 1区域 02 00 xx xx(武将序号 04 00 xx4 04 00 yy4 04 00 xx4 04 00 yy4

武将移动0x32

32 00 40 00 xx xx(0charindex 1 武将序号 02 00 xx xx (武将序号 04 00 xx xx xx xx (index 04 00 xx xx xx xx 04 00 yy yy yy yy 2b 00 xx xx(方向 北东南西递增 默认ffff

0x33 武将转向

33 00 02 00 xx xx(武将序号 0d 00 yy yy (动作 2b 00 zz zz (方向

0x34 武将动作

34 00 02 00 xx xx 0d 00 yy yy

0x35 武将形象改变

35 00 02 00 xx xx 13 00 yy yy (形象序号

0x36 武将状态测试

36 00 02 00 xx xx(武将序号 23 00 yy yy (某个属性 04 00 zz zz zz zz(数值 image

24 00 xx xx(运算符 >=, <, =

0x37 钱 剧本跳转 忠奸测试

37 00 28 00 xx xx (测试类型 image 04 00 xx xx xx xx 数值 24 00 xx xx 运算符 image

0x38 武将能力设定

38 00 02 00 xx xx 武将序号 23 00 xx xx 某个属性 34 00 xx xx 运算符 04 00 xx xx xx xx 数值

0x39 武将等级提升

39 00 02 00 xx xx武将序号 04 00 xx xx xx xx 数值

0x3a 钱剧本忠奸设置

3a 00 28 00 xx xx (测试类型 image 34 00 xx xx 运算符 04 00 xx xx xx xx 数值 image

0x3b 武将加入

3b 00 02 00 xx xx 武将序号 0e 00 xx xx 加入还是离开 3e 00 xx xx 加入等级+x

0x3c 武将加入测试

3c 00 02 00 xx xx 武将序号 0e 00 xx xx 加入还是离开 3a 00 xx xx 运算符 image

0x3d 获得武器

3d 00 17 00 xx xx 物品序号 49 00 xx xx 物品等级 26 00 xx xx 是否显示动作 02 00 xx xx武将序号

0x3e 加入装备设定

02 00 xx xx 武将序号 3b 00 xx xx武器序号 0 默认 1没有 2开始序号 49 00 xx xx 武器等级 3c 00 xx xx防具序号 49 00 xx xx防具等级 3d 00 xx xx 辅助等级

lometsj commented 2 years ago

3f回合测试

3f 00 04 00 xx xx xx xx 回合数 24 00 xx xx 运算符 image

行动方测试40

40 00 48 00 xx xx image

41战场人数测试

41 00 03 00 xx xx 战斗方 image 04 00 xx xx 人数 24 00 xx xx 运算符 image 3f 00 xx xx 区域还是整个战场 image 04 00 xx xx xx xx *4 区域坐标

战斗胜利测试42

42 00

战斗胜利测试43

43 00

战斗初始化44

44 00

lometsj commented 2 years ago

45战场全局变量

45 00 26 00 xx xx 未知1 26 00 xx xx 未知2 04 00 xx xx xx xx 回合数 3e 00 xx xx 等级补正 00默认 32 00 xx xx 无用 02 00 xx xx 敌方主将 (ff ff无 32 00 xx xx无用 02 00 xx xx 我方主将 (ff ff 无 47 00 xx xx 天气类别 image 22 00 xx xx 起始天气 image

image

友军出场设定46

46 00

下面乘以20

02 00 xx xx 武将编号 26 00 xx xx 是否隐藏 04 00 xx xx xx xx 横坐标 04 00 xx xx xx xx 纵坐标 2b 00 xx xx 面朝方向 ff ff 默认 3e 00 xx xx 等级补正 45 00 xx xx 兵种级别限制 image 07 00 xx xx ai类别 image

敌军出场设定47

47 00 剩下参考上一条

敌军装备设定48

48 00 02 00 xx xx武将编号 3b 00 xx xx武器编号 49 00 xx xxlv 3c 00 xx xx防具编号 49 00 xx xx lv 3d 00 xx xx 辅助编号

49战斗结束

49 00

4a我军出场强制设定

4a 00 04 00 xx xx xx xx 人数 38 00 xx xx 5 出场武将编号 39 00 xx xx 5 强制不出场武将编号

4b我军出场设定

4b 00 04 00 xx xx xx xx 武将编号 04 00 xx xx xx xx 横坐标 04 00 xx xx xx xx 纵坐标 2b 00 xx xx 方向 26 00 xx xx 隐藏

lometsj commented 2 years ago

4c隐藏武将出现

4c 00 40 00 xx xx 武将编号00 00 战场编号01 00 02 00 xx xx 武将编号 04 00 xx xx xx xx 战场编号

4d 武将状态变更

4d 00 41 00 xx xx image 02 00 xx xx 武将编号 04 00 xx xx xx xx 战场编号 04 00 xx xx xx xx 4 区域 03 00 xx xx image 2f 00 xx xx 属性 image 18 00 xx xx 变化 image 30 00 xx xx 状态 image 04 00 xx xx 2 hp,mp

武将方针变更4e

4e 00 2c 00 xx xx 单人00 00 范围 01 00 02 00 xx xx 武将编号 04 00 xx xx xx xx 4 范围 03 00 xx xx 战斗方 image 07 00 xx xx ai类型 image 02 00 xx xx ai类型下武将编号 04 00 xx xx xx xx 2 坐标

4f战场转向设置

4f 00 02 00 xx xx 2 武将编号 2b 00 xx xx 方向 26 00 xx xx 3 image

战场动作设定50

50 00 02 00 xx xx 武将编号 46 00 xx xx 动作编号 26 00 xx xx *2 image

战场恢复行动权51

51 00

兵种改变 52

52 00 02 00 xx xx武将编号 03 00 xx xx 兵种编号

战场撤退 53

53 00 2c 00 xx xx 单人00 范围 01 02 00 xx xx 武将编号 04 00 xx xx xx xx *4 范围坐标 03 00 xx xx 战斗方 image 26 00 xx xx 是否死亡

战场撤退确认54

54 00

战场复活55

55 00 40 00 xx xx 武将还是战场编号 02 00 xx xx 武将编号 04 00 xx xx xx xx 战场编号 04 00 xx xx xx xx *2 坐标 2b 00 xx xx 方向

天气类别设定

56 00 47 00 xx xx image

当前天气设定

57 00 22 00 xx xx image

58 战场障碍设定

58 00 42 00 xx xx 物体 image 43 00 xx xx 是否显示 44 00 xx xx 地形 04 00 xx xx xx xx 2 坐标 26 00 xx xx 2 未知

战利品

59 00 04 00 xx xx xx xx 额外金钱 17 00 xx xx 49 00 xx xx 物品编号 等级 *3 26 00 xx xx 是否结局

战场操作开始

5a 00

战场高亮区域

5b 00 04 00 xx xx xx xx *4 区域坐标 26 00 xx xx 战斗中是否

战场高亮人物

5c 00 02 00 xx xx 武将编号

回合上限设定

5d 00 34 00 xx xx 运算符 image 04 00 xx xx xx xx 数值

武将不同测试

5e 00 02 00 xx xx *2 武将编号

单挑结束

5f 00

单挑武将出场

60 00 26 00 xx xx 是否我方武将 05 00 xx 00 出场文字 4c 00 xx xx 单挑动作 image

单挑胜负显示

61 00

单挑阵亡

62 00 26 00 xx xx 是否我方武将

单挑对话

63 00 26 00 xx xx 是否我方武将 05 00 xx 00 文字 26 00 xx xx 延时

单挑动作

64 00 26 00 xx xx 是否我方武将 4c 00 xx xx 单挑动作 image

单挑攻击1

65 00 26 00 xx xx 是否我方武将 4d 00 xx xx image 26 00 xx xx 是否致命一击

单挑攻击2

66 00 26 00 xx xx 是否我方武将 4e 00 xx xx image 26 00 xx xx 是否命中

章名

67 00 04 00 xx xx xx xx 章数 05 00 xx 00 章名

单挑开始

68 00 02 00 xx xx *2 武将编号

旁白

69 00 05 00 xx 00

法术

6b 00 04 00 xx xx xx xx *2 坐标 4b 00 xx xx 法术编号 26 00 xx xx 未知