Yamaha S-YXG 50 逆向记录

真的泪目了,现在还有人在对古老的S-YXG50和S-YXG2006LE音源进行逆向…
必须狠狠支持了
个人觉得S-YXG2006LE那么好的高音质采样,却全败在无法启用的S-YXG50 GUI,败在被砍成XGlite和32个Polyphony上,实在太可惜了。
有朝一日希望看到2006LE也能被破解支持128复音和Full XG!
看了Veg.by大佬制作的S-YXG50 VSTi中的Readme,可以把S-YXG50的sxgwave4.tbl文件删掉替换为自己准备的波表文件,考虑到S-YXG50有GUI并且可以调复音数,就想试试能不能另辟蹊径把S-YXG2006LE的TBL波表移植给S-YXG50来破除复音限制,毫无疑问因为波表加密的缘故没法行得通,遂见此贴!
跪拜大佬们!

3 Likes

话说这个是做什么用的音源呢?拿回去听了一下貌似就只有正弦波的采样,并且GM1 Reset后支持映射为正弦波的乐器反而比XG更多就很奇怪呢(
用来叠加多个正弦波来合成人声或许不错

那狠狠支持用爱发电的大佬了!
很希望有一天S-YXG2006LE采样这么好的音源也能用上Full XG和128个复音…

那个不全,缺少采样文件

1 Like


最近闲的蛋疼,逆向了一下SCVA

3 Likes

别的不说,S-YXG50/70/100的原始2MB波表,String Ensemble 1音色听起来要比4MB波表好多了,很高昂的感觉。至少在我的音乐审美来看是这样的,哈哈 :wink:

Codex对三个文件做了第一轮静态分析,没有运行 DLL。

结果挺有料:

  • SGP2.DLL32 位 x86 VSTi DLL,导出函数只有 main
  • DLL 内部明确包含:
    • sxgbnw6l.tbl
    • sxgdat6l.tbl
    • YAMAHA SoftSynthesizer S-YXG2006LE
  • 它通过 GetModuleFileNameA 找自身路径,再用 CreateFileA / GetFileSize / ReadFile 把两个 .tbl 整个读入内存。
  • sxgbnw6l.tbl 不是波形本体,更像 索引/参数/映射表。开头是:
    • UTG VPRM 06 07 28 15 28
    • 后面有一组 offset table。
  • sxgdat6l.tbl 才更像大数据/波形数据,但它不是裸 WAV/PCM,原始内容高熵,DLL 里有解码逻辑。

Codex找到的关键代码位置:

SGP2.DLL+0x2DD0  负责加载两个 tbl
SGP2.DLL+0x2D60  复制字符串 sxgbnw6l.tbl
SGP2.DLL+0x2D80  复制字符串 sxgdat6l.tbl
SGP2.DLL+0x4C830  通用“读取整个文件”函数
SGP2.DLL+0x2EF0  sxgdat6l.tbl 解码例程
SGP2.DLL+0x4C900  解码 helper

SGP2.DLL+0x2EF0 里面能看到一个明显的处理:先调用 +0x4C900 做第一阶段变换,然后对数据做类似“每两个字节交换 + XOR 0x5A”的第二阶段处理。解码后仍然不是 RIFF/WAVE,所以后面很可能还有专有采样压缩、ADPCM 或 Yamaha 自己的波形编码格式。

另外,外部资料也能对应上:SGP2.DLL 确实被社区记录为 Yamaha MidRadio 7 里的 S-YXG2006LE VSTi,使用 sxgbnw6l.tbl / sxgdat6l.tbl 这组新 wavetable。参考:VEG.BYXG-Central

下一步最有价值的是用 Ghidrax32dbg 深入 SGP2.DLL+0x2EF0+0x4C900 和读取后的数据结构,把 sxgbnw6l.tbl 里的 20 字节表项、6048 个指针表项和 sxgdat6l.tbl 的偏移关系对应起来。这样才能进一步判断它到底是 PCM、ADPCM、循环采样,还是更复杂的 Yamaha AWM2 风格数据。

3 Likes

感谢 @gaozhe3321 带来新的逆向进展。

这种文件有反编译过的样本么,或者说具体结构可以形容下

闲话本人就折叠起来了,想看就展开吧

提到这个其实联想到的是Motif的乐器预设或波表(如wXa,wXv;只是举个例子实际上有更多变种)

https://archive.org/details/motif-es-sound-library
以上这张CD是购买Motif附赠的音色库,如果跑题的话可能就没什么研究价值了

雅马哈的其他私有格式Fmj_Awave说是整理吧(但它软件能读数据,所以基本是转了)

就看看得了


但也并非一无是处,至少得知了雅马哈习惯性的把乐器预设(voice)和样本(waveformat)分开放这个事实(但似乎也早说过了?)

https://www.fmjsoft.com/awavestudio.html#formats
(看一眼这几个格式的介绍也无妨?)

2605082330

你太牛了!

事实上 2006LE 也采取了跟 YXG-50 相似的索引+波表的方式

我事实上还发现,其实很多音色是共用了同一个采样,但是在调制方式上有区别。也就是说我们目前只是知道了这些数据是如何排布的,但是更复杂的调制算法以及效果器/SysEx这些功能是如何实现的,还有待进一步分析。

整个程序的结构也需要进一步去分析,但这可能需要动态调试了,我能力和时间都有限,只能暂时搁置了

1 Like

我找了一个帮手,用AI来尝试还原代码,(烧token的日子开始了……)

1 Like

大佬你也用 OpenCode? :crazy_face:

也许可以试试本地部署大模型(如果可能的话,虽然不太可能)

也许可以试试本地部署大模型(如果可能的话,虽然不太可能)

我没有很好的显卡……也没有¥

烧了一晚上,大概¥15,进度如上图

1 Like

今天的逆向进度

1 Like

花钱充了小米的 mimo token plan
现在烧不完,我打算把 2006le 也逆向掉

另外,如果有想使用mimo的,我这里有邀请码

1 Like

S-YXG2006LE 数据文件结构报告

该文档由AI生成,可能存在不准确的部分

1. 概述

S-YXG2006LE 合成器使用两个数据文件:

文件 大小 加密 用途
sxgbnw6l.tbl 716,608 字节 否 (明文) 参数表:音色定义、波形偏移、力度曲线
sxgdat6l.tbl 11,723,868 字节 是 (Stage 1) 波形数据:16-bit PCM 采样

2. sxgbnw6l.tbl 参数表结构

2.1 文件头部 (0x00-0x3F, 64字节)

偏移    大小    值              说明
0x00    28B     "UTG VPRM 06 07 28 15 28\0"   魔数 + 版本号
0x1C    uint32  0x00004040      Section A 偏移 (Voice 索引表)
0x20    uint32  0x00009740      Section B 偏移 (波形偏移表)
0x24    uint32  0x00032EE8      Section C 偏移 (音色参数数据)
0x28    uint32  0x0007B88C      Section D 偏移 (力度/音量曲线)
0x2C    uint32  0x0007BA8C      Section E 偏移 (鼓组/音符参数)
0x30    uint32  0x0009CE8C      Section F 偏移 (每音符 Voice 映射)
0x34    uint32  0xFFFFFFFF      保留
0x38    uint32  0x000A69F0      Section G 偏移 (鼓键参数)
0x3C    uint32  0xFFFFFFFF      保留

2.2 段布局总览

偏移 大小 用途
Header 0x000000-0x00003F 64 B 文件头
Lookup Table 0x000040-0x00013F 256 B MIDI Program → Voice 索引映射
Sparse Region 0x000140-0x00403F 16 KB 填充 (0xFF)
Section A 0x004040-0x00973F 22 KB Voice 索引表
Section B 0x009740-0x032EE7 170 KB 波形偏移表 (42,474 个 uint32)
Section C 0x032EE8-0x07B88B 297 KB 音色参数数据 (6,048 条记录)
Section D 0x07B88C-0x07BA8B 512 B 力度曲线 (4×128 字节)
Section E 0x07BA8C-0x09CE8B 136 KB 鼓组/音符参数
Section F 0x09CE8C-0x0A69EF 40 KB 每音符 Voice 映射
Section G 0x0A69F0-0x0AEF3F 34 KB 鼓键参数 (1,422×24 字节)

3. 各段详细结构

3.1 Lookup Table (0x40-0x13F)

128 字节,每个字节对应一个 MIDI Program (0-127),值为内部 Voice 索引。

  • 0xFF = 未映射
  • 0x00-0x3D = 有效 Voice 索引

已映射的 MIDI Programs (59个):

0, 1, 3, 6, 8, 12, 14, 16-20, 24-25, 27-28, 32-38, 40-43, 45,
64-72, 96-101, 112-127

映射示例:

MIDI Program Voice Index 说明
0 0 Acoustic Grand Piano
1 1 Bright Acoustic Piano
24 12 Acoustic Guitar (nylon)
40 23 Violin

3.2 Section B: 波形偏移表 (0x9740)

42,474 个 uint32 值,指向 sxgdat6l.tbl 中的波形数据。

结构:

偏移 = Section B 起始 + 索引 * 4
波形大小 = 偏移[索引+1] - 偏移[索引]

统计:

  • 有效偏移 (< 文件大小): 21,057 个
  • 无效偏移 (0xFFFFFFFF): 9,512 个
  • 波形大小范围: 44-440 字节 (22-220 采样)

波形示例:

索引 偏移 大小 采样数
0 0x00A308 264 132
1 0x00A410 152 76
2 0x00A4A8 408 204

3.3 Section C: 音色参数 (0x32EE8)

Section C 开头是偏移表,每个条目指向一个音色定义记录。

音色定义记录大小:

大小 元素数 说明
28 字节 1 单元素音色
60 字节 2 双元素音色
108 字节 4 四元素音色
140 字节 5 五元素音色
176 字节 6 六元素音色

VoiceElement 结构 (28字节):

偏移    大小    说明
0x00    4B      标志 (通常为 0)
0x04    1B      音高范围低 (通常 0)
0x05    1B      音高范围高 (通常 127)
0x06    1B      波形索引 (指向 Section B)
0x07    1B      元素标志
0x08    1B      力度范围低 (通常 0)
0x09    1B      力度范围高 (通常 127)
0x0A    1B      粗调 (有符号)
0x0B    1B      微调 (有符号)
0x0C    1B      电平 (128 = 最大)
0x0D    3B      保留
0x10    1B      声像 (128 = 中央)
0x11    3B      保留
0x14    1B      滤波器截止频率 (128 = 最大)
0x15    3B      保留
0x18    1B      滤波器共振 (128 = 最大)
0x19    3B      保留

音色参数示例 (Program 0, Piano):

  • Voice Index: 0
  • 记录大小: 176 字节 (6 元素)
  • 元素波形索引: [132, 127, 1, 0, 255, 37]

3.4 Section D: 力度曲线 (0x7B88C)

4 条曲线,每条 128 字节,将 MIDI 力度 (0-127) 映射到输出值 (0-127)。

曲线 vel=0 vel=32 vel=64 vel=96 vel=127 特点
0 0 32 72 114 126 标准
1 0 26 60 98 126 柔和
2 0 26 64 104 126 中等
3 0 32 73 115 127 线性

3.5 Section G: 鼓键参数 (0xA69F0)

1,422 个记录,每个 24 字节。

默认记录:

40 40 7F 00 40 00 00 7F 00 01 01 40 40 40 40 40 40 40 0C 36 00 40 40 40

字段解释:

偏移 大小 默认值 说明
0x00 1B 0x40 (64) 电平
0x01 1B 0x40 (64) 声像
0x02 1B 0x7F (127) 编组
0x03 1B 0x00 标志
0x04 1B 0x40 (64) 音调
0x07 1B 0x7F (127) 衰减
0x08 1B 0x00 混响发送
0x09 1B 0x01 合唱发送
0x0B 1B 0x40 (64) 滤波器截止

4. sxgdat6l.tbl 波形数据

4.1 加密方式

波形数据使用 Stage 1 (nibble-swap XOR) 加密。

解密算法 (地址 0x1004c900):

void decrypt(uint8_t* data, uint32_t size) {
    uint8_t key = 0x5B;      // 初始密钥
    uint8_t counter = 0;     // 位置计数器

    for (uint32_t i = 0; i < size; i++) {
        // 1. XOR with key and counter
        uint8_t val = data[i] ^ key ^ counter;

        // 2. Nibble swap (高4位 ↔ 低4位)
        val = ((val >> 4) & 0x0F) | ((val << 4) & 0xF0);

        data[i] = val;

        // 3. 更新密钥和计数器
        counter++;
        key = ~key;  // 0x5B ↔ 0xA4
    }
}

密钥演化:

  • 字节 0: key=0x5B, counter=0
  • 字节 1: key=0xA4, counter=1
  • 字节 2: key=0x5B, counter=2
  • 字节 3: key=0xA4, counter=3

4.2 DLL 中的解密流程

LoadDataFiles (0x10002dd0)
    │
    ├── OpenAndReadFile("sxgbnw6l.tbl")  → 不解密 (明文)
    │
    ├── OpenAndReadFile("sxgdat6l.tbl")
    │
    └── DecryptData(sxgdat6l_data, size)  (0x10002ef0)
            │
            └── DecryptNibbleSwap(data, size, &key)  (0x1004c900)

注意: sxgbnw6l.tbl 是明文,不需要解密。

4.3 解密后的数据格式

解密后的数据是 16-bit 小端 PCM 音频

验证结果 (前 10 个波形):

索引 偏移 大小 前 5 个采样
0 0x00A308 264 [5905, 17423, 8720, 3605, 287]
1 0x00A410 152 [7863, 16049, -4179, -10323, 30896]
2 0x00A4A8 408 [-18141, -2260, -1233, -29651, -28629]
3 0x00A640 296 [-2279, -31209, 24345, -13536, 26156]
4 0x00A768 184 [-22035, -23566, -22280, 20991, 13063]

采样特征:

  • 范围: ±32000 (16-bit)
  • 采样率: 44100 Hz (推测)
  • 格式: 单声道

5. 数据对应关系

5.1 音色加载流程

MIDI Program (0-127)
       │
       ▼
Lookup Table (0x40-0x13F)
       │
       ▼
Voice Index (0-6047)
       │
       ▼
Section C 偏移表
       │
       ▼
音色定义记录 (28-176 字节)
       │
       ├── 元素 0: VoiceElement (28 字节)
       │           └── 波形索引 (byte 6)
       │                    │
       │                    ▼
       │           Section B 偏移表
       │                    │
       │                    ▼
       │           sxgdat6l.tbl 波形数据
       │
       ├── 元素 1: VoiceElement (28 字节)
       │           └── ...
       └── ...

5.2 MIDI 事件处理流程

Note On (channel, note, velocity)
       │
       ├── 通道 9? → 鼓组模式 (Section G)
       │
       └── 其他通道 → 查找音色参数
              │
              ├── 应用力度曲线 (Section D)
              │
              ├── 查找 VoiceElement
              │     ├── 检查音高范围
              │     └── 检查力度范围
              │
              ├── 获取波形数据 (Section B → sxgdat6l.tbl)
              │
              └── 设置合成器参数
                    ├── 频率 (note → Hz)
                    ├── 振幅 (velocity → 0.0-1.0)
                    ├── 滤波器 (cutoff, resonance)
                    └── 包络 (ADSR)

5.3 全局变量地址

地址 名称 说明
0x1009e6cc g_refCount 数据加载引用计数
0x1009e6d0 g_bnwData sxgbnw6l.tbl 数据指针
0x1009e6d4 g_datData sxgdat6l.tbl 数据指针
0x1009b2ec str_sxgbnw6l 字符串 “sxgbnw6l.tbl”
0x1009b2fc str_sxgdat6l 字符串 “sxgdat6l.tbl”

6. 关键函数地址

地址 函数 说明
0x10002dd0 LoadDataFiles 加载两个 .tbl 文件
0x10002ef0 DecryptData 两阶段解密 (实际只需 Stage 1)
0x1004c900 DecryptNibbleSwap Stage 1: nibble-swap XOR
0x10002d60 CopySxgbnw6lPath 复制 “sxgbnw6l.tbl” 字符串
0x10002d80 CopySxgdat6lPath 复制 “sxgdat6l.tbl” 字符串
0x10002db0 ValidateData 验证文件路径
0x1004c830 OpenAndReadFile 打开并读取文件
0x100030a0 SynthInit 合成器初始化
0x10003ec0 VoiceDispatcher 音色参数分发器
0x10005b38 RenderVoice 读取波形索引 [esi+6]

7. 未解密问题

7.1 Stage 2 解密

DLL 中的 DecryptData 函数包含两个阶段:

  1. Stage 1: Nibble-swap XOR (已验证有效)
  2. Stage 2: Byte-pair swap + XOR 0x5A

经验证,sxgdat6l.tbl 只需 Stage 1 即可得到有效 PCM 数据。Stage 2 可能用于其他文件或已被废弃。

7.2 sxgbnw6l.tbl 加密

DLL 代码显示 DecryptData 被调用两次 (对两个文件都解密),但 sxgbnw6l.tbl 是明文。可能的原因:

  1. sxgbnw6l.tbl 的数据部分 (偏移 0x40 之后) 需要解密
  2. 解密函数对明文数据是幂等的
  3. 代码被修改或版本不一致

8. 附录

8.1 完整 Lookup Table 映射

Program 0  → Voice 0    (Piano)
Program 1  → Voice 1    (E.Piano)
Program 3  → Voice 2    (E.Piano 2)
Program 6  → Voice 3    (Harpsichord)
Program 8  → Voice 4    (Clavinet)
Program 12 → Voice 5    (Celesta)
Program 14 → Voice 6    (Vibraphone)
Program 16 → Voice 7    (Organ)
Program 17 → Voice 8    (Organ)
Program 18 → Voice 9    (Organ)
Program 19 → Voice 10   (Church Organ)
Program 20 → Voice 11   (Reed Organ)
Program 24 → Voice 12   (Guitar)
Program 25 → Voice 13   (Guitar)
Program 27 → Voice 14   (Guitar)
Program 28 → Voice 15   (Guitar)
Program 32 → Voice 16   (Bass)
Program 33 → Voice 17   (Bass)
Program 34 → Voice 18   (Bass)
Program 35 → Voice 19   (Bass)
Program 36 → Voice 20   (Bass)
Program 37 → Voice 21   (Bass)
Program 38 → Voice 22   (Bass)
Program 40 → Voice 23   (Strings)
Program 41 → Voice 24   (Strings)
Program 42 → Voice 25   (Strings)
Program 43 → Voice 26   (Strings)
Program 45 → Voice 27   (Strings)
Program 64 → Voice 28   (Brass)
Program 65 → Voice 29   (Brass)
Program 66 → Voice 30   (Brass)
Program 67 → Voice 31   (Brass)
Program 68 → Voice 32   (Brass)
Program 69 → Voice 33   (Brass)
Program 70 → Voice 34   (Brass)
Program 71 → Voice 35   (Brass)
Program 72 → Voice 36   (Brass)
Program 96 → Voice 37   (Synth)
Program 97 → Voice 38   (Synth)
Program 98 → Voice 39   (Synth)
Program 99 → Voice 40   (Synth)
Program 100 → Voice 41  (Synth)
Program 101 → Voice 42  (Synth)
Program 112 → Voice 43  (Percussion)
Program 113 → Voice 44  (Percussion)
Program 114 → Voice 45  (Percussion)
Program 115 → Voice 46  (Percussion)
Program 116 → Voice 47  (Percussion)
Program 117 → Voice 48  (Percussion)
Program 118 → Voice 49  (Percussion)
Program 119 → Voice 50  (Percussion)
Program 120 → Voice 51  (Percussion)
Program 121 → Voice 52  (Percussion)
Program 122 → Voice 53  (Percussion)
Program 123 → Voice 54  (Percussion)
Program 124 → Voice 55  (Percussion)
Program 125 → Voice 56  (Percussion)
Program 126 → Voice 57  (Percussion)
Program 127 → Voice 58  (Percussion)
2 Likes

我这边跑出来的结果,好像是不需要做第二阶段的 字节交换+XOR 0x5A

Update:如果不做第二阶段,能听出乐器的声音,但杂音很重,所以还是要考虑第二阶段

1 Like

我觉得大概率还是 PCM,你可以把解密后的数据文件用 音频编辑器打开看一下,如果你用 Linux,也可以用 aplay 命令播一下听听

1 Like

用AI做了几天逆向,我的感觉是,这玩意没有预想中那么智能
AI很会偷懒,有的时候跟智障一样,总之需要大量调教才可以

2 Likes