图:自己和自己过情人节。。。
环境
首先不知道deemo是什么的估计对这篇帖子也不会感兴趣,这部分就省下不写了(咕咕咕)。由于deemo也是Unity做的,所以在Android破解进阶之:元气骑士中的大部分工具这里也适用。
- Deemo安装包:要带有obb文件的(1G+的那个),因为数据主要在obb文件里,找不到的可以在这里下载。
- AssetStudio:提取asset,下载。
- Python环境:人生苦短啊。。。
解包
下载完后把apk/apkx文件解包,然后再找到obb文件把obb文件解压,AssetStudio打开并提取asset文件夹的内容。提取后的AudioClip文件夹里是游戏原声,Texture2D文件夹里是游戏原画(含剧透,慎重打开)。提取MIDI的话主要是TextAsset文件夹里的json文件,里面存有音符的信息,对于每首曲子有easy、normal、hard(、extra)难度的对应json文件,但其实MIDI信息是一样的,随便用一个就好。
json文件里主要关注notes数组的信息,尤其是sounds里面的d、p、v和notes的time,由于MIDI是二进制的文件,所以就用了Python的MIDIUtil来生成。文档里面有例程,可以直接用,其中sounds的d、p、v分别对应MIDIUtil的duration、pitch、volume,notes的time对应MIDIUtil的time,track和channel是轨道之类的信息,默认0就可。BPM的话可以用librosa提取(其实librosa也不是很准的,还慢,所以还是自己搞了个- -),嫌麻烦的话可以直接填60。要注意一下json文件里的_time单位是秒,MIDIUtil的time单位是one beat。
另:
- duration是一个音的延续时间。
- pitch是音高,即do re mi那些。
- volume是音量,都懂的。
- time是出现时间,注意单位。
- 还有bpm只能其参考作用,毕竟有些bpm是会变的- -
直接贴代码(2021.9.12更新):
import json
from midiutil.MidiFile import MIDIFile
import numpy as np
import os
import pandas as pd
RESULT_DIR = 'path\\to\\your\\result_dir'
JSON_DIR = 'path\\to\\your\\asset_exported_dir\\TextAsset\\'
# get file name
tmp = []
for _,_,f in os.walk(JSON_DIR):
tmp = f
files = []
for t in tmp:
if 'hard' in t:
files.append(t)
#print(files)
# run
werrors = []
jerrors = []
for name in files:
print('starting -> '+name)
# get notes in file
try:
with open(JSON_DIR+name,'r') as f:
datas = json.loads(f.read())
except:
jerrors.append(name)
print('get json error -> '+name)
print('------------------')
continue
speed = datas['speed']
#print(speed)
#print(list(datas))
notes = datas['notes']
#print(list(notes[0]))
sounds = []
for n in notes:
try:
try:
t = n['_time']
except:
t = 0
for s in n['sounds']:
sounds.append({'d':s['d'],'p':s['p'],'v':s['v'],'t':t})
except KeyError:
#print('!')
None
#print(len(sounds))
#print(list(notes[0]['sounds'][0]))
try:
start = sounds[0]['t']
for s in sounds:
s['t'] -= start
except:
None
# get bpm
times = []
for s in sounds:
#if s['p']<50:
times.append(s['t'])
#times = [s['t'] for s in sounds]
if(len(times)<2):
bpm = 60
else:
dt = []
for i in range(len(times))[1:]:
r = times[i]-times[i-1]
if r != 0:
dt.append(r)
gm = pd.Series(data=dt)
bpm = 60/gm.mode()[0]
while(bpm>200):
bpm /= 2
while(bpm<60):
bpm *= 2
bpm = round(bpm)
#bpm = int(bpm)
print('bpm -> '+str(bpm))
# write midi
MyMIDI = MIDIFile(1)
track = 0
time = 0
bias = float(bpm/60)
# Add track name and tempo.
MyMIDI.addTrackName(track,time,name+'_deemo')
MyMIDI.addTempo(track,time,bpm)
for s in sounds:
MyMIDI.addNote(0,0,s['p'],s['t']*bias,s['d']*bias,s['v'])
# And write it to disk.
try:
binfile = open(RESULT_DIR + name.split('.')[0]+'.mid', 'wb')
MyMIDI.writeFile(binfile)
binfile.close()
except:
werrors.append(name)
print('write file error -> '+name)
#continue
#print('finished -> '+name)
print('------------------')
print('json_errors:')
for e in jerrors:
print(e)
print('write_error:')
for e in werrors:
print(e)
代码是提取全部的,如果要提取单个的话可以把for循环的东西拿出来用,name填单个的名字。
MIDIUtil的bug
用MIDIUtil时有的文件会报下面的错误(github上有同样的issue的,不过2018年到现在都没人修,估计是不维护了- -):
可以找到有问题的MidiFile.py文件,找到下面地方:
然后改成这样:
测试过运行起来没问题。
结果
MIDI文件可以直接用播放器播放(如果播放器支持的话),也可以用Synthesia之类的软件打开(上次破解没破文件打开hhhh),打开后是这样:
(level10的曲子enmmm)
Ps:
写的时候Chrome卡了一下,然后又重写了一次,有些细节的东西下次再补吧(咕咕咕)
强烈建议站主加个草稿功能!