仙剑4音乐加密解密代码

仙剑4的音乐为SMP格式,由MP3格式加密而来。
在mayafei大神的笔记《仙剑奇侠传4》SMP音乐解密部分研究(附源代码)基础上,我研究了加密算法,下面给出加密和解密的C语言代码。

注意:仙剑4的MP3解码功能较弱,建议将加密前的MP3先用格式工厂一类的软件重新转换过。

解密代码

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

unsigned int key[] = {0x706D6156, 0x2E657269, 0x204A2E43, 0x53207461}; //"Vampire.C.J at SoftStar..."

//Decoding Part, see http://www.tuicool.com/articles/Mv63Yv
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
void btea(unsigned int *v, int n) {
unsigned int y, z, sum;
unsigned int p, rounds, e;
rounds = 6 + 52/n;
sum = rounds*DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--) {
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
} while ((sum -= DELTA) != 0);
}

void decrypt(char *input, char *output)
{
FILE *fin = fopen(input, "rb");
fseek(fin, 0, 2);
int flen = ftell(fin);
fseek(fin, 0, 0); //get file length
char *buf = (char*)malloc(flen);
fread(buf, flen, 1, fin);
fclose(fin);
unsigned int *cipher = (unsigned int*)(buf + 0x80);
btea(cipher, 0x400); //get index data
int pos = cipher[3]; //music data offset
int len = cipher[4] / 4;//music data length
unsigned int *music = (unsigned int*)(buf + pos);
btea(music, len);
FILE *fout = fopen(output, "wb");
fwrite(music, sizeof(int) * len, 1, fout);
fclose(fout);
free(buf);
}

int main(int argc, char *argv[])
{
for(int i=1;i<argc;i++)
{
char *s = argv[i];
int n = strlen(s);
char *t = (char*)malloc(n + 1);
strcpy(t, s);
strcpy(t + n - 3, "mp3"); //change "smp" to "mp3"
decrypt(s, t);
free(t);
}
}

加密代码

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

unsigned int key[] = {0x706D6156, 0x2E657269, 0x204A2E43, 0x53207461};
unsigned int hash_table[256];

//Coding Part, see http://www.tuicool.com/articles/Mv63Yv
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(unsigned int *v, int n) {
unsigned int y, z, sum;
unsigned int p, rounds, e;
rounds = 6 + 52/n;
sum = 0;
z = v[n-1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p=0; p<n-1; p++) {
y = v[p+1];
z = v[p] += MX;
}
y = v[0];
z = v[n-1] += MX;
} while (--rounds);
}

//put the file "hash_table" in the work directory
void read_hash_table()
{
FILE* fp=fopen("hash_table","rb");
fread(hash_table, sizeof(int), sizeof(hash_table), fp);
fclose(fp);
}

unsigned int hash(char *s) //hash(crc-like) of filename (**.mp3)
{
unsigned int i, init = 0;
char v7;
for(i=0;i<4;i++)
{
init <<= 8;
init |= s[i];
}
s+=4;
v7 = *s;
for ( i = ~init; v7; ++s )
{
i = hash_table[i >> 24] ^ (v7 | (i << 8));
v7 = s[1];
}
return ~i;
}

/*
Ex: If encrypted file's name is "P01-1.smp", then the string to be hashed is "p01-1.mp3"
*/
unsigned int hash_filename(char *path)
{
int n = strlen(path);
int slash;
for(slash=n-1;slash>=0;slash--)
{
if(path[slash]=='/'||path[slash]=='\\')
break;
}
n = n - slash - 1;
path += slash + 1;
char filename[30];
for(int i=0;i<n;i++)
{
if(path[i]>='A'&&path[i]<='Z') //lower
filename[i]=path[i]-'A'+'a';
else
filename[i]=path[i];
}
filename[n]=0;
strcat(filename, ".mp3");
unsigned int h = hash(filename);
return h;
}

#define FLAG_SIZE 0x20
#define INDEX_SIZE 0x400
void encrypt(char *input, char *output)
{
FILE *fin = fopen(input, "rb");
fseek(fin, 0, 2);
int flen = ftell(fin) / sizeof(int) + 1; //pad to multiple of sizeof int
fseek(fin, 0, 0);
unsigned int *music = (unsigned int*)calloc(flen, sizeof(int));
fread(music, flen, sizeof(int), fin);
fclose(fin);
btea(music, flen);

unsigned int flag[FLAG_SIZE] = {0};
flag[0] = 0x1a545352;
flag[3] = 0x100080;
flag[4] = 0x8000;
flag[1] = flag[5] = flag[8] = 1;
flag[2] = flag[7] = 0x80;

unsigned int index[INDEX_SIZE] = {0};
index[0] = hash_filename(output);
index[1] = 0x10005;
index[3] = 0x10080;
index[4] = index[5] = flen * sizeof(int);
index[7] = 1; //important
btea(index, INDEX_SIZE);

unsigned int pad[0x3c00] = {0};

char filename[30];
strcpy(filename,output);
strcat(filename,".smp");
FILE *fout = fopen(filename, "wb");
fwrite(flag, sizeof(int), FLAG_SIZE, fout);
fwrite(index, sizeof(int), INDEX_SIZE, fout);
fwrite(pad, sizeof(int), 0x3c00, fout);
fwrite(music, sizeof(int), flen, fout);
fclose(fout);
free(music);
}

int main(int argc, char *argv[])
{
read_hash_table();
char tmp[30];
for(int i=1;i<argc;i++)
{
printf("%s -> ___.smp ( <20 chars)\n", argv[i]);
scanf("%s", tmp);
encrypt(argv[i], tmp);
}
}

文件hash_table进行base64编码后的文本:

1
AAAAALcdwQRuO4IJ2SZDDdx2BBNra8UXsk2GGgVQRx647QgmD/DJItbWii9hy0srZJsMNdOGzTEKoI48vb1POHDbEUzHxtBIHuCTRan9UkGsrRVfG7DUW8KWl1Z1i1ZSyDYZan8r2G6mDZtjERBaZxRAHXmjXdx9enufcM1mXnTgtiOYV6vinI6NoZE5kGCVPMAni4vd5o9S+6WC5eZkhlhbK77vRuq6NmCpt4F9aLOELS+tMzDuqeoWraRdC2ygkG0y1Cdw89D+VrDdSUtx2UwbNsf7BvfDIiC0zpU9dcoogDryn5379ka7uPvxpnn/9PY+4UPr/+WazbzoLdB97HdwhjTAbUcwGUsEPa5WxTmrBoInHBtDI8U9AC5yIMEqz52OEniATxahpgwbFrvNHxPrigGk9ksFfdAICMrNyQwHq5d4sLZWfGmQFXHejdR1292Ta2zAUm+15hFiAvvQZr9Gn14IW15a0X0dV2Zg3FNjMJtN1C1aSQ0LGUS6FthAl8alrCDbZKj5/SelTuDmoUuwob/8rWC7JYsjtpKW4rIvK62KmDZsjkEQL4P2De6H812pmURAaJ2dZiuQKnvqlOcdtOBQAHXkiSY26T479+07a7DzjHZx91VQMvriTfP+X/C8xujtfcIxyz7Phtb/y4OGuNU0m3nR7b063Fqg+9ju4AxpWf3NbYDbjmA3xk9kMpYIeoWLyX5crYpz67BLd1YNBE/hEMVLODaGRo8rR0KKewBcPWbBWORAglVTXUNRnjsdJSkm3CHwAJ8sRx1eKEJNGTb1UNgyLHabP5trWjsm1hUDkcvUB0jtlwr/8FYO+qAREE290BSUm5MZI4ZSHQ5WL/G5S+71YG2t+NdwbPzSICviZT3q5rwbqesLBmjvtrsn1wGm5tPYgKXeb51k2mrNI8Td0OLABPahzbPrYMl+jT69yZD/uRC2vLSnq32wovs6rhXm+6rMwLine915o8ZgNptxffefqFu0kh9GdZYaFjKIrQvzjHQtsIHDMHGFmZCKXS6NS1n3qwhUQLbJUEXmjk7y+09KK90MR5zAzUMhfYJ7lmBDf09GAHL4W8F2/QuGaEoWR2yTMARhJC3FZelLmxFeVloVh3AZGDBt2Bw1PZ8CgiBeBlsGHQvsG9wPUaaTN+a7UjM/nRE+iIDQOo3QlyQ6zVYg4+sVLVT21Cl5JqnFzjtowRcdK8ygAOrIpVCt1hJNbNLLay/ffHbu28HLoeN21mDnr/Aj6hjt4u4dvaXwqqBk9HOGJ/nEm+b9Cf24ib7geY1nxjqA0Nv7hNWLvJpiln2eu7A+kwyt/5exELCvBg1xq98rMqZoNvOibWa0vNp7dbgDXTa1tED3sQ==