Yamaha S-YXG 50 逆向记录

SXGBIN41.TBL 解析

SXGBIN41.TBL 是一个二进制文件,它的结构如下:

name baseAddr length check result comment
header 0x00000000 0x0010 - MU50 4MB V2.0
waveFile 0x00000010 0x000E - SXGWAVE4.TBL
decoded 0x0000001F 0x0001 - used by veg.by, decoded=1, encoded=0
segLength 0x00000020 0x0044 - length list for data segments below
dataSeg00 0x00000064 0x0080 Yes GS drum kit table
dataSeg01 0x000000E4 0x0080 No XG drum kit table
dataSeg02 0x00000164 0x0080 No XG SFX kit table
dataSeg03 0x000001E4 0x0080 Yes GM2 drum kit table
dataSeg04 0x00000264 0x1F00 No XG drum map, double byte
dataSeg05 0x00002164 0x2490 No XG drum default data
dataSeg06 0x000045F4 0x00AE Yes SFX Index Table
dataSeg07 0x000046A2 0x0080 No GS Bank MSB Table
dataSeg08 0x00004722 0x0080 No XG Bank MSB Table
dataSeg09 0x000047A2 0x0080 Yes XG Bank LSB Table
dataSeg10 0x00004822 0x0080 No XG Melody Voice LSB Table
dataSeg11 0x000048A2 0x1800 No GS/GM Index Table, double byte data
dataSeg12 0x000060A2 0x3800 Yes XG Index Table, double byte data
dataSeg13 0x000098A2 0xFB2A No Base pre-voice table, double byte
dataSeg14 0x000193CC 0x5CAE No Extend pre-voice table, double byte
dataSeg15 0x0001F07A 0x01EC Yes Index for dataSeg16, double byte
dataSeg16 0x0001F266 0x4410 No for sxgbin21.tbl, length is 0x44A0

dataSeg00 dataSeg01 dataSeg02 dataSeg03

这四段数据是鼓组表,分别是 GS 鼓组表、XG 鼓组表、XG SXF 鼓组表、GM2 鼓组表。它们的结构仅仅是单字节的偏移量,指向 dataSeg04 中的数据。

例如,dataSeg00 中的数据为:

00000000: 0101 0101 0101 0101 0201 0101 0101 0101  ................
00000010: 0301 0101 0101 0101 0405 0101 0101 0101  ................
00000020: 0601 0101 0101 0101 0701 0101 0101 0101  ................
00000030: 0801 0101 0101 0101 0901 0101 0101 0101  ................
00000040: 0101 0101 0101 0101 0101 0101 0101 0101  ................
00000050: 0101 0101 0101 0101 0101 0101 0101 0101  ................
00000060: 0101 0101 0101 0101 0101 0101 0101 0101  ................
00000070: 0101 0101 0101 0101 0101 0101 0101 010a  ................

01 表示 dataSeg04 中偏移量为 1*0x7F 的数据,即 Roland GS Drum Set Standard
0a 表示 dataSeg04 中偏移量为 10*0x7F 的数据,即 Roland CM64/32L Set

dataSeg04dataSeg05 获取 midiKey 参数

dataSeg04dataSeg05 是两个数据段,它们的长度分别为 0x1F00 和 0x2490。它们的结构如下:

unsigned int16_t dataSeg04[31][128]; // XG drum map,double byte, offset for dataSeg16

// 根据 Yamaha XG Specification 1.26
// 实际长度为 30 字节
struct drumData {
    int8_t pitchCoarse;
    int8_t pitchFine;
    int8_t level;
    int8_t alternateGroup;
    int8_t pan;
    int8_t reverbSend;
    int8_t chorusSend;
    int8_t variationSend;
    int8_t keyAssign; // 0:Single, 1:Multi
    int8_t rcvNoteOff;
    int8_t rcvNoteOn;
    int8_t filterCutOffFreq;
    int8_t filterResonance;
    int8_t egAttack;
    int8_t egDecay1;
    int8_t egDecay2;
    int8_t isSFXSound; // 0: SFX, -1: Drum
    int8_t sfxSoundID;
    int8_t ...
}


struct drumData *dataSeg05[312]; // XG drum default data

然后需要定义一个 drumData 类型的数组,用于存储 midi 键盘各个键的参数

struct drumData midiKey[128];

首先获取一个 drumSetID

const char noteOffset = 13;
// 假设 drumSetID 为 0
int drumSetID = 0;

// 获取 drumSetID 对应的 drumSet
unsigned int16_t *drumSet = dataSeg04[drumSetID];

// keyArgs 用于存储 drumSet 中的参数
MidiKey *keyArgs;

// 获取 drumSet 中的参数
for (char noteKey = 0; noteKey < 79; noteKey++) {
    if (drumSet[noteKey] != 0xFFFF) keyArgs = dataSeg05[noteKey+noteOffset+drumSetID*128];
    else keyArgs = &{0x4040, 0x7F, 0x00, 0x40, 0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x01, 0x40, 0x40, 0x40, 0x40, 0x40};
}

dataSeg04 中的分段

name baseAddr comment
None 0x00000000
Roland GS Drum Set Standard 0x00000100
Roland GS Drum Set Room Set 0x00000200
Roland GS Drum Set Power Set 0x00000300
Roland GS Drum Set Electronic Set 0x00000400
Roland GS Drum Set TR808 Set 0x00000500
Roland GS Drum Set Jazz Set 0x00000600
Roland GS Drum Set Brush Set 0x00000700
Roland GS Drum Set Orchestra Set 0x00000800
Roland GS SFX Set 0x00000900
Roland CM64/32L Set 0x00000A00
Yamaha XG Drum Set Standard 1 0x00000B00
Yamaha XG Drum Set Standard 2 0x00000C00
Yamaha XG Drum Set Room 0x00000D00
Yamaha XG Drum Set Rock 0x00000E00
Yamaha XG Drum Set Electro 0x00000F00
Yamaha XG Drum Set Analog 0x00001000
Yamaha XG Drum Set Jazz 0x00001100
Yamaha XG Drum Set Brush 0x00001200
Yamaha XG Drum Set Classic 0x00001300
Yamaha XG SFX Set 1 0x00001400
Yamaha XG SFX Set 2 0x00001500
GM2 Drum Set Standard 0x00001600
GM2 Drum Set Room 0x00001700
GM2 Drum Set Power 0x00001800
GM2 Drum Set Electronic 0x00001900
GM2 Drum Set TR808 0x00001A00
GM2 Drum Set Jazz 0x00001B00
GM2 Drum Set Brush 0x00001C00
GM2 Drum Set Orchestra 0x00001D00
GM2 SFX Kit 0x00001E00

dataSeg06 解析

dataSeg06 是一个索引表,指向 dataSeg13 中的数据。

dataSeg06 通过解析 dataSeg05 中,sfxSoundID 作为自己的偏移量,得出的双字节数据,指向 dataSeg13 中的数据。

dataSeg13dataSeg14 数据表

dataSeg13dataSeg14 是两个索引表,均为双字节数据

当索引值大于 0x7FFF 时,表示指向 dataSeg14 中的数据,否则指向 dataSeg13 中的数据。

dataSeg15dataSeg16

dataSeg15 是一个双字节偏移索引表,指向 dataSeg16 中的数据。

dataSeg16 是一个结构体,长度 16 字节,它的结构如下:

struct seg16Data {
    int8_t velocity; // 力度,0 是最大值的意思
    int8_t baseKey; // 基准音高
    int8_t field3;
    int8_t field4;
    int8_t sampleNegOffset[2]; // 起始点修正值(负向),高位在前,低位在后
    int8_t field7;
    int8_t loopEnd[2]; // 循环结束点,高位在前,低位在后
    int8_t sampleBaseAddr[3]; // 采样起始地址,高位在前,低位在后

    int8_t inputSampleRate; // 0x80 = 22050
    int8_t outputSampleRate; // 0x00 = 44100
    // keyStart 和 keyEnd 为该声音对应的音域范围
    int8_t keyStart;
    int8_t keyEnd;
}

SXGWAVE4.TBL 解析

SXGWAVE4.TBL 是一个二进制文件,只是存放了一些波形数据,波形数据参数为无符号 8 位整数,采样率 22050Hz,单声道。

原版的文件经过编码,需要以下步骤才能解码:

/*
 * 解码 SXGWAVE4.TBL 文件
 *
 * 输入:
 *  - sxgWaveTblData:SXGWAVE4.TBL 文件数据
 *  - length:SXGWAVE4.TBL 文件长度
 *
 * 输出:
 *  - 解码后的波形数据
 *
 * 返回值:
 *  - 是否成功,1 表示成功,0 表示失败
 *
 * 原理详解:
 * - 动态密钥机制:初始密钥 93 (0x5D),每处理一个字节后取反(0xA2),形成交替使用的双密钥
 * - 三重异或加密:
 *   - 混合字节索引 + 动态密钥 + 原始数据
 *   - 左移4位后形成16位中间值
 * - 数据重组:将16位值拆分为高低字节组合后,再与 0x5C 进行最终异或
 * - 原地改写:直接修改输入缓冲区的内容,实现内存高效处理
 *
 * 注意事项:
 * - 输入缓冲区必须足够大,以容纳解码后的波形数据
 * - 输入缓冲区的内容将被直接修改,确保在使用之前备份原始数据
 *
 * 吐槽:
 * - 这个函数的实现非常巧妙,但也非常难以理解
 * - 它的实现方式非常类似于 RC4 加密算法
 */
int decodeSxgWaveTblData(_BYTE *sxgWaveTblData, int length)
{
    char key = 0x5D; // 初始密钥
    int result = 0;

    // 遍历每个字节
    for (int i = 0; i < length; i++) {
        uint16_t middleValue = i & 0xFFFF; // 当前字节索引
        uint16_t decodedData = (middleValue ^ key ^ sxgWaveTblData[i]) << 4; // 三重异或后左移4位
        
        result = decodedData & 0xFF; // 取低8位作为中间结果
        // 组合高低字节后二次异或,写入原始内存
        sxgWaveTblData[i] = (decodedData | (decodedData >> 8)) ^ 0x5C;
        
        key = ~key; // 密钥取反(0x5D <-> 0xA2)
    }

    return result;
}

dataSeg16SXGWAVE 对应关系

根据目前逆向的结果,dataSeg16 可能存在某种算法去对应 SXGWAVE 文件中的波形数据。

目前已知在 dataSeg16 中 snare roll 这个声音的数据如下

00000110: 002c 3000 1089 0018 cc00 5e76 8000 1d1d  .,0.......^v.... # 结尾的 1d 1d 是指代音域范围,即从 29-29

SXGWAVE 中,snare roll 这个声音起始地址为 0x00004DED,长度为 0x2957

起始点计算

起始点计算方法如下:

int sampleBaseAddr = dataSeg16->sampleBaseAddr[0] << 16 | dataSeg16->sampleBaseAddr[1] << 8 | dataSeg16->sampleBaseAddr[2];
int sampleNegOffset = dataSeg16->sampleNegOffset[0] << 8 | dataSeg16->sampleNegOffset[1];
int startAddr = sampleBaseAddr - sampleNegOffset;

循环点计算

循环点计算方法如下:

int loopEnd = dataSeg16->loopEnd[0] << 8 | dataSeg16->loopEnd[1];
int loopOffset = sampleBaseAddr + loopEnd;

采样长度

循环点是当采样播放到这里时,折返到 sampleBaseAddr 继续播放。

因此,采样长度为 loopOffset - startAddr (可能会少几个字节)。

1 Like

还有一部分内容在这里 https://gist.github.com/senseab/545ef2745a2c8cfbfd60540dd48ca343

目前以我的精力,暂时到这里了,后续如果有时间我还会继续逆向,我的目的是搞清楚从 sxgbin 到 sxgwave 是怎样工作的,为后续重写 yamaha midi 音源做准备,项目名称倒是想好了,就叫 Madaha (

顺带在这里呼叫一下 KingSky 大佬 XD

1 Like

不明觉厉,膜技术佬

帮您叫一下 @华北kingsky
不过他好久都没来过了……

kingsky 应该很忙吧……我现在就只有两个大问题需要解决了:

  1. 我还没有核实 dataSeg13 dataSeg14dataSeg15 的关系,以及各种 drum 与这些数据段的关系
  2. dataSeg16 的数据是怎么对应到 SXGWAVE

事实上芬兰程序员 Evgeny Vrublevsky (aka veg.by) 也尝试了逆向,我也是在他的基础上做了进一步的尝试,不过不得不说,Yamaha 的合成软件的代码也确实够复杂,到目前还有很多数据结构我还没能完整分析出来

支持,顺便看看能不能逆向S-YXG2011EE
https://ftp.mcmodule.org/DTM/VSTi%20plugin/S-YXG2011EE.zip

1 Like

我是打算后续逆向 2006-LE 的,不过我也是在 deepseek 的加持下,才逆向出现在这个进度,后面有时间我会看看把这些逆向试试

简单看了一下,这里面的tbl 加密方式跟 sxg50 很相似

主要是这个版本编译出来就是128复音的,S-YXG2006LE也才32复音

s-yxg 50 vsti 版本是可以调复音数的,能到 128

帖子更新了一下,告诉大家一个秘密,其实 S-YXG 50 的 2MB 版本波表库的音质比 4MB 要好

1 Like

This post was flagged by the community and is temporarily hidden.

https://veg.by/ru/projects/syxg50/comment-page-3/#comment-50325

根据这里的讨论,他们认为 2MB 波表里面鼓的细节比较多一点(看不懂请找各路AI)

事实上已经有人尝试改波表的采样率和其他一些参数了,我现在有点进行不下去了,这个软件并不是特别容易逆向的那种,有很多数据结构我还没有搞明白具体的含义

2 Likes

This post was flagged by the community and is temporarily hidden.

1 Like

[本篇文章没有涉及到技术性讨论
杂谈?<大概是>]

雅马哈的大刀 取舍给错了
不过也都不清楚具体是哪点不好

老感觉这句话有耳熟

下面这位回复的就挺中肯


(毕竟还是一种主观感受,每个人都觉得不同)

讲实话,就像以前在摸主机的时候
总是争论 Gba和sfc (那就扯远了)
<总之没有可比性,不管是技术上还是设备定位>


雅马哈的情况,如我提出主观的话应该是:
取舍不当就成这样了
(不清楚这里的rom<或sample>是否有封装大小的限制)


总之主观论点就是猜的

有上述情况就是取舍错误
没有的话雅马哈的大刀接英特尔 (doge)


https://veg.by/ru/projects/syxg50/comment-page-3/#comment-50325

@DecemberLens 这帖子讨论的东西挺有趣的 (提到sample与质量)

我看看这个帖 (虽然可能不会对您现在正在进行的工作,起到什么变化吧)

chirs.

25/03/27 17:50 utc+8 首评

17:53 二改

嗯,我昨晚联系了 veg,然后他就给我指路到这个俄语的评论区了,事实上已经有大神逆向成功了 S-YXG 50,我也在研究他们评论区的内容,现在的问题是我需要把这些数据表以及每个字段的作用完全搞明白,不过一路逆向到现在,我觉得其实只要能兼容 XG 标准,我也完全没有必要非得用 Yamaha 的采样……可能我比较怀旧吧
总之逆向还需要一些时间,接下来我需要把这些毛子说了啥完全搞明白先

1 Like


也好理解,2006LE好像没有设置可调
(连面板都找不到)

见过有人套着这个用


(不过我没用过xgworks不清楚干嘛的)

25/03/27 18:40

2006LE 其实是给 Midradio Player 专用的音源,虽然它是 VSTi 但是本来就没考虑给其他宿主用的,自然也就不需要什么设置了。
xgworks 是 yamaha 自己的 daw,支持 XG 标准的,能用 SysEX 去调某些隐藏参数(比如打击乐器的频率参数什么的)

1 Like