前言
我只是想听歌和找张壁纸而已。
准备
游戏目录结构如下:
1 2 3 4 5 6 7 8
| . ├── D3D12 ├── GameAssembly.dll ├── ******.exe ├── ******_Data ├── UnityCrashHandler64.exe ├── UnityPlayer.dll └── baselib.dll
|
典型的使用 Unity 框架的游戏。
好消息:针对 Unity 框架,我们已经拥有相当多的成熟工具可用。
坏消息:下一级目录表明,该游戏使用了 Il2Cpp 技术,这意味着,我们大概率不太可能获取到 C# 源码。
1 2 3 4 5 6 7 8 9 10
| ******_Data ├── Plugins ├── Resources ├── RuntimeInitializeOnLoads.json ├── ScriptingAssemblies.json ├── StreamingAssets ├── app.info ├── boot.config ├── il2cpp_data └── ... (一堆资源文件)
|
不过我们总是可以看看这些资源文件里面存储了些什么东西。
使用 AssetRipper 解包资源,发现这里面并没有以典型音频格式(.mp3、.m4a 等)格式存储的文件,搜索曲目名称发现这些资源的类型是 MonoBehavior。
某个音频的详细信息如下:

这表明,这些音频直接以 MuaAudio 对象的形式存储为二进制。
查看数据开头:
1
| 0000000100000000001ab2bb0000bb800000000200...
|
发现这的确不是一个常见音频文件的格式,应当是游戏自定义的一种音频格式,不妨称其为 mua 格式。
而 mua 格式的解析只能尝试阅读游戏源码了。
Unity 层
使用 Il2CppDumper 恢复各种符号的定义。
在生成的 dump.cs 中查找 MuaAudio 的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| [NullableContext(1)] [Nullable(0)] public class MuaAudio : ScriptableObject { [SerializeField] [HideInInspector] private MuaAudioClip clip;
public virtual MuaAudioClip Get() { }
public virtual MuaAudioClip GetNormalClip() { }
public virtual string GetState() { }
public void Import(MuaAudioClip audioClip) { }
public void .ctor() { } }
|
发现 MuaAudio 内仅仅存储了一个 MuaAudioClip 的对象。
MuaAudioClip 定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| [NullableContext(1)] [Nullable(0)] [Serializable] public class MuaAudioClip { [SerializeField] [HideInInspector] private byte[] data;
public byte[] GetRawBytes() { }
public static MuaAudioClip FromRawBytes(byte[] bytes) { }
public void .ctor() { } }
|
只是简单的把这种文件的二进制以 byte[] 形式存储起来而已。
这说明,MuaAudio 不负责处理 mua 格式,只保存原始二进制形式。
不过,在 dump.cs 中搜索 mua 的时候,发现了一个非常可疑的类 JuceMUASourcePlayer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
| [NullableContext(1)] [Nullable(0)] public class JuceMUASourcePlayer : DisposableNativeObject, IJuceAudioPlayer, INativePtrHolder { [CompilerGenerated] private AudioTag <Tag>k__BackingField; private readonly ReaderWriterLockSlim Lock; private readonly PlayerManager _owner; [Range(0, 1)] private float _volume; private float _volumeWeight; private bool _mute; private VolumePreset _volumePreset;
public AudioTag Tag { get; set; } public bool PreviewDecoded { get; } public bool FullSongDecoded { get; } public long PlaybackIndex { get; } public long SampleLength { get; } public long PreviewSampleLength { get; } public double Length { get; } public double PreviewLength { get; } public float Volume { get; set; } public float VolumeWeight { get; set; } public bool Mute { get; set; } public VolumePreset VolumePreset { get; set; } public double SampleRate { get; } public uint Channels { get; }
[CompilerGenerated] public AudioTag get_Tag() { }
[CompilerGenerated] public void set_Tag(AudioTag value) { }
private void .ctor(IntPtr ptr, PlayerManager owner) { }
public void Dispose() { }
protected override void DisposeUnmanaged() { }
internal static JuceMUASourcePlayer CreateFromBytes(PlayerManager owner, byte[] data, float initialAudioSourcePlayerGain) { }
public void DecodePreview(CancellationToken ctx) { }
public bool get_PreviewDecoded() { }
public void DecodeFullSong(CancellationToken ctx) { }
public void ReleaseFullSong() { }
public bool get_FullSongDecoded() { }
public long get_PlaybackIndex() { }
public long get_SampleLength() { }
public long get_PreviewSampleLength() { }
public double get_Length() { }
public double get_PreviewLength() { }
public void Play(bool doInitialize) { }
public void Start() { }
public void Resume() { }
public void Stop() { }
public void ResumeAt(double position) { }
public float get_Volume() { }
public void set_Volume(float value) { }
public float get_VolumeWeight() { }
public void set_VolumeWeight(float value) { }
public bool get_Mute() { }
public void set_Mute(bool value) { }
public VolumePreset get_VolumePreset() { }
public void set_VolumePreset(VolumePreset value) { }
public double get_SampleRate() { }
public uint get_Channels() { }
public BufferPlayerPair Detach() { }
[NullableContext(2)] public void ApplyIIRFilterState(float[] c, uint durationInSamples) { } }
|
该类提供了 CreateFromBytes 和 DecodeFullSong 等方法,很难不让人怀疑这是和音频解码高度相关的类。
该类继承了 DisposableNativeObject,预示着这些方法可能会以 native 代码的形式存在,而非由 C# 实现。
使用 IDA 打开 GameAssembly.dll,使用 Il2CppDumper 提供的脚本恢复符号。
其中 CreateFromBytes 反编译结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| Milody_Audio_JuceMUASourcePlayer_o *Milody_Audio_JuceMUASourcePlayer__CreateFromBytes( Milody_Service_PlayerManager_o *owner, System_Byte_array *data, float initialAudioSourcePlayerGain, const MethodInfo *method) { __int64 (__fastcall *v6)(intptr_t *, uint8_t *, __int64, const MethodInfo *); __int64 max_length_low; intptr_t v8; __int64 v9; System_Threading_ReaderWriterLockSlim_o *v10; Milody_Service_PlayerManager_o *v11; __int64 v13; System_Exception_o *v14; System_String_o *v15; __int64 v16; _QWORD v17[4]; int v18; int v19; int v20; char v21; intptr_t ptr;
if ( !byte_1832B3434 ) { sub_180260980(&Milody_Audio_JuceMUASourcePlayer_TypeInfo); byte_1832B3434 = 1; } ptr = 0; if ( !data ) goto LABEL_11; v6 = (__int64 (__fastcall *)(intptr_t *, uint8_t *, __int64, const MethodInfo *))qword_1832B32E0; max_length_low = SLODWORD(data->max_length); if ( !qword_1832B32E0 ) { v18 = 0; v17[0] = L"libMilody"; v21 = 0; v17[2] = "MilodyAudioJuceMUASourcePlayerCreate"; v17[1] = 9; v17[3] = 36; v19 = 2; v20 = 28; v6 = (__int64 (__fastcall *)(intptr_t *, uint8_t *, __int64, const MethodInfo *))sub_180260C20((__int64)v17); qword_1832B32E0 = (__int64)v6; } if ( v6(&ptr, data->m_Items, max_length_low, method) < 0 ) { v13 = sub_1802609A0(&System_Exception_TypeInfo); v14 = (System_Exception_o *)sub_180260B80(v13); v15 = (System_String_o *)sub_1802609A0(&StringLiteral_7979); System_Exception___ctor_6468750720(v14, v15, 0); v16 = sub_1802609A0(&Method_Milody_Audio_JuceMUASourcePlayer_CreateFromBytes__); sub_180260B90(v14, v16); } v8 = ptr; v9 = sub_180260B80(Milody_Audio_JuceMUASourcePlayer_TypeInfo); if ( !byte_1832B3433 ) { sub_180260980(&System_Threading_ReaderWriterLockSlim_TypeInfo); byte_1832B3433 = 1; } v10 = (System_Threading_ReaderWriterLockSlim_o *)sub_180260B80(System_Threading_ReaderWriterLockSlim_TypeInfo); System_Threading_ReaderWriterLockSlim___ctor_6471138288(v10, 1, 0); *(_QWORD *)(v9 + 64) = v10; sub_18025FBF0(v9 + 64, v10); *(_DWORD *)(v9 + 80) = 1065353216; *(_DWORD *)(v9 + 84) = 1065353216; *(_DWORD *)(v9 + 92) = 1; Paraparty_UnityNative_Base_DisposableNativeObject___ctor_6469717520( (Paraparty_UnityNative_Base_DisposableNativeObject_o *)v9, v8, 1, 0); *(_QWORD *)(v9 + 72) = owner; sub_18025FBF0(v9 + 72, owner); v11 = *(Milody_Service_PlayerManager_o **)(v9 + 72); if ( !v11 ) LABEL_11: sub_180260BD0(); Milody_Service_PlayerManager__Register(v11, (Milody_Model_IJuceAudioPlayer_o *)v9, 0); return (Milody_Audio_JuceMUASourcePlayer_o *)v9; }
|
其清晰地表明该函数会调用 libMilody 这个 native 库的 MilodyAudioJuceMUASourcePlayerCreate 函数,并持有这个函数返回的指针。
DecodeFullSong 的情况也与其类似,调用 native 库的 MilodyAudioJuceMUASourcePlayerDecodeFullSong 函数。
native 层
好消息:现在不需要看反编译 C# $\to$ il $\to$ cpp $\to$ native 制造出的一坨玩意。
坏消息:libMilody 由 cpp 编写,并且使用了大量 cpp 特性和库,导致反编译的产物可读性也不咋样。
使用 IDA 打开 libMilody.dll。
构造函数
先看 MilodyAudioJuceMUASourcePlayerCreate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| __int64 __fastcall MilodyAudioJuceMUASourcePlayerCreate(__int64 *res, const void *rawBytes, size_t size) { __int64 v6; void *v7; __int64 result; __int64 *inited; __int64 v10; milody::audio::format::MUAException *v11; __int64 *v13; __int64 v14; __int64 *v15; int v16; _BYTE v17[48]; milody::audio::format::MUAException *v18; std::runtime_error *v19; const char *v20; __int64 v21; __int64 v22; int v23; int v24; __int64 v25; __int128 v26; __int64 v27; __int64 v28;
v6 = 0; *res = 0; try { v7 = wrapped_malloc(328u); if ( v7 ) v6 = JuceMUASourcePlayerConstructor((__int64)v7, rawBytes, size); *res = v6; result = 0; } catch ( milody::audio::format::MUAException *v18 ) { inited = InitLogger(); v10 = sub_180019090((__int64)inited); v11 = v18; (*(void (__fastcall **)(milody::audio::format::MUAException *))(*(_QWORD *)v18 + 8LL))(v18); v20 = "{}, {}"; v21 = 6; v22 = 0; v23 = 0; v24 = 0; v25 = 0; v26 = 0u; v27 = 0; sub_18001A810(v10, &v26, 4, (__int64)&v20, (__int64)"MilodyAudioJuceMUASourcePlayerCreate", &v28); return *((_QWORD *)v11 + 3); } catch ( std::runtime_error *v19 ) { v13 = InitLogger(); v14 = sub_180019090((__int64)v13); (*(void (__fastcall **)(std::runtime_error *))(*(_QWORD *)v19 + 8LL))(v19); v20 = "{}, {}"; v21 = 6; v22 = 0; v23 = 0; v24 = 0; v25 = 0; v26 = 0u; v27 = 0; sub_18001A810(v14, &v26, 4, (__int64)&v20, (__int64)"MilodyAudioJuceMUASourcePlayerCreate", &v28); return -11709394; } catch ( ... ) { v15 = InitLogger(); v16 = sub_180019090((__int64)v15); v20 = "{}, {}"; v21 = 6; v22 = 0; v23 = 0; v24 = 0; v25 = 0; v26 = 0u; v27 = 0; sub_18001A620( v16, (unsigned int)v17 + 112, 4, (unsigned int)v17 + 64, (__int64)"MilodyAudioJuceMUASourcePlayerCreate", (__int64)"unexpected exception"); return -11709394; } return result; }
|
首先对比 Unity 层的调用和 native 层的声明,发现函数参数少了一个 MethodInfo *,原因未知,不过还是可以依次恢复前三项参数。
这里分配了 $328$ 字节的空间,下面调用函数理应是这个对象的构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| __int64 __fastcall JuceMUASourcePlayerConstructor(__int64 this, const void *rawBytes, size_t size) { float v3; __int64 v7; _QWORD *v8; char *v9; __int64 v10; void *v11; void (__fastcall ***v12)(_QWORD, __int64); __int64 v14; unsigned int v15; __int64 v16; __int64 v17; __int64 v18; unsigned int v19; __int64 v20; __int64 v21; _BYTE pExceptionObject[24]; __int64 v23; _BYTE v24[16]; _BYTE v25[240]; _BYTE v26[32];
v23 = this; v7 = 0; *(_QWORD *)this = &milody::audio::JuceMUASourcePlayer::`vftable; *(_QWORD *)(this + 8) = 0; *(_QWORD *)(this + 16) = 0; *(_QWORD *)(this + 24) = 0; if ( size ) { if ( size > 0x7FFFFFFFFFFFFFFFLL ) std::vector<void *>::_Xlen(); v8 = aligned_malloc(size); *(_QWORD *)(this + 8) = v8; *(_QWORD *)(this + 16) = v8; *(_QWORD *)(this + 24) = (char *)v8 + size; v9 = *(char **)(this + 8); memmove(v9, rawBytes, size); *(_QWORD *)(this + 16) = &v9[size]; } v10 = *(_QWORD *)(this + 8); *(_DWORD *)(this + 40) = 0; *(_QWORD *)(this + 48) = 0; *(_QWORD *)(this + 56) = 0; *(_QWORD *)(this + 64) = 0; *(_QWORD *)(this + 72) = 0; *(_QWORD *)(this + 80) = 0; *(_QWORD *)(this + 88) = 0; clear_16bytes((_QWORD *)(this + 96)); *(_QWORD *)(this + 112) = 0; *(_QWORD *)(this + 120) = 0; *(_QWORD *)(this + 128) = 0; *(_QWORD *)(this + 136) = v10; *(_QWORD *)(this + 144) = size; *(_QWORD *)(this + 152) = 0; *(_QWORD *)(this + 168) = 0; *(_QWORD *)(this + 176) = &milody::audio::format::MUAFormat::`vftable; SubObjectConstructor(this + 32); *(_QWORD *)(this + 248) = 0; *(_QWORD *)(this + 256) = 0; *(_BYTE *)(this + 264) = 0; *(_QWORD *)(this + 272) = 0; *(_QWORD *)(this + 280) = 0; *(_QWORD *)(this + 288) = 0; *(_QWORD *)(this + 296) = 0; *(_QWORD *)(this + 304) = 0; *(_QWORD *)(this + 312) = 0; *(_QWORD *)(this + 320) = 0; if ( (unsigned int)sub_180007AD0(this + 32) != 2 ) { sub_1800049D0(v24, 1); v18 = sub_180001FC0((__int64)v25, "channel_num 2 expected, but "); v19 = sub_180007AD0(this + 32); v20 = std::ostream::operator<<(v18, v19); sub_180001FC0(v20, " found"); v21 = sub_18000A540(v24, v26); sub_180004FE0(pExceptionObject, v21); throw (std::runtime_error *)pExceptionObject; } if ( (unsigned int)sub_180007C30(this + 32) != 48000 ) { sub_1800049D0(v24, 1); v14 = sub_180001FC0((__int64)v25, "sample_rate 48000 expected, but "); v15 = sub_180007C30(this + 32); v16 = std::ostream::operator<<(v14, v15); sub_180001FC0(v16, " found"); v17 = sub_18000A540(v24, v26); sub_180004FE0(pExceptionObject, v17); throw (std::runtime_error *)pExceptionObject; } v11 = wrapped_malloc(0xD80u); if ( v11 ) v7 = AudioSourcePlayerConstructor((__int64)v11); v12 = *(void (__fastcall ****)(_QWORD, __int64))(this + 280); *(_QWORD *)(this + 280) = v7; if ( v12 ) (**v12)(v12, 1); sub_18004C220(*(_QWORD *)(this + 280), v3); return this; }
|
对比调用处的参数和函数声明,由于第一个参数指向刚才分配出的空间,因此推断其为 this 指针。
由于这个对象前 $8$ 个字节赋值为 &milody::audio::JuceMUASourcePlayer::`vftable,因此这个对象大概率为 milody::audio::JuceMUASourcePlayer。
接下来 $24$ 字节,按照如下方式初始化:
1 2 3 4 5 6 7 8 9 10 11 12
| if ( size ) { if ( size > 0x7FFFFFFFFFFFFFFFLL ) std::vector<void *>::_Xlen(); v8 = aligned_malloc(size); *(_QWORD *)(this + 8) = v8; *(_QWORD *)(this + 16) = v8; *(_QWORD *)(this + 24) = (char *)v8 + size; v9 = *(char **)(this + 8); memmove(v9, rawBytes, size); *(_QWORD *)(this + 16) = &v9[size]; }
|
这 $24$ 字节大概率为一个 std::vector,持有三个指针,分别为内存的起始地址(其实也是存储数据的起始地址)、数据的结束地址、内存的结束地址。这个 std::vector 负责存储原始二进制数据。
函数 clear_16bytes 如下:
1 2 3 4 5 6 7
| _QWORD *__fastcall clear_16bytes(_QWORD *a1) { *a1 = 0; a1[1] = 0; return a1; }
|
看样子像是 MSVC 生成的默认构造函数啥的,不过作用是清空 $16$ 字节的内存,因此命名为 clear_16bytes。
接下来在偏移 $136$ 字节处保存了存储原始数据起始内存的指针,$144$ 字节处保存原始数据的长度。
偏移 $176$ 字节处初始化为 &milody::audio::format::MUAFormat::`vftable,因此此处开始应当为一个 milody::audio::format::MUAFormat 的子对象。
然后调用了 SubObjectConstructor,传入了 this + 32 作为参数,猜测 this + 32 也是一个子对象,这是其构造函数。
目前,已经知晓的 milody::audio::JuceMUASourcePlayer 结构信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class JuceMUASourcePlayer { void* vftable; std::vector<std::byte> rawData; { std::byte* begin; std::byte* data_end; std::byte* buf_end; } class SubClass obj; { Unknown; std::byte* data = rawData.begin; std::size_t size; Unknown; class MUAFormat mua; { void* vftable; Unknown; }; }; Unknown; };
|
接下来看 this + 32 处对象的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
| void __fastcall SubObjectConstructor(__int64 this) { RTL_SRWLOCK *v2; __int64 *v3; __int64 v4; int v5; _QWORD *v6; void *v7; unsigned int frame_size; int ignore_frame; unsigned int sample_frame; unsigned int total_frame; unsigned int v12; unsigned __int64 i; unsigned int v14; unsigned int v15; unsigned int v16; unsigned int v17; unsigned int v18; unsigned int v19; unsigned int v20; unsigned int v21; unsigned int v22; unsigned int v23; unsigned int v24; unsigned int v25; unsigned int v26; unsigned int v27; unsigned int v28; unsigned int v29; unsigned int v30; unsigned int v31; __int64 v32; unsigned int v33; __int64 v34; __int64 v35; _QWORD *v36; unsigned __int64 v37; __int64 v38; __int64 v39; unsigned __int64 v40; __int64 v41; unsigned __int64 v42; unsigned __int64 v43; unsigned __int64 v44; _QWORD *v45; __int64 v46; unsigned int v47; unsigned int v48; unsigned int v49; unsigned int v50; unsigned int v51[9]; unsigned int v52; unsigned int v53; _QWORD v54[4]; __int128 v55; __int64 v56; char v57[8]; char v58[16]; __int128 pExceptionObject; __int64 v60; __int64 v61; char v62; void *Block[2]; __m128i si128;
v2 = (RTL_SRWLOCK *)(this + 136); v61 = this + 136; AcquireSRWLockExclusive((PSRWLOCK)(this + 136)); v62 = 1; ReadFromRawData(this, &v47, 60u); *(_DWORD *)(this + 152) = _byteswap_ulong(v47); *(_DWORD *)(this + 156) = _byteswap_ulong(v48); *(_DWORD *)(this + 160) = _byteswap_ulong(v49); *(_DWORD *)(this + 164) = _byteswap_ulong(v50); *(_DWORD *)(this + 168) = _byteswap_ulong(v51[0]); *(_DWORD *)(this + 172) = _byteswap_ulong(v51[1]); *(_DWORD *)(this + 176) = _byteswap_ulong(v51[2]); *(_DWORD *)(this + 180) = _byteswap_ulong(v51[3]); *(_DWORD *)(this + 184) = _byteswap_ulong(v51[4]); *(_DWORD *)(this + 188) = _byteswap_ulong(v51[5]); *(_DWORD *)(this + 192) = _byteswap_ulong(v51[6]); *(_DWORD *)(this + 196) = _byteswap_ulong(v51[7]); *(_DWORD *)(this + 200) = _byteswap_ulong(v51[8]); *(_DWORD *)(this + 204) = _byteswap_ulong(v52); *(_DWORD *)(this + 208) = _byteswap_ulong(v53); v3 = InitLogger(); v4 = sub_180019090((__int64)v3); v5 = (**(__int64 (__fastcall ***)(__int64, char *))(this + 144))(this + 144, v57); v6 = (_QWORD *)sub_1800066E0(v5, (__int64)Block, -1, 32, 0, 0); v54[2] = 0; v55 = 0u; v56 = 0; v54[0] = "Mua File Meta: {}"; v54[1] = 17; pExceptionObject = 0u; v60 = 0; sub_180003C20(v4, &pExceptionObject, 1, (__int64)v54, v6); if ( si128.m128i_i64[1] > 0xFuLL ) { v7 = Block[0]; if ( (unsigned __int64)(si128.m128i_i64[1] + 1) >= 0x1000 ) { v7 = (void *)*((_QWORD *)Block[0] - 1); if ( (unsigned __int64)((char *)Block[0] - (char *)v7 - 8) > 0x1F ) invoke_watson(0, 0, 0, 0, 0); } j_j_free(v7); } si128 = _mm_load_si128((const __m128i *)&xmmword_1800F7320); LOBYTE(Block[0]) = 0; sub_180006150((void **)v58, v57[0]); frame_size = *(_DWORD *)(this + 172); ignore_frame = *(_DWORD *)(this + 180) / frame_size; *(_DWORD *)(this + 212) = ignore_frame; sample_frame = (frame_size + *(_DWORD *)(this + 176) - 1) / frame_size + 2; *(_DWORD *)this = sample_frame; if ( (ignore_frame & 3) != 0 ) { v46 = sub_1800047D0((__int64)Block, "ignore_frame % 4 != 0"); sub_180004BC0((__int64)&pExceptionObject, -6, (_QWORD *)v46); throw (milody::audio::format::MUAException *)&pExceptionObject; } total_frame = sample_frame + ignore_frame; *(_DWORD *)(this + 4) = total_frame; v52 = 0; v12 = 0; for ( i = 0; (__int64)i < 60; i += 20LL ) { v14 = ((*((unsigned __int8 *)&v47 + i) ^ dword_1800F6260[(unsigned __int64)v12 >> 24] ^ (v12 << 8)) << 8) ^ dword_1800F6260[(unsigned __int64)(*((unsigned __int8 *)&v47 + i) ^ dword_1800F6260[(unsigned __int64)v12 >> 24] ^ (v12 << 8)) >> 24] ^ *((unsigned __int8 *)&v47 + i + 1); v15 = (v14 << 8) ^ dword_1800F6260[(unsigned __int64)v14 >> 24] ^ *((unsigned __int8 *)&v47 + i + 2); v16 = (v15 << 8) ^ dword_1800F6260[(unsigned __int64)v15 >> 24] ^ *((unsigned __int8 *)&v47 + i + 3); v17 = (v16 << 8) ^ dword_1800F6260[(unsigned __int64)v16 >> 24] ^ *((unsigned __int8 *)&v48 + i); v18 = (v17 << 8) ^ dword_1800F6260[(unsigned __int64)v17 >> 24] ^ *((unsigned __int8 *)&v48 + i + 1); v19 = (v18 << 8) ^ dword_1800F6260[(unsigned __int64)v18 >> 24] ^ *((unsigned __int8 *)&v48 + i + 2); v20 = (v19 << 8) ^ dword_1800F6260[(unsigned __int64)v19 >> 24] ^ *((unsigned __int8 *)&v48 + i + 3); v21 = (v20 << 8) ^ dword_1800F6260[(unsigned __int64)v20 >> 24] ^ *((unsigned __int8 *)&v49 + i); v22 = (v21 << 8) ^ dword_1800F6260[(unsigned __int64)v21 >> 24] ^ *((unsigned __int8 *)&v49 + i + 1); v23 = (v22 << 8) ^ dword_1800F6260[(unsigned __int64)v22 >> 24] ^ *((unsigned __int8 *)&v49 + i + 2); v24 = (v23 << 8) ^ dword_1800F6260[(unsigned __int64)v23 >> 24] ^ *((unsigned __int8 *)&v49 + i + 3); v25 = (v24 << 8) ^ dword_1800F6260[(unsigned __int64)v24 >> 24] ^ LOBYTE(v51[i / 4 - 1]); v26 = (v25 << 8) ^ dword_1800F6260[(unsigned __int64)v25 >> 24] ^ *((unsigned __int8 *)&v50 + i + 1); v27 = (v26 << 8) ^ dword_1800F6260[(unsigned __int64)v26 >> 24] ^ *((unsigned __int8 *)&v50 + i + 2); v28 = (v27 << 8) ^ dword_1800F6260[(unsigned __int64)v27 >> 24] ^ *((unsigned __int8 *)&v50 + i + 3); v29 = (v28 << 8) ^ dword_1800F6260[(unsigned __int64)v28 >> 24] ^ LOBYTE(v51[i / 4]); v30 = (v29 << 8) ^ dword_1800F6260[(unsigned __int64)v29 >> 24] ^ BYTE1(v51[i / 4]); v31 = (v30 << 8) ^ dword_1800F6260[(unsigned __int64)v30 >> 24] ^ BYTE2(v51[i / 4]); v12 = (v31 << 8) ^ dword_1800F6260[(unsigned __int64)v31 >> 24] ^ HIBYTE(v51[i / 4]); } *(_DWORD *)(this + 8) = v12; *(_DWORD *)(this + 128) = 0; v32 = *(_DWORD *)(this + 168) * frame_size * total_frame; v33 = v32; v34 = *(_QWORD *)(this + 16); if ( (unsigned int)v32 > (unsigned __int64)((*(_QWORD *)(this + 32) - v34) >> 1) ) { v35 = (*(_QWORD *)(this + 24) - v34) >> 1; v36 = aligned_malloc(2 * v32); memmove(v36, *(const void **)(this + 16), *(_QWORD *)(this + 24) - *(_QWORD *)(this + 16)); expand_vector(this + 16, (__int64)v36, v35, v33); } resize_vector(this + 40, 1276); v37 = (unsigned int)(*(_DWORD *)(this + 168) * *(_DWORD *)(this + 172)); v38 = *(_QWORD *)(this + 80); v39 = *(_QWORD *)(this + 88); v40 = (v39 - v38) >> 1; if ( v37 < v40 ) { v41 = v38 + 2 * v37; LABEL_23: *(_QWORD *)(this + 88) = v41; goto LABEL_24; } if ( v37 > v40 ) { v42 = (*(_QWORD *)(this + 96) - v38) >> 1; if ( v37 <= v42 ) { memset(*(void **)(this + 88), 0, 2 * (v37 - v40)); v41 = 2 * (v37 - v40) + v39; goto LABEL_23; } v43 = v42 >> 1; if ( v42 <= 0x7FFFFFFFFFFFFFFFLL - (v42 >> 1) ) { v44 = (unsigned int)(*(_DWORD *)(this + 168) * *(_DWORD *)(this + 172)); if ( v43 + v42 >= v37 ) v44 = v43 + v42; if ( v44 > 0x7FFFFFFFFFFFFFFFLL ) Concurrency::cancel_current_task(); } else { v44 = 0x7FFFFFFFFFFFFFFFLL; } v45 = aligned_malloc(2 * v44); memset((char *)v45 + 2 * v40, 0, 2 * (v37 - v40)); memmove(v45, *(const void **)(this + 80), *(_QWORD *)(this + 88) - *(_QWORD *)(this + 80)); expand_vector(this + 80, (__int64)v45, v37, v44); } LABEL_24: OpusDecoderConstructor(this + 64, *(_DWORD *)(this + 164), *(_DWORD *)(this + 168)); ReleaseSRWLockExclusive(v2); }
|
先 AcquireSRWLockExclusive((PSRWLOCK)(this + 136)); 表明偏移 $136+32=168$ 字节处存储的对象为一个锁 RTL_SRWLOCK *。
ReadFromRawData 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| void __fastcall ReadFromRawData(__int64 this, void *out, size_t count) { _QWORD *v5; char *v6; unsigned __int64 v7; __int64 v8; __int64 v9; __int64 v10; _BYTE v11[32]; _BYTE pExceptionObject[32]; _BOOL8 v13; void *Src; _BYTE *v15; char v16; _BYTE v17[32]; _BYTE v18[32];
CopyFromRaw((_QWORD *)(this + 104), (__int64)&v13, count); if ( !v13 ) { v8 = sub_18000B210(&v13, v17); v9 = sub_1800047D0((__int64)v18, "read error. "); v10 = sub_180002230(v11, v9, v8); sub_180004BC0(pExceptionObject, -8, v10); throw (milody::audio::format::MUAException *)pExceptionObject; } v5 = 0; v6 = 0; v7 = v15 - (_BYTE *)Src; if ( v15 != Src ) { if ( v7 > 0x7FFFFFFFFFFFFFFFLL ) std::vector<void *>::_Xlen(); v5 = aligned_malloc(v15 - (_BYTE *)Src); v6 = (char *)v5 + v7; memmove(v5, Src, v15 - (_BYTE *)Src); } memcpy(out, v5, count); if ( v5 ) { if ( (unsigned __int64)(v6 - (char *)v5) >= 0x1000 ) { if ( (unsigned __int64)v5 - *(v5 - 1) - 8 > 0x1F ) invoke_watson(0, 0, 0, 0, 0); v5 = (_QWORD *)*(v5 - 1); } j_j_free(v5); } if ( v13 ) { if ( v16 ) sub_180005E60((__int64)&Src); } else if ( v16 ) { sub_180005F80(&Src); } }
|
CopyFromRaw 函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| _BYTE *__fastcall CopyFromRaw(__int64 this, __int64 a2, size_t count) { __int128 si128; __int128 v7; size_t v8; size_t v9; _QWORD *v10; void *v11; __int128 v13; __int128 v14; _QWORD v15[4]; void *v16[2]; __int128 v17;
if ( *(_QWORD *)(this + 8) - *(_QWORD *)(this + 16) >= count ) { v13 = 0; v8 = 0; v9 = 0; if ( count ) { if ( count > 0x7FFFFFFFFFFFFFFFLL ) std::vector<void *>::_Xlen(); v10 = aligned_malloc(count); v8 = (size_t)v10 + count; memset(v10, 0, count); v9 = (size_t)v10 + count; } else { v10 = (_QWORD *)v13; } memcpy(v10, (const void *)(*(_QWORD *)this + *(_QWORD *)(this + 16)), count); *(_QWORD *)&v14 = 0; v13 = 0u; *(_QWORD *)(this + 16) += count; *(_BYTE *)a2 = 1; *(_BYTE *)(a2 + 40) = 0; *(_QWORD *)&v17 = 0; v16[1] = 0; v16[0] = 0; v15[0] = v10; v15[1] = v9; v15[2] = v8; VectorCopy((_QWORD *)(a2 + 8), (__int64)v15); *(_BYTE *)(a2 + 40) = 1; sub_180005E60((__int64)v15); v11 = v16[0]; if ( v16[0] ) { if ( (unsigned __int64)v17 - (unsigned __int64)v16[0] >= 0x1000 ) { v11 = (void *)*((_QWORD *)v16[0] - 1); if ( (unsigned __int64)((char *)v16[0] - (char *)v11 - 8) > 0x1F ) invoke_watson(0, 0, 0, 0, 0); } j_j_free(v11); } } else { *(_OWORD *)v16 = 0; v16[0] = aligned_malloc(0x30u); si128 = (__int128)_mm_load_si128((const __m128i *)&xmmword_1800F7350); strcpy((char *)v16[0], "remains length is less than given string length"); v7 = *(_OWORD *)v16; v17 = (__int128)_mm_load_si128((const __m128i *)&xmmword_1800F7320); LOBYTE(v16[0]) = 0; *(_BYTE *)a2 = 0; *(_BYTE *)(a2 + 40) = 0; v13 = v7; v14 = si128; sub_180004710(a2 + 8, &v13); *(_BYTE *)(a2 + 40) = 1; sub_180005F80(&v13); } return (_BYTE *)a2; }
|
该函数内的 this 指针实际上对应整个结构的 $32+104=136$ 字节处,此处先前已推断出的内容如下:
1 2
| std::byte* data = rawData.begin; std::size_t size;
|
因此 this 为存储原始数据的内存起始指针,this + 8 为原始数据长度,由 *(_QWORD *)(this + 8) - *(_QWORD *)(this + 16) >= count 可推断 this + 16 为目前已经读取的长度。
此处实际结构如下:
1 2 3 4 5 6
| struct Record rec; { std::byte* data = rawData.begin; std::size_t size; std::size_t offset; }
|
CopyFromRaw 负责检查边界,并使用 std::vector 复制所需的部分,ReadFromRawData 则额外包装错误处理。
总体来讲,这两个函数均是负责从原始数据中取出后续指定大小的数据的,因此命名如上。
SubObjectConstructor 读取文件最开头 $60$ 字节。_byteswap_ulong 表明这部分数据以 $4$ 个字节为一个单位,并且以大端序在文件中存储。这些数据转化为小端序之后依次放置在整体偏移的 $184\sim 240$ 字节处。
此处的 this + 144 对应整体偏移的 $144+32=176$ 字节处,为 MUAFormat 的虚函数表。
该虚表内唯一的函数为 FormatMetadata,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
| __int64 __fastcall FormatMetadata(__int64 this, __int64 a2) { _QWORD *v4; __int64 v5; char *v6; __int64 v7; char *v8; _DWORD *v9; _QWORD *v10; _QWORD *v11; char *v13; char *v14; _BYTE v15[4]; int v16; __int128 v17; __int64 v18; __int128 v19; __int64 v20; __int128 v21; __int64 v22; __int128 v23; __int64 v24; __int128 v25; __int64 v26; __int128 v27; __int64 v28; _BYTE *v29; _QWORD *v30; __int128 v31; __int64 v32; char v33[24]; __int128 v34; __int64 v35; char v36[24]; __int128 v37; __int64 v38; char v39[24]; __int64 v40; char v41[24]; char v42[24]; char v43[24]; char v44[24]; char v45[24]; char v46[24]; char v47[24]; char v48[24]; char v49[24]; char v50[24]; char v51[24]; char v52[24]; char v53[24]; char v54[24]; char v55[24]; char v56[24]; char v57[24]; char v58[24]; _BYTE v59[16]; __int64 v60; char v61[16]; __int64 v62; char v63[16]; __int64 v64; char v65[16]; __int64 v66; char v67[16]; __int64 v68; char v69[16]; __int64 v70; char v71[16]; __int64 v72; char v73[16]; __int64 v74; char v75[16]; __int64 v76; char v77[16]; __int64 v78; char v79[16]; __int64 v80; char v81[16]; __int64 v82; char v83[24]; char v84[24]; char v85[24]; char v86;
v40 = a2; v16 = 0; v13 = (char *)&v21; v21 = 0u; sub_180006150((void **)&v21 + 1, 0); LOBYTE(v21) = 3; v4 = wrapped_malloc(0x20u); v4[1] = 0; v4[2] = 7; v4[3] = 15; *(_DWORD *)v4 = 1936876918; *((_WORD *)v4 + 2) = 28521; *((_BYTE *)v4 + 6) = 110; *((_BYTE *)v4 + 7) = 0; *((_QWORD *)&v21 + 1) = v4; v22 = 0; v23 = 0u; v5 = *(unsigned int *)(this + 8); sub_180006150((void **)&v23 + 1, 0); LOBYTE(v23) = 6; *((_QWORD *)&v23 + 1) = v5; v24 = 0; v13 = (char *)&v21; v14 = (char *)&v25; sub_180004310((__int64)v59, (__int64 *)&v13, 1, 2); v60 = 0; v13 = (char *)&v17; v17 = 0u; sub_180006150((void **)&v17 + 1, 0); LOBYTE(v17) = 3; v6 = (char *)wrapped_malloc(0x20u); *(_OWORD *)v6 = 0; *((_QWORD *)v6 + 2) = 4; *((_QWORD *)v6 + 3) = 15; strcpy(v6, "type"); *((_QWORD *)&v17 + 1) = v6; v18 = 0; v19 = 0u; v7 = *(unsigned int *)(this + 12); sub_180006150((void **)&v19 + 1, 0); LOBYTE(v19) = 6; *((_QWORD *)&v19 + 1) = v7; v20 = 0; v13 = (char *)&v17; v14 = (char *)&v21; sub_180004310((__int64)v61, (__int64 *)&v13, 1, 2); v62 = 0; v13 = (char *)&v25; v25 = 0u; sub_180006150((void **)&v25 + 1, 0); LOBYTE(v25) = 3; v8 = (char *)wrapped_malloc(0x20u); *(_OWORD *)v8 = 0; *((_QWORD *)v8 + 2) = 9; *((_QWORD *)v8 + 3) = 15; strcpy(v8, "file_size"); *((_QWORD *)&v25 + 1) = v8; v26 = 0; v27 = 0u; sub_1800032B0(&v27, *(unsigned int *)(this + 16)); v28 = 0; v13 = (char *)&v25; v14 = (char *)&v29; sub_180004310((__int64)v63, (__int64 *)&v13, 1, 2); v64 = 0; v13 = (char *)&v37; v37 = 0u; sub_180006150((void **)&v37 + 1, 0); LOBYTE(v37) = 3; v9 = wrapped_malloc(0x20u); v9[3] = 0; *((_QWORD *)v9 + 2) = 11; *((_QWORD *)v9 + 3) = 15; strcpy((char *)v9, "sample_rate"); *((_QWORD *)&v37 + 1) = v9; v38 = 0; sub_180001DC0(v39, this + 20); v13 = (char *)&v37; v14 = (char *)&v40; sub_180004310((__int64)v65, (__int64 *)&v13, 1, 2); v66 = 0; sub_180001E20(v57, "channel_num"); sub_180001DC0(v58, this + 24); v13 = v57; v14 = v59; sub_180004310((__int64)v67, (__int64 *)&v13, 1, 2); v68 = 0; sub_180001E20(v55, "frame_size"); sub_180001DC0(v56, this + 28); v13 = v55; v14 = v57; sub_180004310((__int64)v69, (__int64 *)&v13, 1, 2); v70 = 0; sub_180001E20(v53, "sample_num"); sub_180001DC0(v54, this + 32); v13 = v53; v14 = v55; sub_180004310((__int64)v71, (__int64 *)&v13, 1, 2); v72 = 0; sub_180001E20(v51, "ignore_num"); sub_180001DC0(v52, this + 36); v13 = v51; v14 = v53; sub_180004310((__int64)v73, (__int64 *)&v13, 1, 2); v74 = 0; v13 = (char *)&v34; v34 = 0u; sub_180006150((void **)&v34 + 1, 0); LOBYTE(v34) = 3; v10 = wrapped_malloc(0x20u); v29 = v15; v30 = v10; *(_OWORD *)v10 = 0; v10[2] = 0; v10[3] = 0; sub_180002370(v10, "preview_begin", 0xDu); *((_QWORD *)&v34 + 1) = v10; v35 = 0; sub_180001DC0(v36, this + 40); v13 = (char *)&v34; v14 = (char *)&v37; sub_180004310((__int64)v75, (__int64 *)&v13, 1, 2); v76 = 0; sub_180001E20(v49, "preview_end"); sub_180001DC0(v50, this + 44); v13 = v49; v14 = v51; sub_180004310((__int64)v77, (__int64 *)&v13, 1, 2); v78 = 0; sub_180001E20(v47, "real_preview_intro"); sub_180001DC0(v48, this + 48); v13 = v47; v14 = v49; sub_180004310((__int64)v79, (__int64 *)&v13, 1, 2); v80 = 0; sub_180001E20(v45, "real_preview_begin"); sub_180001DC0(v46, this + 52); v13 = v45; v14 = v47; sub_180004310((__int64)v81, (__int64 *)&v13, 1, 2); v82 = 0; v13 = (char *)&v31; v31 = 0u; sub_180006150((void **)&v31 + 1, 0); LOBYTE(v31) = 3; v11 = wrapped_malloc(0x20u); v29 = v15; v30 = v11; *(_OWORD *)v11 = 0; v11[2] = 0; v11[3] = 0; sub_180002370(v11, "real_preview_end", 0x10u); *((_QWORD *)&v31 + 1) = v11; v32 = 0; sub_180001DC0(v33, this + 56); v13 = (char *)&v31; v14 = (char *)&v34; sub_180004A90(v83, &v13); sub_180001E20(v43, "CRC_all"); sub_180001DC0(v44, this + 60); v13 = v43; v14 = v45; sub_180004A90(v84, &v13); sub_180001E20(v41, "ignore_origin_num"); sub_180001DC0(v42, this + 64); v13 = v41; v14 = v43; sub_180004A90(v85, &v13); v13 = v59; v14 = &v86; sub_180004310(a2, (__int64 *)&v13, 1, 2); v16 = 1; sub_1800EB6FC(v59, 24, 15, sub_180005200); sub_1800EB6FC(v41, 24, 2, sub_180005200); sub_1800EB6FC(v43, 24, 2, sub_180005200); sub_1800EB6FC(&v31, 24, 2, sub_180005200); sub_1800EB6FC(v45, 24, 2, sub_180005200); sub_1800EB6FC(v47, 24, 2, sub_180005200); sub_1800EB6FC(v49, 24, 2, sub_180005200); sub_1800EB6FC(&v34, 24, 2, sub_180005200); sub_1800EB6FC(v51, 24, 2, sub_180005200); sub_1800EB6FC(v53, 24, 2, sub_180005200); sub_1800EB6FC(v55, 24, 2, sub_180005200); sub_1800EB6FC(v57, 24, 2, sub_180005200); sub_1800EB6FC(&v37, 24, 2, sub_180005200); sub_1800EB6FC(&v25, 24, 2, sub_180005200); sub_1800EB6FC(&v17, 24, 2, sub_180005200); sub_1800EB6FC(&v21, 24, 2, sub_180005200); return a2; }
|
函数 sub_180004310 中抛出的异常类型为 nlohmann::json_abi_v3_12_0::detail::type_error,表明 sub_180004310 大概率为 nlohmann::json 库的一部分。
可以猜测,FormatMetadata 函数中调用的别的函数可能也为 nlohmann::json 库的一部分,其中大量的 STL 容器的构造与析构的痕迹可以证明这一点。
其中出现的字符串常量应该为格式化为 json 字符串时的字段,因此 $14$ 个字段均找到其含义。剩余未命名的字段大概率为文件头的 magic 信息。
MuaFormat 信息如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class MUAFormat mua; { void* vftable; int magic; int type; int file_size; int sample_rate; int channel_num; int frame_size; int sample_num; int ignore_num; int preview_begin; int preview_end; int real_preview_intro; int real_preview_begin; int real_preview_end; int CRC_all; int ignore_origin_num; int ignore_frame; };
|
某个 mua 格式的文件第 $12\sim 16$ 字节依次为 00 00 BB 80,按照大端序读取为 $48000$,是一个典型的采样率,验证了我们的猜测。
sub_1800066E0 函数也抛出异常 nlohmann::json_abi_v3_12_0::detail::type_error,因此也为这个库的一部分,对上面构造出的包含 mua 文件元数据的 json 对象进行一些操作。在此处使用 nlohmann::json 库猜测为了便于在出现异常的时候结构化输出其元数据。
1 2 3 4 5 6 7 8 9 10 11 12 13
| frame_size = *(_DWORD *)(this + 172); ignore_frame = *(_DWORD *)(this + 180) / frame_size; *(_DWORD *)(this + 212) = ignore_frame; sample_frame = (frame_size + *(_DWORD *)(this + 176) - 1) / frame_size + 2; *(_DWORD *)this = sample_frame; if ( (ignore_frame & 3) != 0 ) { v46 = sub_1800047D0((__int64)Block, "ignore_frame % 4 != 0"); sub_180004BC0(&pExceptionObject, -6, v46); throw (milody::audio::format::MUAException *)&pExceptionObject; } total_frame = sample_frame + ignore_frame; *(_DWORD *)(this + 4) = total_frame;
|
进行了一些简单的计算,计算出了忽略的帧数和有采样的帧数并计算出了两者的总和,分别存储。
1 2 3 4 5 6 7 8 9
| for ( i = 0; (__int64)i < 60; i += 20LL ) { v14 = ((*((unsigned __int8 *)&v47 + i) ^ dword_1800F6260[(unsigned __int64)v12 >> 24] ^ (v12 << 8)) << 8) ^ dword_1800F6260[(unsigned __int64)(*((unsigned __int8 *)&v47 + i) ^ dword_1800F6260[(unsigned __int64)v12 >> 24] ^ (v12 << 8)) >> 24] ^ *((unsigned __int8 *)&v47 + i + 1); ... }
|
此部分依赖数据 dword_1800F6260,其前面两位为 0 和 0x4C11DB7,符合 CRC32 算法的特征,因此上述这部分疑似一个循环展开后的 CRC32 计算过程,最终的值存储在偏移 $32+8=40$ 字节处。
1 2 3 4 5 6 7 8 9 10
| v32 = *(_DWORD *)(this + 168) * frame_size * total_frame; v33 = v32; v34 = *(_QWORD *)(this + 16); if ( (unsigned int)v32 > (unsigned __int64)((*(_QWORD *)(this + 32) - v34) >> 1) ) { v35 = (*(_QWORD *)(this + 24) - v34) >> 1; v36 = aligned_malloc(2 * v32); memmove(v36, *(const void **)(this + 16), *(_QWORD *)(this + 24) - *(_QWORD *)(this + 16)); expand_vector(this + 16, (__int64)v36, v35, v33); }
|
1
| resize_vector(this + 40, 1276);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| v37 = (unsigned int)(*(_DWORD *)(this + 168) * *(_DWORD *)(this + 172)); v38 = *(_QWORD *)(this + 80); v39 = *(_QWORD *)(this + 88); v40 = (v39 - v38) >> 1; if ( v37 < v40 ) { v41 = v38 + 2 * v37; LABEL_23: *(_QWORD *)(this + 88) = v41; goto LABEL_24; } if ( v37 > v40 ) { v42 = (*(_QWORD *)(this + 96) - v38) >> 1; if ( v37 <= v42 ) { memset(*(void **)(this + 88), 0, 2 * (v37 - v40)); v41 = 2 * (v37 - v40) + v39; goto LABEL_23; } v43 = v42 >> 1; if ( v42 <= 0x7FFFFFFFFFFFFFFFLL - (v42 >> 1) ) { v44 = (unsigned int)(*(_DWORD *)(this + 168) * *(_DWORD *)(this + 172)); if ( v43 + v42 >= v37 ) v44 = v43 + v42; if ( v44 > 0x7FFFFFFFFFFFFFFFLL ) Concurrency::cancel_current_task(); } else { v44 = 0x7FFFFFFFFFFFFFFFLL; } v45 = aligned_malloc(2 * v44); memset((char *)v45 + 2 * v40, 0, 2 * (v37 - v40)); memmove(v45, *(const void **)(this + 80), *(_QWORD *)(this + 88) - *(_QWORD *)(this + 80)); expand_vector(this + 80, (__int64)v45, v37, v44); }
|
分别是三段 std::vector 的大小调整过程。
第一个最终扩容到可以存储 channel_num * frame_size * total_frame 个 $2$ 字节元素,这个 std::vector 存储于偏移 $16+32=48$ 字节处。
第二个调整到 $1276$ 字节,存储于偏移 $40+32=72$ 字节处。
第三个最终扩容到可以存储 channel_num * frame_size 个 $2$ 字节元素,存储于偏移 $80+32=112$ 字节处。
最后调用 OpusDecoderConstructor,这个函数似乎是偏移 64+32=96 字节处对象的构造函数,具体内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| __int64 __fastcall OpusDecoderConstructor(__int64 this, int sample_rate, unsigned int channel_num) { _DWORD *v6; __int64 *v8; __int64 v9; bool v10; int v11[4]; _QWORD v12[4]; __int128 v13; __int64 v14; __int128 v15; __int64 v16; bool v17; int v18;
v18 = 0; v6 = opus_decoder_create(sample_rate, channel_num, &v18); *(_QWORD *)this = v6; if ( v18 || !v6 ) { v8 = InitLogger(); v9 = sub_180019090((__int64)v8); v10 = *(_QWORD *)this == 0; v13 = 0u; v17 = v10; v14 = 0; v12[2] = 0; v12[0] = "[{} {}] Error: {}, Decoder is nullptr: {}"; v11[0] = 24; v12[1] = 41; v15 = 0u; v16 = 0; sub_18000B980( v9, &v15, 4, (__int64)v12, (__int64)"D:\\a\\Milody\\Milody\\src\\audio\\format\\milody_opus.cpp", v11, &v18, &v17); return 0xFFFFFFFFLL; } else { *(_DWORD *)(this + 8) = sample_rate; *(_DWORD *)(this + 12) = channel_num; return 0; } }
|
其中泄漏了源码路径 D:\a\Milody\Milody\src\audio\format\milody_opus.cpp。
这意味着,mua 文件与 opus 编码密切相关。
opus_decoder_create 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| _DWORD *__fastcall opus_decoder_create(int a1, unsigned int a2, int *a3) { unsigned int v7; int v8; _DWORD *v9; int v10; int v11; unsigned int v12; char *v13; unsigned int v14; int v15;
if ( a1 != 48000 && a1 != 24000 && a1 != 16000 && a1 != 12000 && a1 != 8000 || a2 - 1 > 1 ) { if ( a3 ) { *a3 = -1; return 0; } return 0; } v7 = a2 - 1; if ( (unsigned int)sub_1800BE980(&v14) ) { v8 = 0; } else { v14 = (v14 + 7) & 0xFFFFFFF8; v8 = v14 + 96 + sub_1800BABE0(a2); } v9 = malloc(v8); if ( !v9 ) { if ( a3 ) *a3 = -7; return 0; } if ( (a1 == 48000 || a1 == 24000 || a1 == 16000 || a1 == 12000 || a1 == 8000) && a2 - 1 <= 1 ) { if ( v7 > 1 || (unsigned int)sub_1800BE980(&v14) ) { v11 = 0; } else { v14 = (v14 + 7) & 0xFFFFFFF8; v11 = v14 + 96 + sub_1800BABE0(a2); } memset(v9, 0, v11); if ( (unsigned int)sub_1800BE980(&v15) ) { v10 = -3; } else { v12 = v15 + 7; v9[1] = 96; v9[2] = a2; v9[14] = a2; v9[12] = 0; v9[3] = a1; v15 = 8 * (v12 >> 3); v13 = (char *)v9 + v15 + 96; *v9 = v15 + 96; v9[6] = a1; v9[4] = a2; if ( (unsigned int)sub_1800BE990(v9 + 24) ) { v10 = -3; } else if ( (unsigned int)sub_1800BAC20(v13) ) { v10 = -3; } else { sub_1800BBE10(v13, 10016, 0); v9[17] = 0; v9[18] = a1 / 400; v9[13] = sub_1800BD160(); v10 = 0; } } } else { v10 = -1; } if ( a3 ) *a3 = v10; if ( v10 ) { free(v9); return 0; } return v9; }
|
观察这段代码,发现这个代码没有用到任何 C++ 特性(如不使用 class 封装,不使用 exception 而是返回错误码),这与先前的代码风格明显不符。
同时,a1 != 48000 && a1 != 24000 && a1 != 16000 && a1 != 12000 && a1 != 8000 判断了几个特定的采样频率。但是事实上在 JuceMUASourcePlayerConstructor 中还会判断采样率是否为 $48000$:
1 2 3 4 5 6 7 8 9 10 11
| if ( (unsigned int)sub_180007C30(this + 32) != 48000 ) { sub_1800049D0(v24, 1); v14 = sub_180001FC0((__int64)v25, "sample_rate 48000 expected, but "); v15 = sub_180007C30(this + 32); v16 = std::ostream::operator<<(v14, v15); sub_180001FC0(v16, " found"); v17 = sub_18000A540(v24, v26); sub_180004FE0(pExceptionObject, v17); throw (std::runtime_error *)pExceptionObject; }
|
这些发现表明 opus_decoder_create 函数很可能是外部库函数。
源代码名为 milody_opus.cpp,同时开发组的 Github 中 fork 了仓库 xiph/opus:

推断 opus_decoder_create 为这个库的一部分。
经过对比,为如下片段经过内联后的结果:

故函数 OpusDecoderConstructor 在偏移 $96$ 处创建的对象存储了 opus_decoder_create 返回的 OpusDecoder * 以及采样率和通道数。
再回到 JuceMUASourcePlayerConstructor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| if ( (unsigned int)sub_180007AD0(this + 32) != 2 ) { sub_1800049D0(v24, 1); v18 = sub_180001FC0((__int64)v25, "channel_num 2 expected, but "); v19 = sub_180007AD0(this + 32); v20 = std::ostream::operator<<(v18, v19); sub_180001FC0(v20, " found"); v21 = sub_18000A540(v24, v26); sub_180004FE0(pExceptionObject, v21); throw (std::runtime_error *)pExceptionObject; } if ( (unsigned int)sub_180007C30(this + 32) != 48000 ) { sub_1800049D0(v24, 1); v14 = sub_180001FC0((__int64)v25, "sample_rate 48000 expected, but "); v15 = sub_180007C30(this + 32); v16 = std::ostream::operator<<(v14, v15); sub_180001FC0(v16, " found"); v17 = sub_18000A540(v24, v26); sub_180004FE0(pExceptionObject, v17); throw (std::runtime_error *)pExceptionObject; }
|
对采样率和通道数进行限制。
1 2 3 4 5 6 7 8
| v11 = wrapped_malloc(3456u); if ( v11 ) v7 = AudioSourcePlayerConstructor((__int64)v11); v12 = *(void (__fastcall ****)(_QWORD, __int64))(this + 280); *(_QWORD *)(this + 280) = v7; if ( v12 ) (**v12)(v12, 1); sub_18004C220(*(_QWORD *)(this + 280), v3);
|
然后分配了 $3456$ 字节的空间,调用其构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 __fastcall AudioSourcePlayerConstructor(__int64 this) { *(_QWORD *)this = &juce::AudioSourcePlayer::`vftable; unknown_libname_2(this + 8); *(_QWORD *)(this + 56) = 0; *(_QWORD *)(this + 64) = 0; *(_DWORD *)(this + 72) = 0; *(_QWORD *)(this + 3168) = this + 3184; *(_QWORD *)(this + 3152) = 0; *(_QWORD *)(this + 3160) = 0; *(_QWORD *)(this + 3176) = 0; *(_BYTE *)(this + 3440) = 0; *(_DWORD *)(this + 3448) = 1065353216; *(_DWORD *)(this + 3452) = 1065353216; return this; }
|
表明这个大小 $3456$ 字节的对象为 juce::AudioSourcePlayer。
其指针存储于偏移 $280$ 字节处。
现在,我们目前知道的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| class JuceMUASourcePlayer { void* vftable; std::vector<std::byte> rawData; { std::byte* begin; std::byte* data_end; std::byte* buf_end; } class MUAFileInfo info; { int sample_frame; int total_frame; int crc_like_value; Unknown; std::vector<short> pcm_data; { short* begin; short* data_end; short* buf_end; } std::vector<std::byte> unknown_data; { std::byte* begin; std::byte* data_end; std::byte* buf_end; } class opus_decoder decoder; { OpusDecoder*decoder; int sample_rate; int channel_num; } std::vector<short> data2; { short* begin; short* data_end; short* buf_end; } class Record rec; { std::byte* data = rawData.begin; std::size_t size; std::size_t offset; } Unknown; RTL_SRWLOCK * lock; class MUAFormat mua; { void* vftable; int magic; int type; int file_size; int sample_rate; int channel_num; int frame_size; int sample_num; int ignore_num; int preview_begin; int preview_end; int real_preview_intro; int real_preview_begin; int real_preview_end; int CRC_all; int ignore_origin_num; int ignore_frame; }; }; Unknown; AudioSourcePlayer* player; Unknown; };
|
解码函数
再看 MilodyAudioJuceMUASourcePlayerDecodeFullSong:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| __int64 __fastcall MilodyAudioJuceMUASourcePlayerDecodeFullSong(__int64 this, __int64 Cancellation_Token) { __int64 result; __int64 *inited; __int64 v4; __int64 *v5; __int64 v6; __int64 *v7; int v8; _BYTE v9[24]; __int64 v10; milody::audio::format::MUAException *v11; std::runtime_error *v12; const char *v13; __int64 v14; __int64 v15; int v16; int v17; __int64 v18; __int128 v19; __int64 v20; __int64 v21;
try { DecodeFullSong(this, Cancellation_Token); result = 0; } catch ( milody::audio::format::MUAException *v11 ) { inited = InitLogger(); v4 = sub_180019090((__int64)inited); (*(void (__fastcall **)(milody::audio::format::MUAException *))(*(_QWORD *)v11 + 8LL))(v11); v13 = "{}, {}"; v14 = 6; v15 = 0; v16 = 0; v17 = 0; v18 = 0; v19 = 0u; v20 = 0; sub_18001A810(v4, &v19, 4, (__int64)&v13, (__int64)"MilodyAudioJuceMUASourcePlayerDecodeFullSong", &v21); return v10; } catch ( std::runtime_error *v12 ) { v5 = InitLogger(); v6 = sub_180019090((__int64)v5); (*(void (__fastcall **)(std::runtime_error *))(*(_QWORD *)v12 + 8LL))(v12); v13 = "{}, {}"; v14 = 6; v15 = 0; v16 = 0; v17 = 0; v18 = 0; v19 = 0u; v20 = 0; sub_18001A810(v6, &v19, 4, (__int64)&v13, (__int64)"MilodyAudioJuceMUASourcePlayerDecodeFullSong", &v21); return -11709394; } catch ( ... ) { v7 = InitLogger(); v8 = sub_180019090((__int64)v7); v13 = "{}, {}"; v14 = 6; v15 = 0; v16 = 0; v17 = 0; v18 = 0; v19 = 0u; v20 = 0; sub_18001A620( v8, (unsigned int)v9 + 112, 4, (unsigned int)v9 + 64, (__int64)"MilodyAudioJuceMUASourcePlayerDecodeFullSong", (__int64)"unexpected exception"); return -11709394; } return result; }
|
导出函数仍然是对类中的成员函数外封装了异常处理。
内层 DecodeFullSong 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| void __fastcall DecodeFullSong(__int64 this, __int64 Cancellation_Token) { __int64 v4; RTL_SRWLOCK *v5; __int64 *inited; __int64 v7; __int64 v8; __int64 v9; int v10; unsigned __int8 v11; unsigned __int8 v12; __int64 v13; unsigned int ValidNum; __int64 v15; size_t v16; void *v17; unsigned int v18; unsigned int v19; __int64 v20; void *v21; __int64 v22; void (__fastcall ***v23)(_QWORD, __int64); void *v24; __int128 v25; __int64 v26; __int64 v27; void *Buf[2]; char *v29; __int128 v30; __int64 v31; __int128 v32; __int64 (__fastcall **v33)(); __int64 *v34; void **v35; __int64 v36; __int64 (__fastcall ***v37)(); __int64 v38; char v39; _OWORD v40[4]; __int64 v41; __int64 v42;
v4 = 0; v5 = (RTL_SRWLOCK *)(this + 272); v38 = this + 272; v39 = 1; AcquireSRWLockShared((PSRWLOCK)(this + 272)); if ( *(_QWORD *)(this + 320) ) { inited = InitLogger(); v7 = sub_180019090((__int64)inited); v8 = 0; do ++v8; while ( aFullsongaudios[v8] ); v30 = 0; v31 = 0; DWORD2(v30) = 0; v9 = HIDWORD(*((_QWORD *)&v30 + 1)); v10 = *(_DWORD *)(v7 + 64); v11 = sub_1800E5190(v7 + 136); v12 = v11; if ( v10 <= 2 || v11 ) { *(_QWORD *)&v32 = "fullSongAudioSource already ready"; *((_QWORD *)&v32 + 1) = v8; v13 = v7 + 8; if ( *(_QWORD *)(v7 + 32) > 0xFu ) v13 = *(_QWORD *)(v7 + 8); *(_QWORD *)&v30 = 0; DWORD2(v30) = 0; HIDWORD(v30) = v9; v31 = 0; v40[0] = v32; *(_QWORD *)&v32 = v13; *((_QWORD *)&v32 + 1) = *(_QWORD *)(v7 + 24); v25 = v30; v26 = 0; sub_1800E3AA0((__int64)&v33, &v25, &v32, 2, v40); sub_1800EA5B0(v7, &v33, v10 <= 2, v12); } } else { ValidNum = CalcValidNum(this + 32); v15 = ValidNum; *(_OWORD *)Buf = 0; v29 = 0; if ( ValidNum ) { v16 = 2LL * ValidNum; Buf[0] = aligned_malloc(v16); v29 = (char *)Buf[0] + v16; memset(Buf[0], 0, 2 * v15); Buf[1] = (char *)Buf[0] + v16; v4 = 0; } v25 = 0; *(_QWORD *)&v25 = aligned_malloc(0x20u); v26 = 16; v27 = 31; strcpy((char *)v25, "decode full song"); v33 = std::X$$V::Z::_Func_impl_no_alloc<`milody::audio::JuceMUASourcePlayer::DecodeFullSong::`2::_lambda_1_,milody::context::AXPEAVContext * const>::`vftable; v34 = (__int64 *)this; v35 = Buf; v36 = Cancellation_Token; v37 = &v33; Function_Call((__int64)&v33, (__int64)&v25); v17 = wrapped_malloc(0x138u); v42 = (__int64)v17; if ( v17 ) { v18 = (unsigned int)v15 / (unsigned int)sub_180007AD0(this + 32); v19 = sub_180007AD0(this + 32); LOBYTE(v20) = 1; v4 = sub_180015980(v17, v19, v18, v20); } v41 = v4; v21 = wrapped_malloc(0x138u); v42 = (__int64)v21; if ( v21 ) v22 = sub_18004EAC0((__int64)v21, v4, 0, 0); else v22 = 0; v42 = v22; v26 = 14; v27 = 15; strcpy((char *)&v25, "copy full song"); HIBYTE(v25) = 0; v33 = std::X$$V::Z::_Func_impl_no_alloc<`milody::audio::JuceMUASourcePlayer::DecodeFullSong::`2::_lambda_2_,milody::context::AXPEAVContext * const>::`vftable; v34 = &v41; v35 = Buf; v37 = &v33; Function_Call((__int64)&v33, (__int64)&v25); (*(void (__fastcall **)(__int64, _QWORD))(*(_QWORD *)v22 + 32LL))(v22, 0); *(_QWORD *)(this + 256) = v41; v23 = *(void (__fastcall ****)(_QWORD, __int64))(this + 320); *(_QWORD *)(this + 320) = v22; if ( v23 ) (**v23)(v23, 1); v24 = Buf[0]; if ( Buf[0] ) { if ( (unsigned __int64)(2 * ((v29 - (char *)Buf[0]) >> 1)) >= 0x1000 ) { v24 = (void *)*((_QWORD *)Buf[0] - 1); if ( (unsigned __int64)((char *)Buf[0] - (char *)v24 - 8) > 0x1F ) invoke_watson(0, 0, 0, 0, 0); } j_j_free(v24); *(_OWORD *)Buf = 0; v29 = 0; } } ReleaseSRWLockShared(v5); }
|
首先加锁,锁的位置在偏移 $272$ 字节处,证明这里还有另一个锁。这是整个对象的锁,而上文发现的第一个锁应当仅针对偏移 $32$ 字节处的子对象。
然后检查偏移 $320$ 字节处是否有值,如果有值则打印日志并跳过实际的解码操作。这里应当存储的是解码后的音频数据,避免重复解码。
函数 CalcValidNum 如下:
1 2 3 4 5 6 7 8 9
| __int64 __fastcall CalcValidNum(__int64 this) { unsigned int v2;
AcquireSRWLockShared((PSRWLOCK)(this + 136)); v2 = *(_DWORD *)(this + 168) * (*(_DWORD *)(this + 176) - *(_DWORD *)(this + 208)); ReleaseSRWLockShared((PSRWLOCK)(this + 136)); return v2; }
|
对照之前已经得到的结构,这个函数计算了 channel_num * (sample_num - ignore_origin_num),应当是解码后音频所需的空间。然后根据计算得到的值分配所需空间。
1 2 3 4 5 6
| v33 = std::X$$V::Z::_Func_impl_no_alloc<`milody::audio::JuceMUASourcePlayer::DecodeFullSong::`2::_lambda_1_,milody::context::AXPEAVContext * const>::`vftable; v34 = (__int64 *)this; v35 = Buf; v36 = Cancellation_Token; v37 = &v33; Function_Call((__int64)&v33, (__int64)&v25);
|
这部分看样子是在栈上构造一个 std::function 对象,内部存储一个 lambda 表达式。由于这个 lambda 表达式体积较小,因此不进行堆内存的分配。
不进行堆分配的 std::function 内存布局如下:最开头是虚表,接下来存储可调用对象,最后在末尾八字节存储被调用函数相关信息(此处直接存储了一份虚表的指针)。
FunctionCall 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| __int64 __fastcall Function_Call(__int64 this, __int64 a2) { __int64 v4; __int64 *inited; int v6; __int64 v7; __int64 v8; double v10[2]; _QWORD v11[4]; __int128 v12; __int64 v13; __int128 v14; __int64 v15; __int64 v16; __int64 v17;
v16 = this; v17 = a2; perf_monitor(v10); v4 = *(_QWORD *)(this + 56); if ( !v4 ) { std::_Xbad_function_call(); JUMPOUT(0x180016E5DLL); } (*(void (__fastcall **)(__int64))(*(_QWORD *)v4 + 16LL))(v4); perf_monitor(v11); inited = InitLogger(); v6 = sub_180019090((__int64)inited); v10[0] = (double)(int)((double)SLODWORD(v11[0]) - (double)SLODWORD(v10[0])) / 1000000000.0; v11[2] = 0; v12 = 0u; v13 = 0; v11[0] = "{}: {}"; v11[1] = 6; v14 = 0u; v15 = 0; sub_180015510(v6, (unsigned int)&v14, 1, (unsigned int)v11, a2, (__int64)v10); v8 = *(_QWORD *)(this + 56); if ( v8 ) { LOBYTE(v7) = v8 != this; (*(void (__fastcall **)(__int64, __int64))(*(_QWORD *)v8 + 32LL))(v8, v7); *(_QWORD *)(this + 56) = 0; } return sub_180005F80(a2); }
|
这个函数负责监控性能信息,并在日志中输出所用时间。调用的是虚表偏移 $16$ 字节的函数。对应函数 DecodeStarter:
1 2 3 4 5 6 7
| void __fastcall DecodeStarter(__int64 this) { DecodeMain( *(_QWORD *)(this + 8) + 32LL, *(unsigned __int8 (__fastcall ****)(_QWORD))(this + 24), **(char ***)(this + 16)); }
|
负责将 lambda 表达式捕获的变量传入 DecodeMain,依次传入了偏移 $32$ 字节的子对象的指针、Cancellation_Token 和输出缓冲区的地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| void __fastcall DecodeMain(__int64 this, unsigned __int8 (__fastcall ***Cancellation_Token)(_QWORD), char *OutputBuf) { int channel_num; int preview_begin; unsigned __int64 v8; int channel_num_1; int preview_begin_1; size_t v11; _QWORD *v12; _BYTE v13[32]; _BYTE pExceptionObject[32];
DecodeCore(this, (unsigned __int8 (__fastcall ***)(_QWORD, __int64))Cancellation_Token, *(_DWORD *)(this + 4)); AcquireSRWLockShared((PSRWLOCK)(this + 136)); if ( (**Cancellation_Token)(Cancellation_Token) ) { v12 = (_QWORD *)sub_1800047D0((__int64)v13, "canceled"); sub_180004BC0((__int64)pExceptionObject, -100, v12); throw (milody::audio::format::MUAException *)pExceptionObject; } channel_num = *(_DWORD *)(this + 168); preview_begin = *(_DWORD *)(this + 184); v8 = *(_QWORD *)(this + 16) + 2 * ((unsigned int)(*(_DWORD *)(this + 12) * channel_num) + (unsigned int)(*(_DWORD *)(this + 180) * channel_num) + (unsigned __int64)(unsigned int)(channel_num * (*(_DWORD *)(this + 176) - preview_begin))); memcpy( OutputBuf, (const void *)(v8 + 2LL * (unsigned int)(*(_DWORD *)(this + 208) * channel_num)), 2LL * (unsigned int)(channel_num * preview_begin)); channel_num_1 = *(_DWORD *)(this + 168); preview_begin_1 = *(_DWORD *)(this + 184); v11 = 2LL * (unsigned int)(channel_num_1 * (*(_DWORD *)(this + 176) - preview_begin_1)); memcpy(&OutputBuf[2 * channel_num_1 * preview_begin_1], (const void *)(v8 - v11), v11); ReleaseSRWLockShared((PSRWLOCK)(this + 136)); }
|
紧接着,DecodeMain 又调用了 DecodeCore。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| __int64 __fastcall DecodeCore( __int64 this, unsigned __int8 (__fastcall ***Cancellation_Token)(_QWORD, __int64), int total_frame) { unsigned int v4; __int64 result; __int64 v7; unsigned int i; _QWORD *v9; _BYTE v10[32]; _BYTE pExceptionObject[32];
v4 = *(_DWORD *)(this + 128); v7 = (*(_DWORD *)(this + 180) >> 2) % *(_DWORD *)(this + 172); result = (*(_DWORD *)(this + 180) >> 2) / *(_DWORD *)(this + 172); for ( i = result + total_frame; v4 < i; ++v4 ) { if ( (**Cancellation_Token)(Cancellation_Token, v7) ) { v9 = (_QWORD *)sub_1800047D0((__int64)v10, "canceled"); sub_180004BC0((__int64)pExceptionObject, -100, v9); throw (milody::audio::format::MUAException *)pExceptionObject; } result = DecodeFrame(this, v4); } return result; }
|
这个函数先进行了一些小计算,将传入的 total_frame 加上了 (ignore_num / 4) / frame_size 作为需要解码的帧的总数,然后调用 DecodeFrame 解码每一帧。

但是坏消息是 DecodeFrame 这个函数根本无法被 IDA 反编译。
不过好在我们有多样的反编译工具,Ghidra 很好地反编译了这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
void DecodeFrame(longlong this,uint idx,undefined8 param_3,undefined8 param_4)
{ longlong lVar1; basic_ostream<> *pbVar2; undefined8 *puVar3; longlong *plVar4; ushort uVar5; void *_Memory; uint uVar6; int iVar7; int iVar8; undefined *puVar9; ulonglong uVar10; undefined *puVar11; ulonglong uVar12; ulonglong uVar13; uint uVar14; double dVar15; undefined1 auStackY_248 [32]; uint local_218; uint local_214; undefined *local_208; undefined8 uStack_200; undefined8 local_1f8; undefined8 local_1e8; undefined4 uStack_1e0; undefined4 uStack_1dc; undefined8 local_1d8; undefined8 local_1c8; undefined8 uStack_1c0; undefined8 local_1b8; PSRWLOCK local_1a8; undefined1 local_1a0; int iStack_19c; undefined *local_198 [2]; undefined *local_188; undefined **local_180; basic_iostream<> local_178 [96]; undefined8 local_118; undefined4 local_110; basic_ios<> local_100 [104]; void *local_98 [3]; ulonglong local_80; ulonglong local_78; int channel_num; uint frame_size; local_78 = DAT_1801e1b00 ^ (ulonglong)auStackY_248; uVar13 = 0; local_214 = 0; local_1a8 = (PSRWLOCK)(this + 136); AcquireSRWLockExclusive(local_1a8); local_1a0 = 1; if ((idx == *(uint *)(this + 128)) && (idx < *(uint *)(this + 4))) { *(uint *)(this + 128) = *(uint *)(this + 128) + 1; ReadFromRawData(this,&local_218,2,param_4); uVar5 = (ushort)local_218 >> 8 | (ushort)local_218 << 8; uVar10 = (ulonglong)uVar5; uVar6 = *(uint *)(this + 8) << 8 ^ *(uint *)(&DAT_1800f6260 + (ulonglong)(*(uint *)(this + 8) >> 0x18) * 4); *(uint *)(this + 8) = (uVar6 ^ local_218 & 0xff) << 8 ^ *(uint *)(&DAT_1800f6260 + (ulonglong)(uVar6 >> 0x18) * 4) ^ local_218 >> 8 & 0xff; if ((ulonglong)(*(longlong *)(this + 48) - *(longlong *)(this + 40)) < uVar10) { resize_vector((longlong *)(this + 40),uVar10); } ReadFromRawData(this,*(void **)(this + 40),uVar10,param_4); uVar6 = *(uint *)(this + 8); uVar12 = uVar13; if (uVar5 != 0) { do { uVar6 = (uint)(*(byte **)(this + 40))[uVar12] ^ *(uint *)(&DAT_1800f6260 + (ulonglong)(uVar6 >> 0x18) * 4) ^ uVar6 << 8; uVar12 = uVar12 + 1; } while ((longlong)uVar12 < (longlong)uVar10); } *(uint *)(this + 8) = uVar6; OpusFrameDecode((undefined8 *)(this + 64),*(byte **)(this + 40),(uint)uVar5,*(int *)(this + 1 72) ,*(longlong *)(this + 80)); concat_vector((longlong *)(this + 16),*(void **)(this + 24),*(void **)(this + 80), *(longlong *)(this + 88) - (longlong)*(void **)(this + 80) >> 1); channel_num = *(int *)(this + 128); if (channel_num == *(int *)(this + 212)) { local_208 = (undefined *)0xffffffffffffffff; if ((*(uint *)(this + 180) & 3) != 0) { plVar4 = FUN_1800047d0(local_98,"Pad_Sn_Error2"); FUN_180004bc0(&local_1c8,0xfffffffffffffffa,plVar4); _CxxThrowException(&local_1c8,(ThrowInfo *)&DAT_1801cc718); } uVar6 = *(uint *)(this + 180) >> 2; frame_size = uVar6; local_214 = uVar6; if (uVar6 != 0) { do { local_218 = frame_size; puVar9 = (undefined *)0x0; uVar14 = (uint)uVar13; frame_size = *(uint *)(this + 172); lVar1 = *(longlong *)(this + 16); channel_num = *(int *)(this + 168); puVar11 = puVar9; do { iVar8 = (int)puVar9; dVar15 = sin((((double)iVar8 * 6.28318530718) / (double)frame_size) * 4.0); iVar7 = (int)*(short *)(lVar1 + (ulonglong)((iVar8 + uVar14) * channel_num) * 2) - (int)(short)(int)(dVar15 * 10000.0); puVar11 = puVar11 + iVar7 * iVar7; puVar9 = (undefined *)(ulonglong)(iVar8 + 1U); } while (iVar8 + 1U < uVar6 * 2); if (puVar11 < local_208) { local_218 = uVar14; local_208 = puVar11; } uVar13 = (ulonglong)(uVar14 + 1); frame_size = local_218; } while (uVar14 + 1 < uVar6 * 2); channel_num = *(int *)(this + 128); uVar6 = local_218; } uVar6 = uVar6 - local_214; *(uint *)(this + 12) = uVar6; frame_size = -uVar6; if ((int)-uVar6 < 0) { frame_size = uVar6; } if (*(uint *)(this + 172) < frame_size) { plVar4 = FUN_1800047d0(local_98,"Offset_Too_Big"); FUN_180004bc0(&local_1c8,0xfffffffffffffff9,plVar4); _CxxThrowException(&local_1c8,(ThrowInfo *)&DAT_1801cc718); } } if (channel_num == *(int *)(this + 4)) { uVar6 = *(uint *)(this + 8) << 8 ^ *(uint *)(&DAT_1800f6660 + (ulonglong)(*(uint *)(this + 8) >> 0x18) * 4); uVar6 = uVar6 << 8 ^ *(uint *)(&DAT_1800f6660 + (ulonglong)(uVar6 >> 0x18) * 4); uVar6 = uVar6 << 8 ^ *(uint *)(&DAT_1800f6660 + (ulonglong)(uVar6 >> 0x18) * 4); uVar6 = uVar6 << 8 ^ *(uint *)(&DAT_1800f6660 + (ulonglong)(uVar6 >> 0x18) * 4); *(uint *)(this + 8) = uVar6; if (uVar6 != *(uint *)(this + 204)) { local_198[0] = &DAT_1800f5cb0; local_188 = &DAT_1800f5cb8; std::basic_ios<>::basic_ios<>(local_100); local_214 = 1; std::basic_iostream<>::basic_iostream<> ((basic_iostream<> *)local_198,(basic_streambuf<> *)&local_180); *(undefined ***)((longlong)local_198 + (longlong)*(int *)(local_198[0] + 4)) = std::basic_stringstream<>::vftable; *(int *)((longlong)&iStack_19c + (longlong)*(int *)(local_198[0] + 4)) = *(int *)(local_198[0] + 4) + -0x98; std::basic_streambuf<>::basic_streambuf<>((basic_streambuf<> *)&local_180); local_180 = std::basic_stringbuf<>::vftable; local_118 = 0; local_110 = 0; pbVar2 = FUN_180001fc0((basic_ostream<> *)&local_188,"mua crc verification failure: "); pbVar2 = std::basic_ostream<>::operator<<(pbVar2,*(uint *)(this + 0xcc)); pbVar2 = FUN_180001fc0(pbVar2," saved in mua file, but "); pbVar2 = std::basic_ostream<>::operator<<(pbVar2,*(uint *)(this + 8)); FUN_180001fc0(pbVar2," calculated"); puVar3 = FUN_180018f20(); plVar4 = (longlong *)FUN_180019090(puVar3); FUN_18000a330((basic_streambuf<> *)&local_180,(longlong *)local_98); local_1f8 = 0; local_1e8 = 0; uStack_1e0 = 0; uStack_1dc = 0; local_1d8 = 0; local_208 = &DAT_1800f6c00; uStack_200 = 2; uStack_1c0 = 0; local_1c8 = 0; local_1b8 = 0; FUN_180003c20(plVar4,&local_1c8,4,(double *)&local_208,local_98); if (0xf < local_80) { _Memory = local_98[0]; if ((0xfff < local_80 + 1) && (_Memory = *(void **)((longlong)local_98[0] + -8), 0x1f < (ulonglong)((longlong)local_98[0] + (-8 - (longlong)_Memory)))) { _invoke_watson((wchar_t *)0x0,(wchar_t *)0x0,(wchar_t *)0x0,0,0); } free(_Memory); } *(undefined ***)((longlong)local_198 + (longlong)*(int *)(local_198[0] + 4)) = std::basic_stringstream<>::vftable; *(int *)((longlong)&iStack_19c + (longlong)*(int *)(local_198[0] + 4)) = *(int *)(local_198[0] + 4) + -0x98; FUN_180005240((basic_streambuf<> *)&local_180); std::basic_iostream<>::~basic_iostream<>(local_178); std::basic_ios<>::~basic_ios<>(local_100); } local_218 = local_218 & 0xffff0000; FUN_180002da0((longlong *)(this + 16), (*(longlong *)(this + 24) - *(longlong *)(this + 16) >> 1) + (ulonglong)*(uint *)(this + 172),(short *)&local_218); } } ReleaseSRWLockExclusive((PSRWLOCK)(this + 136)); return; }
|
多了两个参数,如果假定后两个参数是多余的,后续分析可以进行下去,
判断传入的需解码的编号必须比 total_frame 小,因此上一个函数中加上 (ignore_num / 4) / frame_size 并无用处,可能是一些保险措施。
同时在偏移 $128+32=160$ 字节处维护了一个下一个应该被解码的帧编号,保证解码是按照顺序进行的。
对于每一帧先按照大端序取出两字节,作为这一帧的大小。取出这么多字节作为原始数据存入偏移为 $40+32=72$ 字节处的 std::vector 中。
然后调用 OpusFrameDecode 解码该帧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| __int64 __fastcall OpusFrameDecode(__int64 *st, _BYTE *data, int len, int frame_size, __int64 pcm) { __int64 v5; __int64 result; unsigned int v7; __int64 *inited; int v9; __int64 *v10; __int64 v11; __int64 v12; __int64 v13; __int64 v14; _QWORD *v15; int v16; int v17; _DWORD v18[4]; const char *v19; __int64 v20; _BYTE pExceptionObject[32]; _BYTE v22[32]; _BYTE v23[32]; _BYTE v24[32];
v17 = len; v5 = *st; if ( !v5 ) return 0xFFFFFFFFLL; result = opus_decode(v5, data, len, pcm, frame_size, 0); v7 = result; v18[0] = result; if ( (int)result < 0 ) { inited = InitLogger(); v9 = sub_180019090((__int64)inited); v16 = 39; v19 = "[{} {}] Frame size error: {}, Input length: {}\n"; v20 = 47; sub_18000B630( v9, (unsigned int)&v19, (unsigned int)"D:\\a\\Milody\\Milody\\src\\audio\\format\\milody_opus.cpp", (unsigned int)&v16, (__int64)v18, (__int64)&v17); v10 = InitLogger(); v11 = sub_180019090((__int64)v10); v16 = -4; v19 = "OPUS_INVALID_PACKET: {}\n"; v20 = 24; sub_18000B6D0(v11, &v19, &v16); v12 = sub_18000AF50(v22, v7); v13 = sub_1800047D0((__int64)v23, "decoder error code: "); v14 = sub_18000B380(v24, "D:\\a\\Milody\\Milody\\src\\audio\\format\\milody_opus.cpp", v13); v15 = (_QWORD *)sub_180002230(&v19, v14, v12); sub_180004BC0((__int64)pExceptionObject, -3, v15); throw (milody::audio::format::MUAException *)pExceptionObject; } return result; }
|
又是熟悉的 milody_opus.cpp,因此这个函数估计是对 opus 库中的解码函数进行简单封装。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| __int64 __fastcall opus_decode(__int64 st, _BYTE *data, int len, __int64 pcm, int frame_size, unsigned int decode_fec) { int v6; int v12; int v13; unsigned __int64 v14; __int64 v15; void *v16; int v17; unsigned int v18; _QWORD v19[6];
v6 = frame_size; if ( frame_size <= 0 ) return 0xFFFFFFFFLL; if ( data && len > 0 && !decode_fec ) { v12 = sub_1800B7840(st, data, len); if ( v12 <= 0 ) return 4294967292LL; if ( frame_size < v12 ) v12 = frame_size; v6 = v12; } v13 = *(_DWORD *)(st + 8); if ( v13 != 1 && v13 != 2 ) sub_1800BC740( "assertion failed: st->channels == 1 || st->channels == 2", "D:\\a\\Milody\\Milody\\ext\\opus\\src\\opus_decoder.c", 879); v14 = 4LL * *(_DWORD *)(st + 8) * v6; v15 = v14 + 15; if ( v14 + 15 < v14 ) v15 = 0xFFFFFFFFFFFFFF0LL; v16 = alloca(v15 & 0xFFFFFFFFFFFFFFF0uLL); v17 = sub_1800B71A0(st, data, len, (__int64)v19, v6, decode_fec, 0, 0, 1); v18 = v17; if ( v17 > 0 ) sub_1800BE9F0(v19, pcm, (unsigned int)(*(_DWORD *)(st + 8) * v17)); return v18; }
|
注意看,这里有个 assert 泄露了源代码及其行号。
在仓库中定位这个文件及行号。

确认了就是 opus_decode 函数,逻辑一致,但是行号有偏差。可能是因为实际进行编译的代码在仓库中的基础上进行了少许修改。
解码完的数据存储在偏移 $80+32=112$ 字节处的 std::vector 中,紧接着被拼接入偏移在 $16+32=48$ 字节处的 std::vector 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| channel_num = *(int *)(this + 128); if (channel_num == *(int *)(this + 212)) { local_208 = (undefined *)0xffffffffffffffff; if ((*(uint *)(this + 180) & 3) != 0) { plVar4 = FUN_1800047d0(local_98,"Pad_Sn_Error2"); FUN_180004bc0(&local_1c8,0xfffffffffffffffa,plVar4); _CxxThrowException(&local_1c8,(ThrowInfo *)&DAT_1801cc718); } uVar6 = *(uint *)(this + 180) >> 2; frame_size = uVar6; local_214 = uVar6; if (uVar6 != 0) { do { local_218 = frame_size; puVar9 = (undefined *)0x0; uVar14 = (uint)uVar13; frame_size = *(uint *)(this + 172); lVar1 = *(longlong *)(this + 16); channel_num = *(int *)(this + 168); puVar11 = puVar9; do { iVar8 = (int)puVar9; dVar15 = sin((((double)iVar8 * 6.28318530718) / (double)frame_size) * 4.0); iVar7 = (int)*(short *)(lVar1 + (ulonglong)((iVar8 + uVar14) * channel_num) * 2) - (int)(short)(int)(dVar15 * 10000.0); puVar11 = puVar11 + iVar7 * iVar7; puVar9 = (undefined *)(ulonglong)(iVar8 + 1U); } while (iVar8 + 1U < uVar6 * 2); if (puVar11 < local_208) { local_218 = uVar14; local_208 = puVar11; } uVar13 = (ulonglong)(uVar14 + 1); frame_size = local_218; } while (uVar14 + 1 < uVar6 * 2); channel_num = *(int *)(this + 128); uVar6 = local_218; } uVar6 = uVar6 - local_214; *(uint *)(this + 12) = uVar6; frame_size = -uVar6; if ((int)-uVar6 < 0) { frame_size = uVar6; } if (*(uint *)(this + 172) < frame_size) { plVar4 = FUN_1800047d0(local_98,"Offset_Too_Big"); FUN_180004bc0(&local_1c8,0xfffffffffffffff9,plVar4); _CxxThrowException(&local_1c8,(ThrowInfo *)&DAT_1801cc718); } }
|
此处先判断当前解码到的编号是否和 ignore_frame 相同。
如果已经相同,则会找到一个最佳的 offset 使波形与一个正弦波的差值的平方和最小。这个正弦波应当是在编码的时候放入辅助定位的。此 offset 被存入偏移 $12+32=44$ 字节处。
后续部分则是继续进行 CRC32 校验以及各种异常和错误处理。
现在看 DecodeMain 中的后半部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| channel_num = *(_DWORD *)(this + 168); preview_begin = *(_DWORD *)(this + 184); v8 = *(_QWORD *)(this + 16) + 2 * ((unsigned int)(*(_DWORD *)(this + 12) * channel_num) + (unsigned int)(*(_DWORD *)(this + 180) * channel_num) + (unsigned __int64)(unsigned int)(channel_num * (*(_DWORD *)(this + 176) - preview_begin))); memcpy( OutputBuf, (const void *)(v8 + 2LL * (unsigned int)(*(_DWORD *)(this + 208) * channel_num)), 2LL * (unsigned int)(channel_num * preview_begin)); channel_num_1 = *(_DWORD *)(this + 168); preview_begin_1 = *(_DWORD *)(this + 184); v11 = 2LL * (unsigned int)(channel_num_1 * (*(_DWORD *)(this + 176) - preview_begin_1)); memcpy(&OutputBuf[2 * channel_num_1 * preview_begin_1], (const void *)(v8 - v11), v11);
|
整体是进行了一些复杂的内存复制,大概是需要删除 ignore_num 指定的正弦波所在的部分,再删除 ignore_orogin_num 指定的部分。最后还需要将被提前的 preview 所在的后半部分还原到后面。
后续还进行了标记为 copy full song 的任务,分析的过程类似这个任务,实质是将整数采样转化为浮点采样并将结果指针存入偏移 $244$ 字节处。
到此,mua 文件的解码方式已经明确,同时已经探明的结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| class JuceMUASourcePlayer { void* vftable; std::vector<std::byte> rawData; { std::byte* begin; std::byte* data_end; std::byte* buf_end; } class MUAFileInfo info; { int sample_frame; int total_frame; int crc_like_value; int offset; std::vector<short> pcm_data; { short* begin; short* data_end; short* buf_end; } std::vector<std::byte> opus_frame_temp_buf; { std::byte* begin; std::byte* data_end; std::byte* buf_end; } struct opus_decoder decoder; { OpusDecoder*decoder; int sample_rate; int channel_num; } std::vector<short> decoded_frame_temp_buf; { short* begin; short* data_end; short* buf_end; } struct Record rec; { std::byte* data = rawData.begin; std::size_t size; std::size_t offset; std::size_t decoded_idx; } RTL_SRWLOCK * lock1; class MUAFormat mua; { void* vftable; int magic; int type; int file_size; int sample_rate; int channel_num; int frame_size; int sample_num; int ignore_num; int preview_begin; int preview_end; int real_preview_intro; int real_preview_begin; int real_preview_end; int CRC_all; int ignore_origin_num; int ignore_frame; }; }; Unknown; RTL_SRWLOCK* lock2; AudioSourcePlayer* player; Unknown; std::byte* result; };
|
总结
mua 文件实际上是自定义的支持 opus 编码的容器。
文件头占 $60$ 字节,大端序存储。正文部分由若干个 opus 帧组成,每帧开头两字节给出该帧的大小,以大端序存储。接下来这么多字节则是一个 opus 帧的内容。
后记
那么图像呢?
其实开发者已经开源了图像格式的编解码。
本质上是 avif 类似物。将图像保存为一个 av1 视频帧。