// Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ #include "m3u_playlist.h" #include "gbs_emu.h" #include #if defined (ROCKBOX) #include "codeclib.h" #include "plugin.h" #else #include #endif /* Copyright (C) 2006 Shay Green. This module is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This module is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this module; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "blargg_source.h" struct plugin_api* frb; static char* skip_white( char* in ) { while ( *in == ' ' || *in == '\t' ) in++; return in; } inline unsigned from_dec( unsigned n ) { return n - '0'; } static char* parse_filename( char* in, struct entry_t* entry ) { entry->file = in; entry->type = ""; char* out = in; while ( 1 ) { int c = *in; if ( !c ) break; in++; if ( c == ',' ) // commas in filename { char* p = skip_white( in ); if ( *p == '$' || from_dec( *p ) <= 9 ) { in = p; break; } } if ( c == ':' && in [0] == ':' && in [1] && in [2] != ',' ) // ::type suffix { entry->type = ++in; while ( (c = *in) != 0 && c != ',' ) in++; if ( c == ',' ) { *in++ = 0; // terminate type in = skip_white( in ); } break; } if ( c == '\\' ) // \ prefix for special characters { c = *in; if ( !c ) break; in++; } *out++ = (char) c; } *out = 0; // terminate string return in; } static char* next_field( char* in, int* result ) { while ( 1 ) { in = skip_white( in ); if ( !*in ) break; if ( *in == ',' ) { in++; break; } *result = 1; in++; } return skip_white( in ); } static char* parse_int_( char* in, int* out ) { int n = 0; while ( 1 ) { unsigned d = from_dec( *in ); if ( d > 9 ) break; in++; n = n * 10 + d; *out = n; } return in; } static char* parse_int( char* in, int* out, int* result ) { return next_field( parse_int_( in, out ), result ); } // Returns 16 or greater if not hex inline int from_hex_char( int h ) { h -= 0x30; if ( (unsigned) h > 9 ) h = ((h - 0x11) & 0xDF) + 10; return h; } static char* parse_track( char* in, struct entry_t* entry, int* result ) { if ( *in == '$' ) { in++; int n = 0; while ( 1 ) { int h = from_hex_char( *in ); if ( h > 15 ) break; in++; n = n * 16 + h; entry->track = n; } } else { in = parse_int_( in, &entry->track ); if ( entry->track >= 0 ) entry->decimal_track = 1; } return next_field( in, result ); } static char* parse_time_( char* in, int* out ) { *out = -1; int n = -1; in = parse_int_( in, &n ); if ( n >= 0 ) { *out = n; while ( *in == ':' ) { n = -1; in = parse_int_( in + 1, &n ); if ( n >= 0 ) *out = *out * 60 + n; } *out *= 1000; if ( *in == '.' ) { n = -1; in = parse_int_( in + 1, &n ); if ( n >= 0 ) *out = *out + n; } } return in; } static char* parse_time( char* in, int* out, int* result ) { return next_field( parse_time_( in, out ), result ); } static char* parse_name( char* in ) { char* out = in; while ( 1 ) { int c = *in; if ( !c ) break; in++; if ( c == ',' ) // commas in string { char* p = skip_white( in ); if ( *p == ',' || *p == '-' || from_dec( *p ) <= 9 ) { in = p; break; } } if ( c == '\\' ) // \ prefix for special characters { c = *in; if ( !c ) break; in++; } *out++ = (char) c; } *out = 0; // terminate string return in; } static int parse_line( char* in, struct entry_t* entry ) { int result = 0; // file entry->file = in; entry->type = ""; in = parse_filename( in, entry ); // track entry->track = -1; entry->decimal_track = 0; in = parse_track( in, entry, &result ); // name entry->name = in; in = parse_name( in ); // time entry->length = -1; in = parse_time( in, &entry->length, &result ); // loop entry->intro = -1; entry->loop = -1; if ( *in == '-' ) { entry->loop = entry->length; in++; } else { in = parse_time_( in, &entry->loop ); if ( entry->loop >= 0 ) { entry->intro = 0; if ( *in == '-' ) // trailing '-' means that intro length was specified { in++; entry->intro = entry->loop; entry->loop = entry->length - entry->intro; } } } in = next_field( in, &result ); // fade entry->fade = -1; in = parse_time( in, &entry->fade, &result ); // repeat entry->repeat = -1; in = parse_int( in, &entry->repeat, &result ); return result; } static void parse_comment( char* in, struct info_t* info, bool first ) { in = skip_white( in + 1 ); const char* field = in; while ( *in && *in != ':' && *in != '@' ) in++; /* Support Kaminari playlist format */ if ( *in == '@' ) { field = in + 1; while ( *in && *in != ' ' && *in != '\t' ) in++; const char* text = skip_white( in + 1 ); if ( *text ) { *in = 0; if ( !stricmp( "Title" , field ) ) info->title = text; else if ( !stricmp( "Composer", field ) ) info->composer = text; else if ( !stricmp( "Artist", field ) || !stricmp( "Sequencer", field) || !stricmp( "Date", field) || !stricmp( "Copyright", field) ) info->engineer = text; else if ( !stricmp( "Ripping" , field ) ) info->ripping = text; else if ( !stricmp( "Tagging" , field ) ) info->tagging = text; else text = 0; if ( text ) return; *in = ':'; } return; } if ( *in == ':' ) { const char* text = skip_white( in + 1 ); if ( *text ) { *in = 0; if ( !strcmp( "Composer", field ) ) info->composer = text; else if ( !strcmp( "Engineer", field ) ) info->engineer = text; else if ( !strcmp( "Ripping" , field ) ) info->ripping = text; else if ( !strcmp( "Tagging" , field ) ) info->tagging = text; else text = 0; if ( text ) return; *in = ':'; } } if ( first ) info->title = field; } blargg_err_t parse( struct M3u_Playlist* this ) { this->info.title = ""; this->info.composer = ""; this->info.engineer = ""; this->info.ripping = ""; this->info.tagging = ""; int const CR = 13; int const LF = 10; *((this->data + this->raw_size - 1)) = LF; // terminate input this->first_error = 0; bool first_comment = true; int line = 0; int count = 0; char* in = this->data; while ( in < (this->data + this->raw_size) ) { // find end of line and terminate it line++; char* begin = in; while ( *in != CR && *in != LF ) { if ( !*in ) return "Not an m3u playlist"; in++; } if ( in [0] == CR && in [1] == LF ) // treat CR,LF as a single line *in++ = 0; *in++ = 0; // parse line if ( *begin == '#' ) { parse_comment( begin, &this->info, first_comment ); first_comment = false; } else if ( *begin ) { if ( (int) this->size <= count ) this->size = count * 2 + 64; if ( !parse_line( begin, &this->entries [count] ) ) count++; else if ( !this->first_error ) this->first_error = line; first_comment = false; } } if ( count <= 0 ) return "Not an m3u playlist"; if ( !(this->info.composer [0] | this->info.engineer [0] | this->info.ripping [0] | this->info.tagging [0]) ) this->info.title = ""; this->size = count; return 0; } blargg_err_t Gbs_load_m3u( struct Gbs_Emu* this, const char* path ) { #if defined (ROCKBOX) int fd = frb->open( path, O_RDONLY ); #else int fd = open( path, O_RDONLY ); #endif if ( fd < 0 ) return "Error when loading file"; #if defined (ROCKBOX) this->m3u.raw_size = frb->read( fd, this->m3u.data, max_size ); frb->close( fd ); #else this->m3u.raw_size = read( fd, this->m3u.data, max_size ); close( fd ); #endif blargg_err_t err = parse( &this->m3u ); if ( err ) return err; if ( this->m3u.size > 0 ) this->track_count = (byte) this->m3u.size; return 0; }