德国坦克Terratec AUREON7.1PCIe声卡的ASIO驱动在freepiano里不能用的解决方案
最近在玩freepiano,觉得很不错,是非常适合程序员的乐器,而且操作也挺简单的,作为一个程序猿,再适合不过的乐器就是键盘了,因为键盘才是随时随地都能触碰到的东西,其他乐器需要环境练习,很容易忙起来就三天打鱼两天晒网,难以进步。好了废话不多说,最近玩freepiano的时候,由于集成声卡不支持ASIO,因此演奏的时候延迟有点大,系统的DirectSound驱动延迟通常在10ms左右,如果再加上以后想要外接MIDI键盘演奏的话,那延迟很容易就会达到几十ms,这在演奏某些bpm高的乐曲的时候是难以接受的,因此支持硬件ASIO的声卡还是挺重要的,虽然可以使用ASIO4ALL来绕过系统驱动模拟ASIO,降低延迟,但毕竟软件ASIO方案有诸多Bug,而且经常会导致演奏的时候破音之类的问题,因此还是需要买一块像样一点的声卡的。
去淘宝上逛了一圈,大多数声卡都是拿来K歌或者直播用的,支持ASIO的好像很难找到,最终选了这款Terratec的AUREON 7.1 PCIe内置声卡,据说口碑还可以。拿回家以后迫不及待的装上了,然而当我兴冲冲的打开freepiano的时候,发现竟然用不了?此时的我满脸问号:
竟然设备不可用或已断开?
难道是驱动没装好,于是我重装了几遍官方驱动,还是这个样子,难道是软件问题吗?于是不信邪的我又尝试了和freepiano差不多的软件everyonepiano(EOP),据说EOP使用了大量的freepiano的代码,然而,似乎一样的结果:
此时的我一脸懵逼中,这可如何是好,好几百买的声卡竟然跟我说不能用,什么鬼?我想正常人到这里应该就放弃了吧,但是作为一个大黑阔,一定要探清楚啥原因才行。
据说freepiano是开源软件,然而网上找了一圈,只找到1.8的代码,而目前最新的freepiano是2.2.2.1版本,翻遍了网上也没找到2.2.2.1的代码,后来找了半天,放弃了,网上有人说freepiano的2.x版本暂时没有开源,开源的是1.x版本。好吧,竟然这样的话那是别指望能拿到代码了。不过没关系,应该1.x和2.x的代码架构差不多才对,先看看1.x的代码吧。
熟练的我一阵git clone操作,然后看了一下ASIO相关的代码,没看出什么,看来还是需要动态调,不过我用的是VS2015,看上去freepiano 1.8应该用的是更早的VS编译的,有很多环境问题,一编译一大堆Error,没办法,作为码农就不要嫌弃Error,一个个修,修了半天,终于可以成功编译了,正在沾沾自喜的时候,突然打脸,连接错误。继续慢慢看吧。
到最后还剩一个LNK2019 无法解析的外部符号 __get_output_format, 这个错误一直没法解决。去网上翻了一圈,貌似是说mingw的静态库libmingwex里面引用了这个符号,而这个符号从VS2010开始已经没有了,所以换个mingw库即可,我去github上找了一圈,总算发现了一个库能用,这里顺便也放出来给大家吧,如果用得着的话。
放在代码的3rd/libx264/lib下面覆盖掉原来的libmingwex.a即可。
好了,替换了库之后终于可以成功生成可执行文件了,接下来我们调试下ASIO驱动加载的地方,最后终于找到了问题之所在:
在加载驱动的loadDriver函数里调用了一个asioGetDriverName函数,这个函数会返回drvID对应的设备名称,而如果名称超过32字节的话,会缩写成28字节+”…”的名字,对于我的这个声卡,名字叫“C-Media High Definition Audio Device(x64)”,也太TM长了吧!而在这个函数里,返回的结果会变成
“C-Media High Definition Audi…” ,而在loadDriver函数里为了判断当前要加载的是哪个驱动,需要用菜单里选中的驱动名称,去匹配数组里的drvID进行加载,但是后面的_tcscmp函数并没有指定长度,也就是说,
asioGetDriverName函数获得的驱动名称最长只有32个字节,超过会被缩写,而菜单里并没有对驱动程序的名称长度作限制,而是原来多长就原模原样显示,这样的话,如果驱动名字没有超过28个字节,那么OK,没有问题,如果一旦超过,那就会因为找不到对应的drvID而失败!这不是傻逼吗?
好了,问题很明确了,只要在比较字符串的时候限制一下长度,只比较前28个字节就可以了,那么如果有两块一模一样的声卡怎么办?那我也没办法了,随机加载一块,似乎也没问题吧?至少应该不会有人在电脑里装2块一模一样的声卡吧,没有意义吧?
但是,freepiano 2.2.2.1是不开源的只有二进制文件怎么办?那就只能发挥patch大法了,由于没有符号,极其恶心,而且版本不同代码有些差别,经过了一段时间的逆向分析,终于找到了2.2.2.1对应的地方:
好了,那怎么patch呢?由于源文件里面并没有多余的空间给我们放代码,所以只能增加一个section,然后跳转过去执行,总体难度不大。
如上图,我直接hook了inc rax那一句,直接改成跳转到我新加的区段,如下图这样,用jmp长跳转。
然后在新节区写patch代码就行了:
然后我们继续测试一下patch完的程序能不能正常工作:
似乎正常了呢!!!完美!
好了,好东西不敢独享,附上patch之后的文件。如果你碰到一样的问题,直接下载我patch之后的版本就可以了:
等到有空的时候再给作者发个邮件报一下Bug吧。
Patch大法好!