音乐通(MITONE)小提琴陪练机逆向分析(附自己添加乐谱的方法)
最近在学小提琴,因为平时是自己练琴,虽然有老师教,但是上的网课,老师没法实时在练琴的时候进行指导。小提琴和吉他钢琴不一样,既不像吉他那样很明确的分为若干个品格,也不想钢琴那样每个音有固定的按键。这两种乐器的音准都是明确的,自己练习时音准是否正确可以有很直观的反馈。小提琴就不一样了,小提琴属于音阶连续变化的乐器,因此不会有很明确的对每个音的标识,所以要了解自己演奏时的音准是不容易的。因此,老师推荐了我一款小提琴的陪练机器,通过拾音器加载琴码上,实时采集演奏的声音用算法判断音准,能够有一个实时的反馈,防止越练越错。这的确是个好东西啊,所以我也就毫不犹豫地下单买了,就是这玩意:

一看价格,还不便宜,不过为了早日实现自己演奏《告白之夜》的梦想,还是买吧,从此开始了自己的练琴生涯,其实还是挺好用的,可以实时评分,判断音准,另外还有指位提示的功能,不过这个功能对于我来说并不需要,所以我不太用,机器和实际使用效果如下图:


就这样练习了两个月左右吧,有一天我突然发现里面有些曲谱和我教材里的不一致,影响我练习,虽然以机器里的乐谱为准问题也不大,但是作为一个追求完美的人来说,非得给他折腾好不可。为此我去专门问了卖家,结果一问三不知,不是说好终身免费升级的吗,结果竟然回答我说教材常用的都有不需要升级?Are you kidding me?软磨硬泡了半天,终于说出了真相,卖家自己也不会升级,给了我一个厂商的客服QQ号,让我去直接问问厂商。问就问吧,我就去加了厂商的客服问了一下。回答说是可以帮加乐谱,但是就是需要我把乐谱发给他,他加好发我乐谱文件我放到SD卡插到机器上自己升级。我问他们是怎么加乐谱的,能不能自己加,回答说他们有专门的软件,但是不能给我。这。。。。。好吧,我咬咬牙让他帮我修改了一首有错误的曲谱,说是两三天弄好,结果后面直接不理我了。都什么鬼,这服务态度。
算了,求人不如求自己,我还是自己来分析吧,于是就有了这样一篇文章,那么,既然要分析的话,首先需要获得固件。所以第一步——拆!拆开以后,我震惊了,这TM什么玩意,还带飞线的,量产产品,也太开玩笑了吧:

从上图中,我们可以看出,板子上留了一个调试串口和一个USB Header,推测有可能这是一个USB Device,先不管他,先用调试串口连上去看看,发现是Allwinner的方案CPU单核心,32位(推测可能是Allwinner A10),跑的是Android系统:


一开始进去发现没有shell?好奇怪,然后后来发现其实是可以执行命令的:

这也太难用了吧,尝试执行/system/bin/sh,这下顺眼多了:

果然是Android无疑了,那么推测之前留下的那个USB Header应该是USB Device调试接口,也许可以adb呢?那么就试试吧,于是我就先焊一个USB接头上去:

然后用USB对公线连接电脑,顺便把显示器一起接上,方便调试,如下图,线比较多,有点难受:

然而,USB线连上电脑没反应,难道是没开开发者模式吗?尝试以下用命令打开adb服务:
echo 0 > /sys/class/android_usb/android0/enable echo 18da1 > /sys/class/android_usb/android0/idVendor echo D002 > /sys/class/android_usb/android0/idProduct echo adb > /sys/class/android_usb/android0/functions echo 1 > /sys/class/android_usb/android0/enable start adbd
执行以上命令以后,就能够在电脑中出现一个adb设备了,然后,这样提取固件就方便了,在adb shell里稍微找了一圈,最终发现在/sdcard中有一个MITONE目录,先不管他吧,对于安卓,我们直接把/system/app目录和/sdcard整个给adb pull出来到电脑上再看:

最终发现,原来教材在/sdcard/MITONE/lev目录下面:

然而,这些书都是所谓的.msb扩展名,网上查了也并没有找到相应的已知格式,看来只能自己分析二进制文件了,先用010Editor打开看看吧:

看样子是私有格式,那么只能分析设备中的程序本身了,看看程序本身是如何解析书本的。既然是安卓系统,那么一定是通过app读取书本进行解析的,通过分析进程列表,最后确定是musictsu2.apk这个app,拖到JEB里面分析看看,在Java层看了一圈,没什么有用的,这个程序有一个二进制so库,名字是libMultakGameLib.so,看来关键代码应该在so库里。然而这个库是C++写的,分析起来极其恶心。不过,作为一个CTF老赛棍,怎么能怕恶心的,有比赛题目恶心吗,还是静下心来看吧。

经过分析加上用android_server二进制调试一番后,终于搞明白了msb格式,同时从so库中的网址信息来看,发现了一些有意思的事情,原来这个学习机程序有好几个马甲,比如“练琴达人”,英文名叫MusicScore。这个app同时用在我这个机器上和另一个平板钢琴学习机的产品上。并且,这家公司叫做上海渐华科技发展有限公司,英文名叫MULTAK TECHNOLOGY DEVELOPMENT。是一家小公司,主要做录音设备,官网是: http://www.multak.com/ 。另外,还发现一个有意思的网站,就是音乐通品牌的主页: http://m2.51kara.com/1503/ ,上面可以下载到课程书本,可以看到都是msb格式的,而且这个网站做得巨烂,似乎已经被废弃了。
好了八卦完了,我们说书本的结论吧,大概就是标签+长度+内容这样的分布,比如MSBK就是书本的Magic头部,后面跟4个字节的长度,然后COVE就是Cover,封面的意思,后面会跟长度+封面PNG图片数据,是的,你没看错,这NB的格式直接把Png图片打包在书本里面。为此,我写了一个010Editor模板来解析书本格式(所以MSB是MusicScoreBook的缩写意思?):
//------------------------------------------------
//--- 010 Editor v9.0.1 Binary Template
//
// File: MSBK.bt
// Authors: Jarvis
// Version:
// Purpose:
// Category:
// File Mask:
// ID Bytes:
// History:
//------------------------------------------------
LittleEndian();
typedef struct Chapter {
char lbl_mscp[4];
uint32 chap_size;
char lbl_type[4];
uint32 chap_type;
char lbl_diff[4];
uint32 chap_diff;
char lbl_enco[4];
uint32 chap_enco;
char lbl_tnnt[4];
char chap_tnnt[4];
char lbl_inon[4];
uint32 inon_size;
char chap_inon[inon_size];
char lbl_title[4];
uint32 chap_title_size;
char chap_title[chap_title_size];
char lbl_author[4];
uint32 author_size;
char chap_author[author_size];
char lbl_midi[4];
uint32 midi_size;
char chap_midi[midi_size];
char lbl_ovtr[4]; //may be overture format?
uint32 ovtr_size;
char chap_ovtr[ovtr_size];
} BookChap <optimize=false>;
struct MusicScoreBook
{
struct BookHeader
{
char Magic[4];
uint32 book_size;
char lbl_type[4];
uint32 book_type;
char lbl_title[4];
uint32 title_size;
char book_title[title_size];
char lbl_author[4];
uint32 author_size;
char book_author[author_size];
char lbl_revision[4];
uint32 rev_size;
char book_rev[rev_size];
char lbl_http[4]; //maybe name on the webpage
uint32 http_size;
char book_httptitle[http_size];
char lbl_simpname[4];
uint32 simpname_size;
char book_simp[simpname_size];
char lbl_level[4];
uint32 level_size;
char book_level[level_size];
char lbl_cover[4];
uint32 cover_size;
char book_cover[cover_size];
char lbl_chap[4];
uint32 total_chapters;
uint32 chap_offset[total_chapters];
} book_header;
BookChap book_chapters[book_header.total_chapters];
} file;
解析结果如下:

其中书本中每个章节中,最重要的信息就是MIDI数据和OVTR数据,其中MIDI数据不用解释,就是用来播放的MIDI声音数据,其中OVTR这个很令人困惑,并且这两部分都似乎是随机数值,像是压缩或者加密的。那么我们继续仔细分析so库,终于发现了解析书本的代码中,对这两部分的处理,竟然这两部分都是加密的!关键代码如下:
int __fastcall sub_5CA4D058(int result, int a2, int a3)
{
signed int v3; // r3
int v4; // r12
signed int v5; // r12
int v6; // r5
int i; // r4
int v8; // r4
int v9; // r3
v3 = 0;
v4 = (a3 + 3) & (a3 >> 32);
if ( a3 >= 0 )
v4 = a3;
v5 = v4 & 0xFFFFFFFC;
v6 = result;
for ( i = a2; ; *(_BYTE *)(i - 1) = *(_BYTE *)(v6 - 1) ^ 0xAC )
{
v6 += 4;
i += 4;
if ( v3 >= v5 )
break;
v3 += 4;
*(_BYTE *)(i - 4) = *(_BYTE *)(v6 - 4) ^ 0x5F;
*(_BYTE *)(i - 3) = *(_BYTE *)(v6 - 3) ^ 0xE3;
*(_BYTE *)(i - 2) = *(_BYTE *)(v6 - 2) ^ 0x1D;
}
if ( v3 < a3 )
{
*(_BYTE *)(a2 + v3) = *(_BYTE *)(result + v3) ^ 0x5F;
v8 = v3 + 1;
if ( v3 + 1 < a3 )
{
v9 = v3 + 2;
*(_BYTE *)(a2 + v8) = *(_BYTE *)(result + v8) ^ 0xE3;
if ( v9 < a3 )
*(_BYTE *)(a2 + v9) = *(_BYTE *)(result + v9) ^ 0x1D;
}
}
return result;
}
int __fastcall Midi_Decode(int result, unsigned __int8 *a2, int a3, unsigned int a4)
{
unsigned int v4; // r5
int v5; // r4
int v6; // r4
unsigned int v7; // r5
bool v8; // cf
int v9; // r2
int v10; // r3
int v11; // r4
int v12; // r2
int v13; // r5
int v14; // r6
int v15; // r12
v4 = a4 - 0x7B8C754D;
if ( a3 <= 127 )
{
v6 = a3 + 3;
v7 = v4 * a4;
v8 = a3 < 0;
v9 = a3 & ~(a3 >> 32);
if ( v8 )
v9 = v6;
v10 = 0;
v11 = v7 + 0x5483B7FD;
v12 = v9 >> 2;
v13 = 0;
v14 = v11;
while ( v13 < v12 )
{
++v13;
v15 = *(_DWORD *)(result + v10) - v14;
v14 += v11;
*(_DWORD *)&a2[v10] = v15 ^ 0x5483B7FD;
v10 += 4;
}
}
else
{
v5 = 0;
do
{
*(_DWORD *)&a2[v5] = (*(_DWORD *)(result + v5) - v4) ^ 0xAAAAAAAA;
v5 += 4;
}
while ( v5 != 128 );
result = sub_5CA4D058(result + 128, (int)(a2 + 128), a3 - 128);
}
return result;
}
int __fastcall Sn_Decode_Ovh(int result, unsigned int *a2, int a3, unsigned __int8 a4)
{
int v4; // r4
signed int v5; // r3
int v6; // r5
v4 = 0;
v5 = 0x8D23B1B;
v6 = 0;
while ( v6 < a3 )
{
++v6;
a2[v4] = (*(_DWORD *)(result + v4 * 4) - v5) ^ 0x5483B7FD;
v5 += 0x8D23B1B;
++v4;
}
return result;
}
int __fastcall Ovh_Decode(unsigned __int8 *a1, unsigned __int8 *a2, int a3, unsigned __int8 a4)
{
unsigned __int8 *v4; // r6
unsigned __int8 *v5; // r5
int v6; // r4
int result; // r0
int v8; // r2
bool v9; // cf
int v10; // r4
v4 = a1;
v5 = a2;
v6 = a3;
if ( a3 <= 127 )
{
v8 = a3 + 3;
v9 = v6 < 0;
v10 = v6 & ~(v6 >> 32);
if ( v9 )
v10 = v8;
result = Sn_Decode_Ovh((int)a1, (unsigned int *)a2, v10 >> 2, a4);
}
else
{
Sn_Decode_Ovh((int)a1, (unsigned int *)a2, 32, a4);
result = sub_5CA4D058((int)(v4 + 128), (int)(v5 + 128), v6 - 128);
}
return result;
}
它使用了一个简单加密算法来加密MIDI数据和OVTR数据,那么密钥key是什么呢?进行深入分析之后,发现这个程序,把每本书的书名(可以是中文)做了一个类似hash算法的操作,转换为4个字节的hash值,并且将这个hash值和对应的加密key存在了一个.sn.cfg的文件中。

如上图,即格式是4字节hash+4字节key的形式,大致看了一下,我这台机器所有书本的key都是相同的,即0xee4025cf,所以直接用此key解密数据即可,根据C程序很快就可以写出一个python脚本来解密:
计算书本hash值的python代如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
def int32_ror(x, k):
if (k<0):
k = -((-k)%32)
else:
k = k%32
binstr = bin(x)[2:]
while (len(binstr)<32):
binstr = '0' + binstr
#print "Before shift: "+ binstr
shiftstr = binstr[k:] + binstr[:k]
#print "after shift: " + shiftstr
return int(shiftstr,2)
bookname = "新编初学小提琴100天"
booklen = len(bookname)
hash = booklen
for ch in bookname:
data = ord(ch)
if (data&0x80 !=0):
data = 0xffffff00 | data
hash = int32_ror(hash,-27)^data
#print hex(hash)
print "book hash =",hex(hash)
解密MIDI和OVTR数据的代码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import struct
def Data_dec_last_part(data,len):
v4 = (len+3)&(len>>32)
if (len >=0):
v4 = len
v5 = v4 & 0xFFFFFFFC
ret = list(data)
idx = 0
while (idx<v5):
idx += 4
ret[idx-4] = chr(ord(ret[idx - 4]) ^ 0x5F)
ret[idx-3] = chr(ord(ret[idx - 3]) ^ 0xE3)
ret[idx-2] = chr(ord(ret[idx - 2]) ^ 0x1D)
ret[idx-1] = chr(ord(ret[idx - 1]) ^ 0xAC)
if (idx<len):
ret[len+idx] = chr(ord(ret[len+idx]) ^ 0x5F)
if (idx + 1<len):
ret[len + idx + 1] = chr(ord(ret[len + idx + 1]) ^ 0xE3)
if (idx + 2 < len):
ret[len + idx + 2] = chr(ord(ret[len + idx + 2]) ^ 0x1D)
return ''.join(ret)
def Midi_Decode(data,len,key):
key2 = (key - 0x7B8C754D)&0xffffffff
if (len<=127): #This condition usually don't happen
v6 = len + 3
v7 = key2 * key
if (len<0):
v9 = v6
else:
v9 = (len & ~(len >> 32)) & 0xffffffff
v10 = 0
v11 = v7 + 0x5483B7FD
v12 = v9>>2
v13 = 0
v14 = v11
ret = []
idx = 0
while (idx < v9):
ret.append(struct.unpack("<I", data[idx:idx + 4])[0])
idx += 4
while (v13 < v12):
v13 += 1
v15 += ret[v10/4] - v14
v14 += v11
ret[v10/4] = v15 ^ 0x5483B7FD
v10 += 4
idx = 0
result = ''
while (idx < v9):
result += struct.pack("I", ret[idx / 4])
idx += 4
return result
else:
ret = []
idx = 0
while (idx < 128):
ret.append(struct.unpack("<I", data[idx:idx + 4])[0])
idx += 4
idx = 0
while (idx < 128):
ret[idx/4] = ((ret[idx/4] - key2)&0xFFFFFFFF) ^ 0xAAAAAAAA
idx +=4
result = ''
idx = 0
while (idx < 128):
result += struct.pack("I",ret[idx/4])
idx += 4
result += Data_dec_last_part(data[128:],len-128)
return result
def Sn_Decode_Ovh(data,len,key):
v5 = 0x8D23B1B
ret = []
idx = 0
while (idx < len):
ret.append(struct.unpack("<I", data[idx:idx + 4])[0])
idx += 4
idx = 0
while (idx<len):
ret[idx/4] = ((ret[idx/4] - v5)&0xffffffff) ^ 0x5483B7FD
v5 = (v5 + 0x8D23B1B)&0xffffffff
idx += 4
result = ''
idx = 0
while (idx < len):
result += struct.pack("I", ret[idx / 4])
idx += 4
return result
def Ovh_Decode(data,len,key):
if (len<=127):
v8 = len+3
v10 = (len & ~(len >> 32)) & 0xffffffff
if (len<0):
v10 = v8
return Sn_Decode_Ovh(data,v10,key)
else:
result = Sn_Decode_Ovh(data,128,key)
result += Data_dec_last_part(data[128:],len-128)
return result
if __name__=="__main__":
key = 0xee4025cf
encmidi = '''69 AF 76 41 2C 5B 5E 1F 2C 5B 5E 1E 2D FB 9A 71
5A 72 5E 1D 2D 3F 5E C8 2B 55 4F 0D 06 3B 17 8A
EE 18 17 D8 A1 1E C5 74 2C 06 A6 21 30 59 66 15
2C 06 AF 1C 22 7E A4 1D D7 59 60 6F 45 76 7A 36
46 5B 09 21 33 98 77 3C 5A 76 8D 38 4E 8F 3E 60
7B 3B CC 74 9D F7 15 BA 98 04 C4 D7 9B 13 20 D0
2C 1B 36 1D 9C 50 9E 1D BC 44 AE 9C 1C DB 47 5D
1C EB 47 6D AB 4A DE 06 6C 4B EE 5D 7C DA 4D 9D
1E A3 2D 3C 1C B3 9E 9C DF A0 5D 9C CF A6 4D 2A
3F 63 58 EC 3F 73 5A FC D9 83 9D EB 1F 83 8D E9
0F 60 2D 2C 1A A3 2D 3C 1C B3 9E 9C DF A0 5D 9C
CF A2 4D 2F 6F 63 5C EC 6F 73 5D FC DC D3 9D EC
1F D3 8D 92 0F 65 7D 2C 61 A3 7D 3C 1A B3 9B CC
DF A6 5D CC CF A0 4D 2F 6F 63 5E EC 6F 73 5C FC
DC D3 9D ED 1F D3 8D EC 0F 60 2D 2C 1F A3 2D 3C
1F B3 9E 9C DF A3 5D 9C CF DD 4D 2A 3F 63 23 EC
3F 73 58 FC D9 83 9D E9 1F 83 8D EF 0F 60 2D 2C
1C A3 2D 3C 1E B3 9E 9C DF A2 5D 9C CF A3 4D 2F
6F 63 5D EC 6F 73 5D FC DC D3 9D EC 1F D3 8D 92
0F 6E 5D 2C 61 A3 1D 53 70 E3 00 00
'''.replace(" ","").replace("\n","").decode('hex')
#print encmidi
encovh = '''D0 19 80 7D EA 0D 94 4D DC 83 1A 53 08 C7 2A 9E
80 DB F6 80 9F FD 70 89 BC 55 43 94 D5 90 19 0D
F0 CB E7 A5 0A 05 BA AC E8 60 FA D5 00 99 C2 90
F7 C4 9D E1 75 F3 02 D0 F9 2D D5 D8 96 68 A7 E1
6A A3 79 EA 3A DE 4B F3 F6 18 1E FC CE 53 F0 04
E8 B7 2C EB F4 09 F4 E8 0F 4E D1 FB 20 5A 6F 48
9F 7D 0B 2D BA B8 DD 39 D6 F3 AF 44 F1 2E 02 4A
0C 6A 51 54 27 A5 2F 5D 42 DC F8 65 DD 04 CB 6E
5D E3 1D AE 5A E3 1D AC 41 E3 1D AA 5F E3 9D 89
5F E3 51 E5 11 A6 19 AC 5F A1 59 ED 0B E2 1D AC
64 E2 51 AC E2 E3 1D A8 5B E3 1D E4 43 E3 1D AC
5F E3 1D AC 5C F3 1D AC 5F BB 1D AC A0 43 53 E3
0B E3 1D AC 5F E3 0E 2C 5E 1C 58 AC 5F E3 1D F9
0F B0 1D FA 5F E3 1D AC 5F E3 53 E3 0B E2 FD AD
5F E3 1E 2C 5E 1C 58 AC 5F E3 1D E2 10 B7 1E 6C
5D E3 1D BF DF E2 E2 E9 5F E3 1D AC 1B B3 4E AC
7C E1 1D 52 A9 E3 2D E2 10 B7 18 0C 5C E3 1D AF
5F E2 1D EB 5F E3 5D AC 5F ED E2 EE 1B A2 49 AE
5F E3 26 AD 95 E3 26 AD 5F E7 19 AC 11 AC 49 AC
5F E3 1D AC 5D E3 1C AD 16 E3 1D 8D 5F AD 52 F8
5C 23 1F AC 5F E0 1D AD 5F A4 1D AC 1F E3 53 E3
0B E6 BD AE A8 E3 1E 2C 5E 1C 58 AC 5F E3 1D AC
51 1C 5F E8 1E B7 1E AC 5F D8 1C E4 5E 5A 1C AC
5B E7 1D E2 10 B7 1D AC 5F A4 1D AF 5F E2 1C E5
5F E3 3C AC 11 AC 49 AD BF E2 78 AC 5C E3 1C AC
18 E3 1D EC 5F AD 52 F8 5C 23 1F 2E 5F E1 9D AD
A0 A6 1D AC 5F E3 1D A2 A0 A1 59 ED 0B E7 1D AC
64 E2 DB AD 67 E1 1D A8 5B E3 53 E3 0B E3 1D AC
5F E3 1E 2C 5E 1C 58 AC 5F E3 1D E2 10 B7 1C 4C
5F 19 1D AF DF E2 E2 E9 5F E3 1D AC 11 AC 49 AF
9F E2 E8 AC 5C 63 1C 53 1A E3 1D AC 5F AD 52 F8
5A 43 1E A9 5F E0 1D AD 5F A4 1D AC 1F E3 1D A2
A0 AF 54 E2 1A E1 1D AC 1D A7 5C F8 5A E3 1D 97
5E D8 1D 9F 5E E3 19 A8 5F AD 52 F8 5F E3 1D EB
5F E1 1D AD 5E AA 1D AC 7E E3 53 E3 0B E0 DD AD
9B E3 1E AC 5E E3 5A AC 5F A3 1D E2 10 B7 18 0C
5D 01 1D AF DF E2 E2 E9 5F E3 1D AC 5F ED E2 EE
1B A2 49 AA 5F E3 26 AD 1F E2 25 AE 5D E7 19 AC
11 AC 49 AC 5F E3 5A AC 5C E3 1C AD 16 E3 1D 8D
5F AD 52 F8 5E 03 1C C9 5F E0 1D AD 5F A4 1D AC
1F E3 53 E3 0B E0 DD AE DD E3 1F 2C 5E 1C 58 AC
5F E3 1D AC 51 1C 00 00
'''.replace(" ","").replace("\n","").decode('hex')
deckey = (key - 0x7b8c754d)&0xffffffff
datalen = len(encmidi)
f = open('decmidi.mid','wb')
f.write(Midi_Decode(encmidi,datalen,key))
f.close()
f = open('decovh.ovh', 'wb')
f.write(Ovh_Decode(encovh, len(encovh), None))
f.close()
解密后的MIDI数据:

没错,这就是MIDI格式,直接保存成.mid文件,可以用windows自带播放器播放。
那么至于OVTR数据呢?看上去并没有看出来和已知数据有什么关联:

一开始其实我看到BDAT之类的标签,一度以为OVTR是著名打谱软件overture的缩写,用的是ove格式,后来经过比对,发现完全不同,网上搜了一圈也没找到完全相同的文件格式记法。看来还是得自己慢慢分析。最终也大致写了一个010Editor模板可以解析出其格式:
//------------------------------------------------
//--- 010 Editor v9.0.1 Binary Template
//
// File: OVH.bt
// Authors: Jarvis
// Version:
// Purpose:
// Category:
// File Mask:
// ID Bytes:
// History:
//------------------------------------------------
LittleEndian();
#define VIOLIN_LINEINFO_LEN 5
typedef struct bar_index
{
char hdr[4];
char bar_info_data[hdr[3]*7];
} BarIndexInfo <optimize=false>;
struct OVH
{
struct OVH_Header
{
char ver_magic[16]; //Magic for version detect
uint16 timeSignature;
short init_Tempo;
uint32 totalTicks;
char totalLines[3];
char displayLines[3];
local uint32 total_lines = totalLines[0] | (uint32)totalLines[1]<<8 | (uint32)totalLines[2]<<16;
local uint32 display_lines = displayLines[0] | (uint32)displayLines[1]<<8 | (uint32)displayLines[2]<<16;
char linesInfo[VIOLIN_LINEINFO_LEN*total_lines];
char label_bar_addr[12];
uint32 bar_nums;
uint32 bar_addr_info[bar_nums];
char label_line_bar_idx[16];
BarIndexInfo bar_idx_info[total_lines];
} ovh_header;
char lineData[FileSize()-sizeof(ovh_header)];
} file;
似乎这个的确是这家公司为他们这个学习机单独创造的一种记谱文件格式,因为毕竟他还包含指位提示信息,需要对应显示在界面上。所以,看来没有捷径可走了。格式本身并不复杂,其中一个LINE代表一行谱子,有几个LINE就显示几行,BDAT标签后面跟的就是谱子中一个小节的数据,有几个BDAT就是乐谱有几个小节。至于里面的NOT代表Note,注释,UPS代表升记号之类的,其他数据表达的是符号的坐标信息之类的,由于我现在乐理知识有限,还不能根据自己的想法做一个打谱软件。
唉,还是自己太菜了,现在能力还不够,还是继续好好练琴吧,等我拥有了充足的乐理知识,打算在MuseScore这个开源项目 https://github.com/musescore/MuseScore 上进行修改,对其作出的谱子信息做一个软件将格式转为这个小提琴学习机需要的格式。
关于如何自己添加歌曲:
其实只需要找一张SD卡(全尺寸SD或者tf卡加上卡套),在SD卡根目录中创建一个MT_Update文件夹,然后将msb文件放进来即可,其中每本书的加密key需要放在一个books.key文件中(没错,我们之前做好的自己的MIDI和OVTR数据需要加密回去再存入书本,但是so库中只有解密算法,没有加密算法,不过这个加密算法太简单了,从解密算法可以很容易反推出加密算法,代码我就不放了,日后如果做出来打谱软件一起放),每4字节一本书,按照书本的hash值顺序排列,不过你可以所有书的hash值都一样,这样就只要一个4字节重复n遍就好了:

最后,将SD卡插入机器的SD卡槽,开机会自动识别出SD卡里有新的书本,问你是否要更新:

总的来说,还是比较方便的。
后记:
没想到这个机器用的书本格式那么复杂,这家公司真的丧心病狂啊,我猜测可能这家公司以为机器可能会卖的很好,后续可以继续出售书本来收取后续服务费,而不是卖了机器就完了。也许结果打脸了吧,机器卖的也许并不好,有购买书本需求的用户可能也不多,最后的结果也许是凉凉了吧。
好了,不讲废话了,拿起我的小提琴,好好练琴去吧:

浙公网安备 33011002014706号
zz
orz!!!师傅tql!!!
foxwallop
大家不要学这个,典型的本末倒置,本来说好练琴的…