diff options
Diffstat (limited to 'apps/metadata/vgm.c')
-rw-r--r-- | apps/metadata/vgm.c | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/apps/metadata/vgm.c b/apps/metadata/vgm.c new file mode 100644 index 0000000000..9ea95b3939 --- /dev/null +++ b/apps/metadata/vgm.c | |||
@@ -0,0 +1,195 @@ | |||
1 | #include <stdio.h> | ||
2 | #include <string.h> | ||
3 | #include <stdlib.h> | ||
4 | #include <ctype.h> | ||
5 | #include <inttypes.h> | ||
6 | |||
7 | #include "system.h" | ||
8 | #include "metadata.h" | ||
9 | #include "metadata_common.h" | ||
10 | #include "metadata_parsers.h" | ||
11 | #include "rbunicode.h" | ||
12 | |||
13 | /* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */ | ||
14 | |||
15 | typedef unsigned char byte; | ||
16 | |||
17 | enum { header_size = 0x40 }; | ||
18 | enum { max_field = 64 }; | ||
19 | |||
20 | struct header_t | ||
21 | { | ||
22 | char tag [4]; | ||
23 | byte data_size [4]; | ||
24 | byte version [4]; | ||
25 | byte psg_rate [4]; | ||
26 | byte ym2413_rate [4]; | ||
27 | byte gd3_offset [4]; | ||
28 | byte track_duration [4]; | ||
29 | byte loop_offset [4]; | ||
30 | byte loop_duration [4]; | ||
31 | byte frame_rate [4]; | ||
32 | byte noise_feedback [2]; | ||
33 | byte noise_width; | ||
34 | byte unused1; | ||
35 | byte ym2612_rate [4]; | ||
36 | byte ym2151_rate [4]; | ||
37 | byte data_offset [4]; | ||
38 | byte unused2 [8]; | ||
39 | }; | ||
40 | |||
41 | static byte const* skip_gd3_str( byte const* in, byte const* end ) | ||
42 | { | ||
43 | while ( end - in >= 2 ) | ||
44 | { | ||
45 | in += 2; | ||
46 | if ( !(in [-2] | in [-1]) ) | ||
47 | break; | ||
48 | } | ||
49 | return in; | ||
50 | } | ||
51 | |||
52 | static byte const* get_gd3_str( byte const* in, byte const* end, char* field ) | ||
53 | { | ||
54 | byte const* mid = skip_gd3_str( in, end ); | ||
55 | int len = (mid - in) / 2 - 1; | ||
56 | if ( field && len > 0 ) | ||
57 | { | ||
58 | len = len < (int) max_field ? len : (int) max_field; | ||
59 | |||
60 | field [len] = 0; | ||
61 | /* Conver to utf8 */ | ||
62 | utf16LEdecode( in, field, len ); | ||
63 | |||
64 | /* Copy string back to id3v2buf */ | ||
65 | strcpy( (char*) in, field ); | ||
66 | } | ||
67 | return mid; | ||
68 | } | ||
69 | |||
70 | static byte const* get_gd3_pair( byte const* in, byte const* end, char* field ) | ||
71 | { | ||
72 | return skip_gd3_str( get_gd3_str( in, end, field ), end ); | ||
73 | } | ||
74 | |||
75 | static void parse_gd3( byte const* in, byte const* end, struct mp3entry* id3 ) | ||
76 | { | ||
77 | char* p = id3->path; | ||
78 | id3->title = (char *) in; | ||
79 | in = get_gd3_pair( in, end, p ); /* Song */ | ||
80 | |||
81 | id3->album = (char *) in; | ||
82 | in = get_gd3_pair( in, end, p ); /* Game */ | ||
83 | |||
84 | in = get_gd3_pair( in, end, NULL ); /* System */ | ||
85 | |||
86 | id3->artist = (char *) in; | ||
87 | in = get_gd3_pair( in, end, p ); /* Author */ | ||
88 | |||
89 | #if MEMORYSIZE > 2 | ||
90 | in = get_gd3_str ( in, end, NULL ); /* Copyright */ | ||
91 | in = get_gd3_pair( in, end, NULL ); /* Dumper */ | ||
92 | |||
93 | id3->comment = (char *) in; | ||
94 | in = get_gd3_str ( in, end, p ); /* Comment */ | ||
95 | #endif | ||
96 | } | ||
97 | |||
98 | int const gd3_header_size = 12; | ||
99 | |||
100 | static long check_gd3_header( byte* h, long remain ) | ||
101 | { | ||
102 | if ( remain < gd3_header_size ) return 0; | ||
103 | if ( memcmp( h, "Gd3 ", 4 ) ) return 0; | ||
104 | if ( get_long_le( h + 4 ) >= 0x200 ) return 0; | ||
105 | |||
106 | long gd3_size = get_long_le( h + 8 ); | ||
107 | if ( gd3_size > remain - gd3_header_size ) | ||
108 | gd3_size = remain - gd3_header_size; | ||
109 | |||
110 | return gd3_size; | ||
111 | } | ||
112 | |||
113 | static void get_vgm_length( struct header_t* h, struct mp3entry* id3 ) | ||
114 | { | ||
115 | long length = get_long_le( h->track_duration ) * 10 / 441; | ||
116 | if ( length > 0 ) | ||
117 | { | ||
118 | long loop_length = 0, intro_length = 0; | ||
119 | long loop = get_long_le( h->loop_duration ); | ||
120 | if ( loop > 0 && get_long_le( h->loop_offset ) ) | ||
121 | { | ||
122 | loop_length = loop * 10 / 441; | ||
123 | intro_length = length - loop_length; | ||
124 | } | ||
125 | else | ||
126 | { | ||
127 | intro_length = length; /* make it clear that track is no longer than length */ | ||
128 | loop_length = 0; | ||
129 | } | ||
130 | |||
131 | id3->length = intro_length + 2 * loop_length; /* intro + 2 loops */ | ||
132 | return; | ||
133 | } | ||
134 | |||
135 | id3->length = 150 * 1000; /* 2.5 minutes */ | ||
136 | } | ||
137 | |||
138 | bool get_vgm_metadata(int fd, struct mp3entry* id3) | ||
139 | { | ||
140 | /* Use the id3v2 part of the id3 structure as a temporary buffer */ | ||
141 | unsigned char* buf = (unsigned char *)id3->id3v2buf; | ||
142 | int read_bytes; | ||
143 | |||
144 | memset(buf, 0, ID3V2_BUF_SIZE); | ||
145 | if ((lseek(fd, 0, SEEK_SET) < 0) | ||
146 | || ((read_bytes = read(fd, buf, header_size)) < header_size)) | ||
147 | { | ||
148 | return false; | ||
149 | } | ||
150 | |||
151 | id3->vbr = false; | ||
152 | id3->filesize = filesize(fd); | ||
153 | |||
154 | id3->bitrate = 706; | ||
155 | id3->frequency = 44100; | ||
156 | |||
157 | /* If file is gzipped, will get metadata later */ | ||
158 | if (memcmp(buf, "Vgm ", 4)) | ||
159 | { | ||
160 | /* We must set a default song length here because | ||
161 | the codec can't do it anymore */ | ||
162 | id3->length = 150 * 1000; /* 2.5 minutes */ | ||
163 | return true; | ||
164 | } | ||
165 | |||
166 | /* Get song length from header */ | ||
167 | struct header_t* header = (struct header_t*) buf; | ||
168 | get_vgm_length( header, id3 ); | ||
169 | |||
170 | long gd3_offset = get_long_le( header->gd3_offset ) - 0x2C; | ||
171 | |||
172 | /* No gd3 tag found */ | ||
173 | if ( gd3_offset < 0 ) | ||
174 | return true; | ||
175 | |||
176 | /* Seek to gd3 offset and read as | ||
177 | many bytes posible */ | ||
178 | gd3_offset = id3->filesize - (header_size + gd3_offset); | ||
179 | if ((lseek(fd, -gd3_offset, SEEK_END) < 0) | ||
180 | || ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) <= 0)) | ||
181 | return true; | ||
182 | |||
183 | byte* gd3 = buf; | ||
184 | long gd3_size = check_gd3_header( gd3, read_bytes ); | ||
185 | |||
186 | /* GD3 tag is zero */ | ||
187 | if ( gd3_size == 0 ) | ||
188 | return true; | ||
189 | |||
190 | /* Finally, parse gd3 tag */ | ||
191 | if ( gd3 ) | ||
192 | parse_gd3( gd3 + gd3_header_size, gd3 + read_bytes, id3 ); | ||
193 | |||
194 | return true; | ||
195 | } | ||