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
dataSeg04
与 dataSeg05
获取 midiKey 参数
dataSeg04
与 dataSeg05
是两个数据段,它们的长度分别为 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
中的数据。
dataSeg13
与 dataSeg14
数据表
dataSeg13
与 dataSeg14
是两个索引表,均为双字节数据
当索引值大于 0x7FFF 时,表示指向 dataSeg14
中的数据,否则指向 dataSeg13
中的数据。
dataSeg15
与 dataSeg16
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;
}
dataSeg16
与 SXGWAVE
对应关系
根据目前逆向的结果,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
(可能会少几个字节)。