/* * Interfacing library for Sony's obfuscated MP3 file format as used * on their network walkman devices * * Waider, March 2005 */ #include #include #include #include #include #include #include #include #include #include /* mp3 internals handling */ #include /* hardware abstraction layer, for obtaining the serial off the device */ #ifdef HAL_VERSION #include #if HAL_VERSION == 4 #define libhal_get_all_devices(x,y,z) hal_get_all_devices(x,y) #define libhal_device_property_exists(w,x,y,z) \ hal_device_property_exists(w,x,y) #define libhal_device_get_property_string(w,x,y,z) \ hal_device_get_property_string(w,x,y) #define libhal_device_get_property_bool(w,x,y,z) \ hal_device_get_property_bool(w,x,y) #define libhal_free_string(x) hal_free_string(x) #define libhal_free_string_array(x) hal_free_string_array(x) #endif #endif #include "mple.h" #include "common.h" /* Util functions */ /* * effectively mkdir -p * create directory PATH, including any required parent directories, * using MODE as the file mode for each newly created directory. */ static gboolean mkpath( char *path, mode_t mode ) { gchar *dir; struct stat statbuf; gboolean retval; if ( stat( path, &statbuf ) == 0 ) { if ( S_ISDIR( statbuf.st_mode )) { return TRUE; } else { return FALSE; } } if ( errno != ENOENT ) { return FALSE; } if ( mkdir( path, mode ) == 0 ) { return TRUE; } if ( errno != ENOENT ) { return FALSE; } dir = g_dirname( path ); retval = mkpath( dir, mode ); g_free( dir ); return retval; } time_t fat_to_unix( guint32 fatts ) { time_t unixts = 0; struct tm unixtm; memset( &unixtm, 0, sizeof( struct tm )); unixtm.tm_sec = ( fatts & 0x0000001f ); unixtm.tm_min = ( fatts & 0x000007e0 ) >> 5; unixtm.tm_hour = ( fatts & 0x0000f800 ) >> 11; unixtm.tm_mday = ( fatts & 0x001f0000 ) >> 16; unixtm.tm_mon = ( fatts & 0x01e00000 ) >> 21; unixtm.tm_year = ( fatts & 0xfe000000 ) >> 25; /* fixups */ unixtm.tm_sec *= 2; unixtm.tm_mon -= 1; unixtm.tm_year += 80; unixts = mktime( &unixtm ); return unixts; } guint32 unix_to_fat( time_t unixts ) { guint32 fatts = 0; struct tm *unixtm; unixtm = localtime( &unixts ); /* fixups */ unixtm->tm_sec /= 2; unixtm->tm_mon += 1; unixtm->tm_year -= 80; fatts = unixtm->tm_sec | ( unixtm->tm_min << 5 ) | ( unixtm->tm_hour << 11 ) | ( unixtm->tm_mday << 16 ) | ( unixtm->tm_mon << 21 ) | ( unixtm->tm_year << 25 ); return fatts; } /****************************************************************************** * DEVICE MANIPULATION *****************************************************************************/ /* this is, obviously, undocumented */ gboolean mple_fix_checksum( mple_device *device ) { char *pblistpath; struct stat statbuf; pblist_hdr header; size_t cs, size; FILE *pb = NULL; /* sanity */ if ( device == NULL || device->path == NULL || device->pbtemplate == NULL ) { errno = EINVAL; return FALSE; } pblistpath = g_strdup_printf( device->pbtemplate, device->path, device->master_pblist ); /* * If the pblist file doesn't exist, bail out without error. It * probably just means the device is unformatted. */ if ( stat( pblistpath, &statbuf ) != 0 ) { g_free( pblistpath ); errno = ENOENT; goto out; } /* * on the other hand, if the file can't be opened, it *is* an error. */ if (( pb = fopen( pblistpath, "rb" )) == NULL ) { g_free( pblistpath ); goto out; } /* done with this */ g_free( pblistpath ); /* read file header */ if ( fread( &header, sizeof( pblist_hdr), 1, pb ) != 1 ) { goto out; } if ( memcmp( header.signature, "WMPLESYS", 8 ) != 0 ) { errno = EIO; /* as good as anything, really */ goto out; } /* verify the checksum */ cs = 0; for ( size = 0; size < 8; size++ ) { guint32 val = GUINT32_FROM_BE(((guint32 *)&header)[size]); cs ^= val; } if ( cs != 0 ) { /* your checksum is broken! */ fprintf( stderr, "checksum error: have %08x, expected %08x\n", GUINT32_FROM_BE(((guint32 *)&header)[7]), GUINT32_FROM_BE(((guint32 *)&header)[7]) ^ cs ); goto out; } out: if ( pb != NULL ) { fclose( pb ); } return cs; } /* * parse the master pblist file on DEVICE, populating the tracklist in * the device structure and returning a pointer to the tracklist. */ GList *mple_parse_pblist( mple_device *device ) { char *pblistpath; struct stat statbuf; pblist_hdr header; size_t cs, size, f; mple_folder *fptr; mple_track *tptr; GList *retval, *node; FILE *pb = NULL; retval = NULL; errno = 0; /* sanity */ if ( device == NULL || device->path == NULL || device->pbtemplate == NULL ) { errno = EINVAL; return NULL; } pblistpath = g_strdup_printf( device->pbtemplate, device->path, device->master_pblist ); /* * If the pblist file doesn't exist, bail out without error. It * probably just means the device is unformatted. */ if ( stat( pblistpath, &statbuf ) != 0 ) { g_free( pblistpath ); errno = ENOENT; goto out; } /* * on the other hand, if the file can't be opened, it *is* an error. */ if (( pb = fopen( pblistpath, "rb" )) == NULL ) { g_free( pblistpath ); goto out; } /* done with this */ g_free( pblistpath ); /* read file header */ if ( fread( &header, sizeof( pblist_hdr), 1, pb ) != 1 ) { goto out; } if ( memcmp( header.signature, "WMPLESYS", 8 ) != 0 ) { errno = EIO; /* as good as anything, really */ goto out; } /* verify the checksum */ cs = 0; for ( size = 0; size < 8; size++ ) { guint32 val = GUINT32_FROM_BE(((guint32 *)&header)[size]); cs ^= val; } if ( cs != 0 ) { errno = EIO; /* again, close enough */ goto out; } /* extract any auxilliary information we're missing */ if (!(device->aux && MPLE_MSN )) { device->msn = GUINT32_FROM_BE( header.msn ); } if (!(device->aux && MPLE_TS )) { device->timestamp = fat_to_unix( GUINT32_FROM_BE( header.timestamp )); } if (!(device->aux && MPLE_MAGIC )) { device->magic = GUINT32_FROM_BE( header.magic ); } device->aux = MPLE_MAGIC | MPLE_TS | MPLE_MSN; /* * pull the folderlist */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { fptr = g_malloc0( sizeof( mple_folder )); if (( size = fread( &(fptr->folderdata), sizeof( pblist_folder ), 1, pb )) == 1 ) { fptr->tracklist = NULL; fptr->dev = device; retval = g_list_append( retval, fptr ); continue; } /* fall through */ errno = EIO; g_free( fptr ); goto bailout; } /* Pull the MP3 file data */ /* sanity check: are we looking at the first folder offset? */ node = retval; if ( node != NULL ) { /* careful! */ fptr = node->data; if ( ftell( pb ) != GUINT32_FROM_BE( fptr->folderdata.offset )) { errno = ENODATA; goto bailout; } } /* get tracklists */ /* this could be merged with the above, but it would require file seeking. doing it this way allows contiguous reading */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { long start, end; guint32 tracks, t; mple_folder *efptr; node = g_list_nth( retval, f ); fptr = node->data; start = GUINT32_FROM_BE( fptr->folderdata.offset ); if ( f == GUINT32_FROM_BE( header.folders ) - 1 ) { node = g_list_nth( retval, 0 ); efptr = node->data; end = GUINT32_FROM_BE( efptr->folderdata.offset ) + ( GUINT32_FROM_BE( header.tracks ) * 2 ); } else { node = g_list_nth( retval, f + 1 ); efptr = node->data; end = GUINT32_FROM_BE( efptr->folderdata.offset ); } tracks = ( end - start ) / 2; fptr->tracklist = NULL; for ( t = 0; t < tracks; t++ ) { tptr =g_malloc0( sizeof( mple_track )); if ( fread( &(tptr->tracknum ), sizeof( guint16 ), 1, pb ) == 1 ) { tptr->tracknum = GUINT16_FROM_BE( tptr->tracknum ); tptr->parent = fptr; tptr->dev = device; memset( &(tptr->header), 0, sizeof( mpdat_hdr )); tptr->id3data = NULL; fptr->tracklist = g_list_append( fptr->tracklist, tptr ); } else { goto bailout; } } } /* skip over padding */ if ( GUINT32_FROM_BE( header.tracks ) % 8 ) { if ( fseek( pb, ( 8 - ( GUINT32_FROM_BE( header.tracks ) % 8 )) * 2, SEEK_CUR ) != 0 ) { goto bailout; } } /* get metadata */ for ( f = 0; f < GUINT32_FROM_BE( header.folders ); f++ ) { guint16 t = 0; node = g_list_nth( retval, f ); fptr = node->data; while(( node = g_list_nth( fptr->tracklist, t )) != NULL ) { size_t bytes; tptr = node->data; if (( bytes = fread( &(tptr->trackdata ), 1, sizeof( pblist_track ), pb )) == sizeof( pblist_track )) { t++; continue; } /* FALL THROUGH */ if ( feof( pb )) { errno = ENODATA; } else { if ( errno == 0 ) { errno = EIO; } } goto bailout; } } out: if ( pb != NULL ) { fclose( pb ); } device->folderlist = retval; return retval; /* clean up the mess and return NULL */ bailout: if ( retval != NULL ) { GList *node = retval; mple_folder *fptr; while( node ) { fptr = node->data; if ( fptr->tracklist != NULL ) { GList *tnode = fptr->tracklist; while( tnode ) { g_free( tnode->data ); tnode = g_list_next( tnode ); } g_list_free( fptr->tracklist ); } g_free( fptr ); node = g_list_next( node ); } g_list_free( retval ); retval = NULL; } goto out; } /* * synchronize the in-memory version of DEVICE to the device */ guint16 mple_dev_sync( mple_device *device ) { guint16 retval = 0; gchar *pbfile0 = NULL; gchar *pbfile1 = NULL; pblist_hdr header; gboolean rollback = FALSE; FILE *pb = NULL; guint32 cs; int i; guint32 tracklist; guint16 *tracks = NULL; guint16 trackcnt; struct stat statbuf; gchar *dirname; if ( device == NULL ) { goto out; } if ( device->path == NULL ) { goto out; } /* very rough */ if ( strlen( device->path ) > PATH_MAX - strlen( device->pbtemplate )) { goto out; } pbfile1 = g_strdup_printf( device->pbtemplate, device->path, device->master_pblist ); if ( device->master_pblist > 0 ) { pbfile0 = g_strdup_printf( device->pbtemplate, device->path, device->master_pblist - 1 ); } /* set up the header */ memset( &header, 0, sizeof( pblist_hdr )); memcpy( header.signature, "WMPLESYS", 8 ); /* the timestamp field never changes once it's written */ if ( device->aux & MPLE_TS ) { header.timestamp = GUINT32_TO_BE( unix_to_fat( device->timestamp )); } else { /* to be 100% accurate, we should stat the ESYS directory and use its ctime. Since it's not actually a show-stopper, I've not bothered. */ header.timestamp = GUINT32_TO_BE( unix_to_fat( time( NULL ))); } header.msn = GUINT32_TO_BE( device->msn ); memcpy( &header.magic, PBLIST_MAGIC, sizeof( PBLIST_MAGIC )); if ( device->folderlist != NULL ) { GList *node = device->folderlist; mple_folder *fptr; for ( ; node != NULL ; node = node->next ) { fptr = node->data; header.folders++; if ( fptr->tracklist != NULL ) { GList *tnode = fptr->tracklist; mple_track *tptr; for ( ; tnode != NULL; tnode = tnode->next ) { tptr = tnode->data; /* round up to the nearest multiple of 16 */ if ( header.tracks % 16 == 0 ) { guint16 *tmp; tmp = g_realloc( tracks, sizeof( guint16 ) * 16 * (( header.tracks / 16 ) + 1 )); tracks = tmp; } tracks[header.tracks++] = GUINT16_TO_BE( tptr->tracknum ); } } else { /* nothing to write */ } } } /* these need to be byteswapped before going onto the device */ header.folders = GUINT32_TO_BE( header.folders ); header.tracks = GUINT32_TO_BE( header.tracks ); cs = 0; for ( i = 0; i < 7; i++ ) { guint32 val = GUINT32_TO_BE(((guint32 *)&header)[i]); cs ^= val; } header.checksum = GUINT32_TO_BE( cs ); /* now we touch the disk */ dirname = g_dirname( pbfile1 ); if ( mkpath( dirname, 0755 ) == FALSE ) { goto out; } g_free( dirname ); if ( stat( pbfile1, &statbuf ) == 0 ) { if ( pbfile0 != NULL ) { unlink( pbfile0 ); /* don't care if it fails */ if ( rename( pbfile1, pbfile0 ) != 0 ) { goto out; } /* from this point on we can do a rollback if required */ rollback = TRUE; } } if (( pb = fopen( pbfile1, "wb" )) == NULL ) { goto rollback; } if ( fwrite( &header, sizeof( pblist_hdr ), 1, pb ) != 1 ) { goto rollback; } if ( device->folderlist != NULL ) { guint16 folder; tracklist = sizeof( pblist_hdr ) + sizeof( pblist_folder ) * g_list_length( device->folderlist ); GList *node; mple_folder *fptr; for ( folder = 0; ( node = g_list_nth( device->folderlist, folder )) != NULL; folder++ ) { fptr = node->data; trackcnt = g_list_length( fptr->tracklist ); fptr->folderdata.offset = GUINT32_TO_BE( tracklist ); if ( fwrite( &(fptr->folderdata), sizeof( pblist_folder ), 1, pb ) != 1 ) { goto rollback; } tracklist += sizeof( guint16 ) * trackcnt; } /* next, the tracklist */ trackcnt = GUINT32_TO_BE( header.tracks ); if ( trackcnt ) { if ( fwrite( tracks, sizeof( guint16 ), trackcnt, pb ) != trackcnt ) { goto rollback; } /* padding */ while( trackcnt++ % 8 ) { guint16 pad = 0; if ( fwrite( &pad, sizeof( guint16 ), 1, pb ) != 1 ) { goto rollback; } } /* now do the actual track data */ for ( folder = 0; ( node = g_list_nth( device->folderlist, folder )) != NULL; folder++ ) { fptr = node->data; if ( fptr->tracklist != NULL ) { mple_track *tptr; for ( trackcnt = 0; ( node = g_list_nth( fptr->tracklist, trackcnt )) != NULL; trackcnt++ ) { tptr = node->data; if ( fwrite( &(tptr->trackdata), sizeof( pblist_track ), 1, pb ) != 1 ) { goto rollback; } } } } } else { /* no tracks to write */ } } /* yay! */ retval = 1; device->dirty = FALSE; out: if ( tracks != NULL ) { g_free( tracks ); } if ( pb != NULL ) { fclose( pb ); } if ( pbfile0 != NULL ) { g_free( pbfile0 ); } if ( pbfile1 != NULL ) { g_free( pbfile1 ); } return retval; rollback: /* if these fail, you're screwed either way. */ if ( rollback == TRUE ) { int old_errno = errno; /* preserve */ unlink( pbfile1 ); rename( pbfile0, pbfile1 ); errno = old_errno; } goto out; } /* * create a new device structure based at DEVPATH */ mple_device *mple_dev_init( char *devpath ) { mple_device *device = NULL; device = g_malloc0( sizeof( mple_device )); device->device = NULL; device->msn = 0; device->aux = 0; device->path = NULL; device->pbtemplate = NULL; device->master_pblist = DEFAULT_MASTER_PBLIST; device->mptemplate = NULL; device->folderlist = NULL; device->progress = NULL; /* for great justice, or compatibility with earlier code, or, I dunno, making people's lives easier */ device->autosync = TRUE; device->dirty = FALSE; if ( devpath != NULL ) { if (( device->path = g_strdup( devpath )) == NULL ) { mple_dev_free( device ); device = NULL; goto out; } } if (( device->pbtemplate = g_strdup( DEFAULT_PB_TEMPLATE )) == NULL ) { mple_dev_free( device ); device = NULL; goto out; } if (( device->mptemplate = g_strdup( DEFAULT_MP_TEMPLATE )) == NULL ) { mple_dev_free( device ); device = NULL; goto out; } out: return device; } /* * similar to the above, but takes a /dev argument and attempts to * mount the device if it's not already available. * Not yet finished. */ mple_device *mple_dev_mount( char *devpath ) { mple_device *device = NULL; /* approximately: - stat the device - check /proc/mounts? for device mount - if not mounted, do a mount, then recheck - call mple_dev_init( mountpoint ) - if root, get msn - cache msn on the device? */ return device; } /* * free up all memory associated with DEVICE * do not use the pointer after you've called this! */ void mple_dev_free( mple_device *device ) { if ( device == NULL ) { return; } if ( device->device != NULL ) { g_free( device->device ); } if ( device->path != NULL ) { g_free( device->path ); } if ( device->pbtemplate != NULL ) { g_free( device->pbtemplate ); } if ( device->mptemplate != NULL ) { g_free( device->mptemplate ); } if ( device->folderlist != NULL ) { GList *node = device->folderlist; mple_folder *fptr; while( node ) { fptr = node->data; if ( fptr->tracklist != NULL ) { GList *tnode = fptr->tracklist; while( tnode ) { mple_track *tptr = tnode->data; if ( tptr->id3data != NULL ) { g_free( tptr->id3data ); tptr->id3data = NULL; } g_free( tptr ); tnode = g_list_next( tnode ); } g_list_free( fptr->tracklist ); } g_free( fptr ); node = g_list_next( node ); } g_list_free( device->folderlist ); device->folderlist = NULL; } g_free( device ); } /* * find the next available track number on DEVICE */ static guint16 mple_dev_get_next_trackno( mple_device *device ) { guint16 trackno = 1; GList *node; mple_folder *fptr; if ( device->folderlist == NULL ) { goto out; } FOLDERS: node = device->folderlist; while( node != NULL ) { GList *tnode; mple_track *tptr; fptr = node->data; tnode = fptr->tracklist; while( tnode != NULL ) { tptr = tnode->data; if ( tptr->tracknum == trackno ) { trackno++; /* reset folderlist */ goto FOLDERS; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } if ( trackno > MAX_TRACKS ) { trackno = 0; errno = ENOSPC; } out: return trackno; } /****************************************************************************** * FOLDER MANIPULATION *****************************************************************************/ /* * find on DEVICE FOLDER and return its (1-based) position in the device */ guint16 mple_get_folder( mple_device *device, pblist_folder *folder ) { guint16 retval = 0; if ( device->folderlist == NULL ) { if ( device->autosync && mple_parse_pblist( device ) == NULL ) { /* check errno? */ } } /* see if the folder already exists */ if ( device->folderlist != NULL ) { GList *node = device->folderlist; for ( ; node != NULL; node = node->next ) { mple_folder *fptr = node->data; retval++; if ( memcmp( fptr->folderdata.foldername, folder->foldername, sizeof( folder->foldername )) == 0 ) { return retval; } } } return 0; } /* * add a folder to the DEVICE at position POS with details in FOLDER. * POS == 0 means use the first available position. Will not allow the * creation of folders with duplicate names, which is an artificial * restriction that I probably shouldn't be enforcing here. */ guint16 mple_add_folder( mple_device *device, pblist_folder *folder, guint16 fpos ) { mple_folder *newfolder; if ( device->folderlist == NULL ) { if ( device->autosync && mple_parse_pblist( device ) == NULL ) { if ( errno ) { /*goto out;*/ } else { /* empty device */ } fpos = 0; } } /* * I'm not actually sure about this; I think it's okay to have * multiple folders with the same name on the device, but it makes * it harder for filesystem-like stuff to work. */ #ifdef YAY_WE_RE_A_FILESYSTEM if ( mple_get_folder( mple_device *device, pblist_folder *folder )) { errno = EEXIST; goto out; } #endif newfolder = g_malloc0( sizeof( mple_folder )); newfolder->dev = device; memmove( &(newfolder->folderdata), folder, sizeof( pblist_folder )); newfolder->tracklist = NULL; if (( fpos > g_list_length( device->folderlist )) || ( fpos == 0 )) { device->folderlist = g_list_append( device->folderlist, newfolder ); fpos = g_list_length( device->folderlist ); } else { device->folderlist = g_list_insert( device->folderlist, newfolder, fpos - 1 ); } device->dirty = TRUE; if ( device->autosync ) { if ( !mple_dev_sync( device )) { /* holy crap, you're in trouble now... */ fpos = 0; goto out; } } out: return fpos; } /* * Nuke a folder on DEVICE at POS * Caution: will delete folder contents also! */ guint16 mple_del_folder( mple_device *device, guint16 pos ) { GList *node; if (( device == NULL ) || ( pos == 0 )) { errno = EINVAL; return 0; } if (( node = device->folderlist ) == NULL ) { if ( device->autosync ) { if (( node = mple_parse_pblist( device )) == NULL ) { return 0; } } else { errno = ENOENT; return 0; } } if ( g_list_nth( node, pos - 1 ) != NULL ) { mple_folder *fptr = g_list_nth_data( node, pos - 1 ); device->folderlist = g_list_remove( device->folderlist, fptr ); node = fptr->tracklist; while( node ) { g_free( node->data ); node = g_list_next( node ); } g_free( fptr ); device->dirty = TRUE; if ( device->autosync ) { return mple_dev_sync( device ); } else { return 1; /* what mple_dev_sync would return */ } } errno = ENOENT; return 0; } /* * change the position of DEVICE's FOLDER'th folder to POS */ guint16 mple_mv_folder( mple_device *device, guint16 folder, guint16 pos ) { GList *node; if (( device == NULL ) || ( folder == 0 )) { errno = EINVAL; return 0; } if (( node = device->folderlist ) == NULL ) { if ( device->autosync ) { if (( node = mple_parse_pblist( device )) == NULL ) { return 0; } } else { errno = ENOENT; return 0; } } if ( pos > g_list_length( device->folderlist )) { pos = 0; } if ( g_list_nth( node, folder - 1 ) != NULL ) { mple_folder *fptr = g_list_nth_data( node, folder - 1 ); device->folderlist = g_list_remove( device->folderlist, fptr ); if ( pos == 0 ) { g_list_append( device->folderlist, fptr ); pos = g_list_length( device->folderlist ); } else { device->folderlist = g_list_insert( device->folderlist, fptr, pos - 1 ); } device->dirty = TRUE; if ( device->autosync ) { if ( mple_dev_sync( device )) { return pos; } } else { return pos; } } else { errno = ENOENT; } return 0; } /* * rename DEVICE's FOLDER'th folder to the name in NEWNAME */ guint16 mple_ren_folder( mple_device *device, guint16 folder, pblist_folder *newname ) { GList *node; if (( device == NULL ) || ( folder == 0 ) || ( newname == NULL )) { errno = EINVAL; goto out; } if (( node = device->folderlist ) == NULL ) { if ( device->autosync ) { if (( node = mple_parse_pblist( device )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } if ( g_list_nth( node, folder - 1 ) != NULL ) { mple_folder *fptr = g_list_nth_data( node, folder - 1 ); if ( memmove( fptr->folderdata.foldername, newname->foldername, sizeof( fptr->folderdata.foldername )) == NULL ) { goto out; } device->dirty = TRUE; if ( device->autosync ) { if ( mple_dev_sync( device )) { return folder; } } else { return folder; } } else { errno = ENOENT; } out: return 0; } /* * Add a track to a folder * * Adds a track to DEVICE in the FOLDER'th folder. TRACK contains the * metadata, and TRACKNUM is the track number used to generate the * conversion matrix. POS is the position within the folder; if zero, * the track is appended to the end of the existing tracklist. This * assumes that the track has already been written to the device. */ guint16 mple_add_to_folder( mple_device *device, guint16 folder, pblist_track *track, guint16 tracknum, guint16 pos ) { mple_folder *fptr = NULL; mple_track *tptr = NULL; /* validate the parameters */ if (( device == NULL ) || ( folder == 0 ) || ( track == NULL ) || ( tracknum == 0 )) { errno = EINVAL; pos = 0; goto out; } if ( device->folderlist == NULL ) { if ( device->autosync ) { if ( mple_parse_pblist( device ) == NULL ) { pos = 0; goto out; } } else { pos = 0; errno = ENOENT; goto out; } } if ( g_list_nth( device->folderlist, folder - 1 ) == NULL ) { pos = 0; errno = ENOENT; goto out; } else { fptr = g_list_nth_data( device->folderlist, folder - 1 ); } tptr = g_malloc0( sizeof( mple_track )); tptr->tracknum = tracknum; tptr->dev = device; tptr->parent = fptr; memcpy( &(tptr->trackdata), track, sizeof( pblist_track )); if (( pos == 0 ) || ( pos > g_list_length( fptr->tracklist ))) { fptr->tracklist = g_list_append( fptr->tracklist, tptr ); pos = g_list_length( fptr->tracklist ); } else { fptr->tracklist = g_list_insert( fptr->tracklist, tptr, pos - 1 ); } device->dirty = TRUE; if ( device->autosync && !mple_dev_sync( device )) { pos = 0; goto out; } out: return pos; } /****************************************************************************** * TRACK MANIPULATION *****************************************************************************/ /* * extract ID3 metadata from file FILENAME and return a pblist_track * structure containing the filename, artist & name */ pblist_track *mple_get_metadata_NAME( char *filename ) { pblist_track *metadata = NULL; FILE *mp3 = fopen( filename, "rb" ); if ( mp3 != NULL ) { metadata = mple_get_metadata_FILE( mp3 ); if ( metadata != NULL ) { gchar *basename; guint16 i; /* copy the filename data. Note, this assumes / as a directory separator, and a pretty-much flat ASCII filename */ basename = strrchr( filename, '/' ); if ( basename != NULL ) { basename++; } else { basename = filename; } for ( i = 0; i < strlen( basename ); i++ ) { if ( i == 127 ) { break; } metadata->filename[i] = GUINT16_FROM_BE( basename[i] ); } metadata->filename[i] = 0; } fclose( mp3 ); } return metadata; } /* * extract ID3 metadata from filehandle MP3 and return a pblist_track * structure containing the artist & name */ pblist_track *mple_get_metadata_FILE( FILE *mp3 ) { guint8 buffer[BUFSIZ], *tagbuf = NULL; pblist_track *metadata = NULL; signed long taglen, id31_fudge; if ( mp3 == NULL ) { goto out; } if ( fseek( mp3, -128, SEEK_END ) != 0 ) { goto out; } if ( fread( &buffer, 1, 128, mp3 ) != 128 ) { goto out; } if (( taglen = id3_tag_query( buffer, 128 )) != 0 ) { tagbuf = g_malloc0( 128 ); memcpy( tagbuf, buffer, 128 ); } if ( fseek( mp3, 0, SEEK_SET ) != 0 ) { goto out; } if ( fread( &buffer, 1, 10, mp3 ) != 10 ) { goto out; } id31_fudge = taglen; if (( taglen = id3_tag_query( buffer, 10 )) > 0 ) { gpointer tmp; tmp = g_realloc( tagbuf, taglen ); tagbuf = tmp; if ( taglen > 10 ) { memcpy( tagbuf, buffer, 10 ); fread( &tagbuf[10], 1, taglen - 10, mp3 ); } } else { taglen = id31_fudge; } /* at this point, any errors are parse errors in the ID3 data, which we're not overly concerned about */ metadata = g_malloc0( sizeof( pblist_track )); if ( taglen > 0 ) { struct id3_tag *tag; struct id3_frame *frame; unsigned idx; tag = id3_tag_parse( tagbuf, taglen ); for ( idx = 0; ( frame = id3_tag_findframe( tag, "T", idx )); idx++ ) { union id3_field *field = id3_frame_field( frame, 1 ); const id3_ucs4_t *text = id3_field_getstrings( field, 0 ); id3_utf16_t *rtext; /* rendered */ guint16 *dest = NULL; if ( text == NULL ) { continue; } rtext = id3_ucs4_utf16duplicate( text ); if ( memcmp( frame->id, ID3_FRAME_TITLE, 4 ) == 0 ) { dest = metadata->title; } else if ( memcmp( frame->id, ID3_FRAME_ARTIST, 4 ) == 0 ) { dest = metadata->artist; } /* else we're not interested */ if ( dest != NULL && dest[0] == 0 ) { guint8 i; for ( i = 0; i < 128; i++ ) { /* the device wants UTF16BE */ dest[i] = GUINT16_FROM_BE( rtext[i] ); if ( rtext[i] == 0 ) { break; } } } free( rtext ); } id3_tag_delete( tag ); } out: if ( tagbuf != NULL ) { g_free( tagbuf ); } return metadata; } /* * Build the conversion array for track TRACKNUM in CONV. * * There may be a cleaner/smarter way to do this - perhaps as a * function rather than an array, for example. * * This iterates through the bits from bit 0 to the highest bit * required to represent the track number. If the bit N is set, then * every 2N bytes in the file are taken as a block, the two halves of * which are swapped. The initial conversion matrix is simply a * straight ( 256 - bytevalue ) array. * * After generating, each value is XOR'd with a bitflipped version of * the last octet of the media serial number. */ void mple_build_conv_array( guint16 trackno, guint8 *conv, guint8 xorvalue ) { guint16 bit; guint16 i; for ( i = 0; i < 256; i++ ) { conv[i] = 255 - i; } bit = 1; while( bit <= trackno ) { if ( trackno & bit ) { guint16 j; guint16 k; for ( j = 0; j < 256; j+= bit * 2 ) { for ( k = 0; k < bit; k++ ) { guint8 temp; temp = conv[j + k]; conv[j + k] = conv[j + k + bit]; conv[j + k + bit] = temp; } } } bit <<= 1; } /* apply the XOR */ for ( i = 0; i < 256; i++ ) { conv[i] = conv[i] ^ ( ~xorvalue & 0xff ); } } /* * add to DEVICE the track pointed to by FILENAME using metadata * TRACK. The file is put in the FOLDER'th folder, POS'th position. If * TRACK is null, it will be populated (if possible) from FILENAME's * ID3 metadata. If FOLDER and/or POS are 0, the first available * folder/position will be used. This function WILL NOT create a * folder; the device must contain at least one folder for FOLDER = 0 * to work. */ guint16 mple_add_track( mple_device *device, char *filename, pblist_track *track, guint16 folder, guint16 pos ) { guint16 retval = 0, i, tracknum; guint8 conv[256]; guint32 framerates[15], frames = 0, flen = 0, frate = 0; FILE *mp3 = NULL, *dat = NULL; mpdat_hdr dathdr; mp3file mp3f; gchar *datname = NULL, *dirname = NULL; double seconds = 0; struct frame *frm; struct stat statbuf; pblist_track *metadata = NULL; gboolean format_ok = TRUE; /* check parameters for sanity */ if (( device == NULL ) || ( filename == NULL )) { errno = EINVAL; goto out; } /* check if there's a folder available */ if ( device->folderlist == NULL ) { if ( device->autosync ) { if ( mple_parse_pblist( device ) == NULL ) { if ( errno == 0 ) { errno = ENOENT; /* rrrr. */ } goto out; } } else { errno = ENOENT; goto out; } } if ( g_list_length( device->folderlist ) == 0 ) { errno = ENOENT; /* not exactly, but... */ return 0; } /* can't write a file unless the MSN has been obtained */ if ( !( device->aux & MPLE_MSN ) && !mple_get_media_serial_number( device )) { errno = EADDRNOTAVAIL; /* a bit of a stretch, but. */ return 0; } /* find a track number */ if (( tracknum = mple_dev_get_next_trackno( device )) == 0 ) { goto out; } /* build the conversion array for the new track */ mple_build_conv_array( tracknum, conv, device->msn & 0xff ); if ( stat( filename, &statbuf ) != 0 ) { goto out; } /* IWBNI: check device for space here. compare to the size of the mp3 file, and don't even TRY writing anything if there's insufficient room */ if (( mp3 = fopen( filename, "rb" )) == NULL ) { goto out; } if ( track == NULL ) { /* no metadata provided, so we build it from the file */ gchar *basename; if (( metadata = mple_get_metadata_FILE( mp3 )) == NULL ) { goto out; } /* copy the filename data. Note, this assumes / as a directory separator, and a pretty-much flat ASCII filename */ basename = strrchr( filename, '/' ); if ( basename != NULL ) { basename++; } else { basename = filename; } for ( i = 0; i < strlen( basename ); i++ ) { if ( i == 127 ) { break; } metadata->filename[i] = GUINT16_FROM_BE( basename[i] ); } metadata->filename[i] = 0; } else { metadata = track; } /* rewind mp3 file to start */ if ( fseek( mp3, 0, SEEK_SET ) != 0 ) { goto out; } /* easy parts of the header */ memset( &dathdr, 0, sizeof( mpdat_hdr )); memcpy( dathdr.signature, "WMMP", 4 ); dathdr.msn = GUINT32_TO_BE(device->msn); memcpy( dathdr.magic, MPDAT_MAGIC, sizeof( MPDAT_MAGIC )); /* get the time & frames */ mp3f.fp = mp3; mp3f.fsizeold = 0; mp3f.bsbuf = mp3f.bsspace[1]; mp3f.bsnum = 0; datname = g_strdup_printf( device->mptemplate, device->path, tracknum ); /* check if the directory for the file is present */ dirname = g_dirname( datname ); if ( mkpath( dirname, 0755 ) != TRUE ) { goto out; } g_free( dirname ); /* maybe we want to bail if the file exists */ unlink( datname ); if (( dat = fopen( datname, "wb" )) == NULL ) { goto out; } if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, dat ) != 1 ) { goto out; } /* initialise the framerate counters */ for ( i = 0; i < 15; i++ ) { framerates[i] = 0; } /* setting this to FALSE earlier will mask other errors, so we only do it now. */ format_ok = FALSE; /* Read the frames, counting as we go */ while( mpg123_read_frame( &mp3f, &frm )) { long where; int l; /* if we get at least one frame, then it's an MP3 file. */ format_ok = TRUE; /* however, it also needs to have a 44k1 sampling and a 8-320kbit encoding rate */ if (( mpg123_freqs[frm->sampling_frequency] != 44100 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] < 8 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] > 320 )) { format_ok = FALSE; goto out; } /* do a progress callback */ if ( device->progress != NULL ) { where = ftell( mp3 ); if ( where != -1 ) { (*(device->progress))( (double)where/(double)statbuf.st_size, device->context ); } } /* pull these out of the first frame */ if ( flen == 0 ) { flen = framesize( frm ); frate = tabsel_123[lsf( frm )][2][bitrate_index( frm )]; } frames++; framerates[15 - bitrate_index( frm )]++; /* write out the header - damned mpg123 code... */ for ( l = 3; l >= 0; l-- ) { if ( fputc( conv[header(frm)[l]], dat ) != conv[header(frm)[l]] ) { goto out; } } /* and the body */ for ( i = 0; i < framesize( frm ); i++ ) { mp3f.bsbuf[i] = conv[mp3f.bsbuf[i]]; } if ( fwrite( mp3f.bsbuf, framesize( frm ), 1, dat ) != 1 ) { goto out; } } if ( !format_ok ) { errno = EINVAL; /* why is there a *font file* format error, but no *generic file* format error? */ goto out; /* will clean up the file for us */ } /* by definition... */ if ( device->progress != NULL ) { (*(device->progress))( 1.0, device->context ); } /* now calculate the length */ for ( i = 0; i < 15; i++ ) { seconds += (double)(flen * framerates[i] ) / (double)(frate * 125); } /* convert to miliseconds */ seconds *= 1000; fflush( dat ); dathdr.size = GUINT32_FROM_BE( ftell( dat )); dathdr.frames = GUINT32_FROM_BE( frames ); dathdr.time = GUINT32_FROM_BE( seconds ); /* let the C lib do whatever truncating it likes */ /* write the updated header */ rewind( dat ); if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, dat ) != 1 ) { goto out; } fclose( dat ); /* now we need to update the pblist file */ if ( folder == 0 ) { /* this is kinda stupid... */ folder = 1; } retval = mple_add_to_folder( device, folder, metadata, tracknum, pos ); out: if ( mp3 != NULL ) { fclose( mp3 ); } if ( datname != NULL ) { if ( retval == 0 ) { unlink( datname ); } g_free( datname ); } if ( track == NULL && metadata != NULL ) { g_free( metadata ); } /* to be sure to be sure */ if ( !format_ok ) { errno = EINVAL; } return retval; } /* * find the track pointer for the specified track number */ mple_track *mple_get_track_ptr( mple_device *device, guint16 tracknum ) { GList *node; mple_track *tptr; mple_folder *fptr; if (( device == NULL ) || ( tracknum == 0 )) { errno = EINVAL; goto out; } if ( device->folderlist == NULL ) { if ( device->autosync ) { if ( mple_parse_pblist( device ) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } node = device->folderlist; while( node ) { GList *tnode; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { tptr = tnode->data; if ( tptr->tracknum == tracknum ) { return tptr; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } errno = ENOENT; out: return NULL; } /* * delete from DEVICE track TRACKNUM */ guint16 mple_del_track( mple_device *device, guint16 tracknum ) { mple_folder *fptr; mple_track *tptr; int retval = 0; tptr = mple_get_track_ptr( device, tracknum ); if ( tptr == NULL ) { goto out; } fptr = tptr->parent; fptr->tracklist = g_list_remove( fptr->tracklist, tptr ); if ( tptr->id3data != NULL ) { g_free( tptr->id3data ); tptr->id3data = NULL; } device->dirty = TRUE; if ( !device->autosync || mple_dev_sync( device )) { gchar *filename = g_strdup_printf( device->mptemplate, device->path, tptr->tracknum ); unlink( filename ); g_free( filename ); } else { /* flag error? wtf */ } g_free( tptr ); retval = 1; out: return retval; } /* * Change the position of a DEVICE's track TRACKNUM to folder FOLDER * position POS. If FOLDER is 0, keep it within the folder it's * already in. If POS is 0, append it to the folder it's being moved * to. */ guint16 mple_mv_track( mple_device *device, guint16 tracknum, guint16 folder, guint16 pos ) { GList *node; guint16 retval = 0; if (( device == NULL ) || ( tracknum == 0 )) { errno = EINVAL; goto out; } if (( node = device->folderlist ) == NULL ) { if ( device->autosync ) { if (( node = mple_parse_pblist( device )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } /* find the track */ while( node ) { GList *tnode; mple_folder *fptr = node->data; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { mple_track *tptr = tnode->data; mple_folder *nptr = fptr; if ( tptr->tracknum == tracknum ) { if ( folder != 0 ) { if ( g_list_nth( device->folderlist, folder - 1 ) != NULL ) { nptr = g_list_nth_data( device->folderlist, folder - 1 ); tptr->parent = nptr; } else { errno = ENOENT; goto out; } } fptr->tracklist = g_list_remove( fptr->tracklist, tptr ); if ( pos == 0 ) { nptr->tracklist = g_list_append( nptr->tracklist, tptr ); pos = g_list_length( nptr->tracklist ); } else { nptr->tracklist = g_list_insert( nptr->tracklist, tptr, pos - 1); } device->dirty = TRUE; if ( device->autosync ) { if ( !mple_dev_sync( device )) { goto out; } } retval = pos; goto out; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } out: return retval; } /* * 'rename' DEVICE's track TRACKNUM using the metadata structure TRACK * this just does a raw copy. It's up to the caller to make sure that * the data is sane. */ guint16 mple_ren_track( mple_device *device, guint16 tracknum, pblist_track *track ) { GList *node; guint16 retval = 0; if (( device == NULL ) || ( tracknum == 0 ) || ( track == NULL )) { errno = EINVAL; goto out; } if (( node = device->folderlist ) == NULL ) { if ( device->autosync ) { if (( node = mple_parse_pblist( device )) == NULL ) { goto out; } } else { errno = ENOENT; goto out; } } /* find the track */ while( node ) { GList *tnode; mple_folder *fptr = node->data; fptr = node->data; tnode = fptr->tracklist; while( tnode ) { mple_track *tptr = tnode->data; if ( tptr->tracknum == tracknum ) { if ( memmove( &(tptr->trackdata), track, sizeof( tptr->trackdata )) == NULL ) { goto out; } device->dirty = TRUE; if ( device->autosync ) { if ( !mple_dev_sync( device )) { goto out; } } retval = tracknum; goto out; } tnode = g_list_next( tnode ); } node = g_list_next( node ); } out: return retval; } /* find all likely devices */ #ifdef HAL_VERSION /* * use HAL to do the job. Some code duplication here, but screw * it. Also some clunkiness, but screw that, too. */ GList *mple_get_nw_devs_from_hal( gchar *magic ) { #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif LibHalContext *hc = NULL; GList *retval = NULL; char **alldevs = NULL; int i, n; #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "volume.uuid", &error )) { char *parent = libhal_device_get_property_string( hc, alldevs[i], "block.storage_device", &error ); char *dvpt = libhal_device_get_property_string( hc, alldevs[i], "block.device", &error ); dbus_bool_t mounted = libhal_device_get_property_bool( hc, alldevs[i], "volume.is_mounted", &error ); char *mtpt = NULL; if ( mounted ) { mtpt = libhal_device_get_property_string( hc, alldevs[i], "volume.mount_point", &error ); } if (( parent != NULL ) && ( strlen( parent ) >= strlen( magic )) && ( strcmp( &parent[strlen( parent ) - strlen( magic )], magic ) == 0 )) { mple_device *dev = mple_dev_init( mtpt ); dev->device = g_strdup( dvpt ); retval = g_list_append( retval, dev ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { libhal_free_string( mtpt ); } if ( parent != NULL ) { libhal_free_string( parent ); } } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { dbus_connection_disconnect( dc ); dbus_connection_unref( dc ); } dbus_error_free( &error ); #endif return retval; } #endif /* auto-probe a nw device */ #ifdef HAL_VERSION /* * use HAL to do the job. Need to be corrected by a real C guy :( */ GList *mple_probe_nw_devs_from_hal() { #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif LibHalContext *hc = NULL; GList *retval = NULL; char **alldevs = NULL; char *model = NULL; char *nw_udi = NULL; char *parent_udi = NULL; int i, j, n; #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } /* Ugly algo : walk and walk */ /* The alldevs isn't sorted hierarchicaly (at least the lshal command isn't */ /* Perhaps there's some find functions in libhal, but for the moment, */ /* to preserve 0.4/0.5 compatibility, use basic ones */ /* First, search a NW device */ for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "storage.model", &error )) { model = libhal_device_get_property_string( hc, alldevs[i], "storage.model", &error ); if (( model != NULL ) && ( strlen( model ) == strlen( HAL_MAGIC )) && ( strcmp( model, HAL_MAGIC ) == 0 )) { /* Got one */ /* Could be double-checked with storage.bus='usb' and storage.vendor='SONY' */ nw_udi = libhal_device_get_property_string( hc, alldevs[i], "info.udi", &error ); /* Now, find the attached partition */ for ( j = 0; j < n; j++ ) { /* computer don't have parent... */ if ( libhal_device_property_exists( hc, alldevs[j], "info.parent", &error )) { parent_udi = libhal_device_get_property_string( hc, alldevs[j], "info.parent", &error ); if (( parent_udi != NULL ) && ( strlen( parent_udi ) == strlen( nw_udi )) && ( strcmp( parent_udi, nw_udi ) == 0 )) { /* Got the partition */ char *dvpt = libhal_device_get_property_string( hc, alldevs[j], "block.device", &error ); dbus_bool_t mounted = libhal_device_get_property_bool( hc, alldevs[j], "volume.is_mounted", &error ); char *mtpt = NULL; /* For the moment, only care about pre-mounted devices */ if ( mounted ) { mtpt = libhal_device_get_property_string( hc, alldevs[j], "volume.mount_point", &error ); mple_device *dev = mple_dev_init( mtpt ); dev->device = g_strdup( dvpt ); retval = g_list_append( retval, dev ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { libhal_free_string( mtpt ); } } } if ( parent_udi != NULL ) { libhal_free_string( parent_udi ); parent_udi = NULL; } } } } /* clean up */ if ( model != NULL ) { libhal_free_string( model ); model = NULL; } if ( nw_udi != NULL ) { libhal_free_string( nw_udi ); nw_udi = NULL; } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { dbus_connection_disconnect( dc ); dbus_connection_unref( dc ); } dbus_error_free( &error ); #endif return retval; } #endif GList *mple_get_nw_devs( gchar *magic ) { GList *retval = NULL; #ifdef HAL_VERSION if ( magic == NULL ) { retval = mple_probe_nw_devs_from_hal (); } else { retval = mple_get_nw_devs_from_hal( magic ); } #endif return retval; } /* * retrieve the media serial number. requires root privileges if you * don't have HAL, and there doesn't appear to be any way for me to * get around that. */ #ifdef HAL_VERSION /* * Use HAL to find the device and fetch its MSN and any other data * we're missing. */ gboolean mple_get_msn_from_hal( mple_device *device ) { #if HAL_VERSION == 5 DBusError error; DBusConnection *dc = NULL; #endif gboolean retval = FALSE; char **alldevs = NULL; int n, i; char *dvpt = NULL, *mtpt = NULL, *uuid = NULL; gboolean mtpt_gfree = FALSE; LibHalContext *hc = NULL; #if HAL_VERSION == 5 dbus_error_init( &error ); if (( dc = dbus_bus_get( DBUS_BUS_SYSTEM, &error )) == NULL ) { goto out; } if (( hc = libhal_ctx_new()) == NULL ) { goto out; } if ( !libhal_ctx_set_dbus_connection( hc, dc )) { goto out; } if ( !libhal_ctx_init( hc, &error )) { goto out; } #else if (( hc = hal_initialize( NULL, FALSE )) == NULL ) { goto out; } #endif /* phew. now we can actually ask HAL what's on the system */ if (( alldevs = libhal_get_all_devices( hc, &n, &error )) == NULL ) { goto out; } for ( i = 0; i < n; i++ ) { if ( libhal_device_property_exists( hc, alldevs[i], "volume.uuid", &error )) { mtpt = libhal_device_get_property_string( hc, alldevs[i], "volume.mount_point", &error ); uuid = libhal_device_get_property_string( hc, alldevs[i], "volume.uuid", &error ); dvpt = libhal_device_get_property_string( hc, alldevs[i], "block.device", &error ); /* validate uuid very briefly: set and 9 chars long (that's 8 chars of uuid plus a dash in the middle. */ if ( uuid != NULL && strlen( uuid ) == 9 ) { if (( device->device != NULL ) && ( dvpt != NULL ) && ( strcmp( device->device, dvpt ) == 0 )) { retval = TRUE; } /* HAL has been known to lie about mounted devices */ if ( mtpt == NULL || strlen( mtpt ) == 0 ) { FILE *pm; gchar *mounts = NULL; gchar buf[BUFSIZ]; size_t size, msize = 0; /* hope you don't have too many mounts... */ if (( pm = fopen( "/proc/mounts", "rb" )) != NULL ) { while(( size = fread( buf, 1, BUFSIZ, pm )) > 0 ) { mounts = g_realloc( mounts, msize + size ); memcpy( &mounts[msize], buf, size ); msize += size; } if ( msize > 0 ) { gchar *d = g_strstr_len( mounts, msize, dvpt ); if ( d != NULL && d + strlen( dvpt ) + 1 < mounts + msize ) { size_t l; d += strlen( dvpt ) + 1; for ( l = 0; d + l < mounts + msize && d[l] != ' '; l++ ); d[l] = '\0'; mtpt = g_strdup( d ); mtpt_gfree = TRUE; } } if ( mounts != NULL ) { g_free( mounts ); } fclose( pm ); } } if (( device->path != NULL ) && ( mtpt != NULL ) && ( strcmp( device->path, mtpt ) == 0 )) { retval = TRUE; } } if ( retval == TRUE ) { guint8 c; /* may as well fill in some other bits */ if ( device->device == NULL && dvpt != NULL ) { device->device = g_strdup( dvpt ); } if ( device->path == NULL && mtpt != NULL ) { device->path = g_strdup( mtpt ); } /* unpack the UUID into something I can use. This is a little nasty, but saves me using something like gscanner to get the job done. */ memmove( &(uuid[4]), &(uuid[5]), 5 ); /* overwrite the dash */ device->msn = 0; /* safety clown says */ for ( c = 0; c < 8; c++ ) { char v = uuid[c]; if ( v > 'F' ) { v -= ( 'a' - 10 ); } else if ( v > '9' ) { v -= ( 'A' - 10 ); } else { v -= '0'; } device->msn |= ( v << (( 7 - c )* 4 )); } device->msn = GUINT32_FROM_BE( device->msn ); } if ( dvpt != NULL ) { libhal_free_string( dvpt ); } if ( mtpt != NULL ) { if ( mtpt_gfree ) { g_free( mtpt ); } else { libhal_free_string( mtpt ); } } if ( uuid != NULL ) { libhal_free_string( uuid ); } if ( retval == TRUE ) { break; } } } out: if ( alldevs != NULL ) { libhal_free_string_array( alldevs ); } if ( hc != NULL ) { /* not sure about these - they may be covered by safeguards in HAL */ /* -> if hal is init'd, uninit it */ /* -> if hal is connected, disconnect it */ #if HAL_VERSION == 5 libhal_ctx_shutdown( hc, &error ); /* free hal ctx */ libhal_ctx_free( hc ); #else hal_shutdown( hc ); #endif } #if HAL_VERSION == 5 /* clean up dbus */ if ( dc != NULL ) { dbus_connection_disconnect( dc ); dbus_connection_unref( dc ); } #endif return retval; } #endif gboolean mple_get_media_serial_number( mple_device *device ) { FILE *d = NULL; gboolean retval = FALSE; if ( device == NULL ) { errno = EINVAL; } if ( device->aux & MPLE_MSN ) { retval = TRUE; goto out; } /* no device information -> we're doomed */ if ( device->device == NULL && device->path == NULL ) { errno = EINVAL; goto out; } #ifdef HAL_VERSION if (( retval = mple_get_msn_from_hal( device ))) { goto out; } /* else fall through */ #endif /* attempt #2: see if there's a pblist file we can mooch off */ device->aux &= ~MPLE_MSN; if ( mple_parse_pblist( device ) != NULL ) { if ( device->aux & MPLE_MSN ) { retval = TRUE; goto out; } } if ( device->device == NULL ) { errno = EINVAL; goto out; } /* attempt #3: raw device access */ if (( d = fopen( device->device, "rb" )) == NULL ) { goto out; } /* 0x27 = offset to serial number in boot sector */ if ( fseek( d, 0x27, SEEK_SET ) != 0 ) { goto out; } if (( fread( &(device->msn), sizeof( device->msn ), 1, d )) != 1 ) { goto out; } /* don't forget to byte-swap it */ device->msn = GUINT32_FROM_BE( device->msn ); out: if ( d != NULL ) { fclose( d ); } return retval; } /* * convert a WMMP file back to an MP3 file, writing it to fd */ int mple_convert_wmmp( mple_track *track, int fd ) { FILE *mp, *out; struct stat statbuf; mpdat_hdr header; guint8 buffer[BUFSIZ]; guint8 conv[256]; size_t i; char *mpdat = NULL; /* xxx */ int trackno = 0; if ( stat( mpdat, &statbuf ) != 0 ) { return 0; } if (( mp = fopen( mpdat, "rb" )) == NULL ) { return 0; } if (( out = fdopen( fd, "rb" )) == NULL ) { return 0; } /* read & discard the file signature */ if ( fread( &header, sizeof( mpdat_hdr), 1, mp ) != 1 ) { return 0; } if ( memcmp( header.signature, "WMMP", 4 ) != 0 ) { return 0; } fprintf( stderr, "track %d, size %x, time %ums, frames %d\n", trackno, GUINT32_FROM_BE( header.size ), GUINT32_FROM_BE( header.time ), GUINT32_FROM_BE( header.frames )); statbuf.st_size -= sizeof( mpdat_hdr ); while( statbuf.st_size ) { size_t size = fread( buffer, 1, BUFSIZ, mp ); if ( size <= 0 ) { break; } for ( i = 0; i < size; i++ ) { guint16 in = buffer[i]; fprintf( out, "%c", conv[in]); } statbuf.st_size -= size; } return 0; } /* * functions to provide support for filesystem-style access */ #define _mple_filesize(tptr) \ (tptr->id3datalen + GUINT32_FROM_BE(tptr->header.size) - sizeof( mpdat_hdr )) /* * create a filehandle. Should have similar semantics to fopen, once I * finish it out... */ MPLEFILE *mple_open( mple_device *device, guint16 folder, guint16 tracknum, const gchar *filename, char *mode ) { MPLEFILE *mfp = NULL; mple_track *tptr = NULL; gchar *datname = NULL; if ( device == NULL ) { errno = EINVAL; goto out; } if ( !(device->aux & MPLE_MSN)) { if ( !mple_get_media_serial_number( device )) { goto out; } } /* 0 => fetch next available track number */ if ( tracknum != 0 ) { tptr = mple_get_track_ptr( device, tracknum ); } /* * valid modes: * "r" "r+" - file must exist * "w", "w+" - file created if it doesn't exist * "a", "a+" - file created if it doesn't exist */ if ( mode[0] == 'r' && tptr == NULL ) { errno = ENOENT; goto out; } /* read/append: try and pick up any existing file data */ if ( tptr != NULL ) { if ( !mple_get_track_header( device, tracknum )) { goto out; } } if ( tptr == NULL && mode[0] == 'w' ) { /* XXX also cater for append! */ pblist_track track; size_t convlen; guint16 *newname; if ( filename == NULL || strlen( filename ) == 0 ) { errno = EINVAL; goto out; } if ( folder == 0 ) { /* xxx get first folder */ errno = EINVAL; goto out; } memset( &track, 0, sizeof( pblist_track )); newname = utf8_to_utf16be( filename, 127, &convlen ); memcpy( &track.filename, newname, convlen * 2 ); g_free( newname ); tracknum = mple_dev_get_next_trackno( device ); if ( mple_add_to_folder( device, folder, &track, tracknum, 0 ) == 0 ) { perror( "adding to folder" ); goto out; } tptr = mple_get_track_ptr( device, tracknum ); } mfp = g_malloc0( sizeof( MPLEFILE )); mfp->tptr = tptr; mfp->ptr = 0; mfp->buffer = NULL; mfp->bufsize = 0; mple_build_conv_array( tptr->tracknum, mfp->conv, device->msn & 0xff ); if ( mode[0] != 'r' ) { mfp->writing = TRUE; } if ( mfp->writing ) { mfp->mp3f = g_malloc0( sizeof( mp3file )); mfp->mp3f->fp = NULL; mfp->mp3f->fsizeold = 0; mfp->mp3f->bsbuf = mfp->mp3f->bsspace[1]; mfp->mp3f->bsnum = 0; } datname = g_strdup_printf( device->mptemplate, device->path, tptr->tracknum ); mfp->fp = fopen( datname, mode ); g_free( datname ); if ( mfp->fp == NULL ) { if ( mfp->writing == TRUE ) { g_free( tptr ); } g_free( mfp ); mfp = NULL; } else { mpdat_hdr dathdr; int terrno, i; memset( &dathdr, 0, sizeof( mpdat_hdr )); memcpy( dathdr.signature, "WMMP", 4 ); dathdr.msn = GUINT32_TO_BE(device->msn); memcpy( dathdr.magic, MPDAT_MAGIC, sizeof( MPDAT_MAGIC )); if ( mfp->writing == TRUE ) { /* xxx this isn't entirely appropriate for appending */ if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, mfp->fp ) != 1 ) { perror( "fwrite" ); terrno = errno; fclose( mfp->fp ); g_free( tptr ); g_free( mfp ); mfp = NULL; errno = terrno; goto out; } } for ( i = 0; i < 15; i++ ) { mfp->framerates[i] = 0; } mfp->frames = 0; } out: return mfp; } int mple_seek( MPLEFILE *mfp, long offset, int whence ) { int newptr = 0; int retval = 0; if ( mfp == NULL ) { errno = EBADF; return -1; } switch( whence ) { case SEEK_SET: newptr = offset; break; case SEEK_CUR: newptr += offset; break; case SEEK_END: newptr = _mple_filesize( mfp->tptr ) - offset; break; default: errno = EINVAL; return -1; } if ( newptr < 0 ) { errno = EINVAL; return -1; } /* fixme: if newptr won't fit in an off_t, return -1/EOVERFLOW */ if ( newptr > mfp->tptr->id3datalen ) { retval = fseek( mfp->fp, newptr - mfp->tptr->id3datalen + sizeof( mpdat_hdr ), SEEK_SET ); } else { retval = fseek( mfp->fp, sizeof( mpdat_hdr ), SEEK_SET ); } if ( retval == 0 ) { mfp->ptr = newptr; } return retval; } long mple_tell( MPLEFILE *mfp ) { if ( mfp == NULL ) { errno = EBADF; return -1; } return mfp->ptr; } size_t mple_read( void *ptr, size_t size, size_t nmemb, MPLEFILE *mfp ) { size_t retval = -1, i, nbytes; gchar *myptr = ptr; if ( mfp == NULL ) { errno = EBADF; goto out; } nbytes = MIN(( size * nmemb ), _mple_filesize( mfp->tptr ) - mfp->ptr ); if ( mfp->ptr < mfp->tptr->id3datalen && mfp->ptr + nbytes >= mfp->tptr->id3datalen ) { memcpy( myptr, &(mfp->tptr->id3data[mfp->ptr]), mfp->tptr->id3datalen - mfp->ptr ); retval = mfp->tptr->id3datalen - mfp->ptr; myptr += retval; mfp->ptr += retval; nbytes -= retval; } else { retval = 0; } if ( nbytes ) { nbytes = fread( myptr, 1, nbytes, mfp->fp ); if ( nbytes >= 0 ) { retval += nbytes; mfp->ptr += nbytes; /* apply conv */ for ( i = 0; i < nbytes; i++ ) { myptr[i] = mfp->conv[(guint8)myptr[i]]; } } else { goto out; } } out: return retval; } size_t mple_write( void *ptr, size_t size, size_t nmemb, MPLEFILE *mfp ) { size_t retval = -1; guint8 *buffer = NULL; signed long taglen; struct frame *frm; guint32 i; if ( mfp == NULL ) { errno = EBADF; goto out; } mfp->buffer = g_realloc( mfp->buffer, mfp->bufsize + ( size * nmemb )); memcpy( &mfp->buffer[mfp->bufsize], ptr, size * nmemb ); mfp->bufsize += ( size * nmemb ); if ( mfp->bufsize < ID3_TAG_QUERYSIZE ) { /* we need more data! */ goto fake_ok_out; } /* scan for ID3 tagging */ for ( buffer = mfp->buffer; buffer < mfp->buffer + mfp->bufsize - ID3_TAG_QUERYSIZE; buffer++ ) { if (( taglen = id3_tag_query( buffer, mfp->bufsize - ( buffer - mfp->buffer ))) != 0 ) { if ( taglen < 0 ) { /* this isn't catered for yet. what we'd need to do is: - save our place in the file - seek backwards taglen bytes - deobfuscate - remove bytes from stream The problem is that since the data isn't mp3 frames, it's prboably not there to be retrieved. */ fprintf( stderr, "found id3v2.4 footer, crap\n" ); } else { /* we may not have got all the tagging, but I want to make sure it's really a tag and not a fortuitious sequence of bytes. However, I'm not clear on how to do this! */ /* this doesn't appear to be a problem, as it happens, or if it is the mp3 code doesn't cater for it either */ if ( buffer + taglen > mfp->buffer + mfp->bufsize ) { goto fake_ok_out; } else { struct id3_tag *tag; int idx; struct id3_frame *frame; tag = id3_tag_parse( buffer, taglen ); if ( tag == NULL || id3_tag_findframe( tag, "", 0 ) == NULL ) { continue; } /* lifted from mple_get_metadata */ for ( idx = 0; ( frame = id3_tag_findframe( tag, "T", idx )); idx++ ) { union id3_field *field = id3_frame_field( frame, 1 ); const id3_ucs4_t *text = id3_field_getstrings( field, 0 ); id3_utf16_t *rtext; /* rendered */ guint16 *dest = NULL; if ( text == NULL ) { continue; } rtext = id3_ucs4_utf16duplicate( text ); if ( memcmp( frame->id, ID3_FRAME_TITLE, 4 ) == 0 ) { dest = mfp->tptr->trackdata.title; } else if ( memcmp( frame->id, ID3_FRAME_ARTIST, 4 ) == 0 ) { dest = mfp->tptr->trackdata.artist; } /* else we're not interested */ if ( dest != NULL && dest[0] == 0 ) { guint8 i; for ( i = 0; i < 128; i++ ) { /* the device wants UTF16BE */ dest[i] = GUINT16_FROM_BE( rtext[i] ); if ( rtext[i] == 0 ) { break; } } } free( rtext ); } id3_tag_delete( tag ); /* push it up into the pblist */ if ( mple_ren_track( mfp->tptr->dev, mfp->tptr->tracknum, &mfp->tptr->trackdata ) == 0 ) { /* report an error, I guess xxx */ } buffer += taglen; } } } } /* now write to the file */ /* load up the buffer */ mfp->mp3f->fb = mfp->buffer; mfp->mp3f->fb_ptr = 0; mfp->mp3f->fb_size = mfp->bufsize; /* Read the frames, counting as we go */ while( mpg123_read_frame( mfp->mp3f, &frm )) { int l; /* if we get at least one frame, then it's an MP3 file. */ mfp->format_ok = TRUE; /* however, it also needs to have a 44k1 sampling and a 8-320kbit encoding rate */ if (( mpg123_freqs[frm->sampling_frequency] != 44100 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] < 8 ) || ( tabsel_123[lsf( frm )][2][bitrate_index( frm )] > 320 )) { mfp->format_ok = FALSE; errno = EINVAL; goto bailout; } /* pull these out of the first frame */ if ( mfp->flen == 0 ) { mfp->flen = framesize( frm ); mfp->frate = tabsel_123[lsf( frm )][2][bitrate_index( frm )]; } mfp->frames++; mfp->framerates[15 - bitrate_index( frm )]++; /* write out the header - damned mpg123 code... */ for ( l = 3; l >= 0; l-- ) { if ( fputc( mfp->conv[header(frm)[l]], mfp->fp ) != mfp->conv[header(frm)[l]] ) { goto bailout; } } /* and the body */ for ( i = 0; i < framesize( frm ); i++ ) { mfp->mp3f->bsbuf[i] = mfp->conv[mfp->mp3f->bsbuf[i]]; } if ( fwrite( mfp->mp3f->bsbuf, framesize( frm ), 1, mfp->fp ) != 1 ) { goto bailout; } } /* check for incomplete transfer of data */ if ( mfp->mp3f->fb_ptr != mfp->mp3f->fb_size ) { guint32 left = mfp->mp3f->fb_size - mfp->mp3f->fb_ptr; memmove( mfp->buffer, &mfp->buffer[mfp->bufsize - left], left ); mfp->buffer = g_realloc( mfp->buffer, left ); mfp->bufsize = left; goto fake_ok_out; } g_free( mfp->buffer ); mfp->buffer = NULL; mfp->bufsize = 0; /* lie to the caller about how we did */ fake_ok_out: retval = size * nmemb; out: return retval; /* fatal error with data stacked ends up here */ bailout: g_free( mfp->buffer ); mfp->buffer = NULL; mfp->bufsize = 0; goto out; } int mple_close( MPLEFILE *mfp ) { int retval = 0; mpdat_hdr dathdr; if ( mfp != NULL ) { if ( mfp->fp != NULL ) { if ( mfp->writing == TRUE ) { double seconds = 0; int i; /* need this to figure out the filesize */ fseek( mfp->fp, 0, SEEK_END ); /* now calculate the length */ for ( i = 0; i < 15; i++ ) { seconds += (double)(mfp->flen * mfp->framerates[i] ) / (double)(mfp->frate * 125); } /* convert to miliseconds */ seconds *= 1000; memset( &dathdr, 0, sizeof( mpdat_hdr )); memcpy( dathdr.signature, "WMMP", 4 ); dathdr.size = GUINT32_FROM_BE( ftell( mfp->fp )); dathdr.frames = GUINT32_FROM_BE( mfp->frames ); dathdr.time = GUINT32_FROM_BE( seconds ); dathdr.msn = GUINT32_TO_BE( mfp->tptr->dev->msn ); memcpy( dathdr.magic, MPDAT_MAGIC, sizeof( MPDAT_MAGIC )); fflush( mfp->fp ); rewind( mfp->fp ); if ( fwrite( &dathdr, sizeof( mpdat_hdr ), 1, mfp->fp ) != 1 ) { perror( "writing header" ); } } if ( mfp->bufsize != 0 ) { /* fprintf( stderr, " %ld bytes left!\n", mfp->bufsize );*/ /* I don't bloody well care. your input is malformed, if there's anything left */ } if ( mfp->buffer != NULL ) { g_free( mfp->buffer ); } if ( mfp->mp3f != NULL ) { g_free( mfp->mp3f ); } retval = fclose( mfp->fp ); if ( retval != 0 ) { perror( "closing file" ); } mfp->fp = NULL; } g_free( mfp ); } return retval; } int mple_stat( mple_device *device, guint16 tracknum, struct stat *buf ) { int retval = -1; mple_track *tptr = NULL; gchar *datname = NULL; if ( buf == NULL ) { errno = EINVAL; goto out; } if (( tptr = mple_get_track_ptr( device, tracknum )) == NULL ) { goto out; } datname = g_strdup_printf( device->mptemplate, device->path, tracknum ); retval = stat( datname, buf ); g_free( datname ); if ( retval == 0 ) { if ( mple_get_track_header( device, tracknum )) { /* adjust for our nefarious purposes */ buf->st_size = _mple_filesize( tptr ); } else { retval = -1; } } out: return retval; } /* * pull the track header and the ID3 metadata for the specified track */ gboolean mple_get_track_header( mple_device *device, guint16 tracknum ) { mple_track *tptr; gboolean retval = FALSE; FILE *fp = NULL; gchar *datname = NULL; struct id3_tag *tag = NULL; struct id3_frame *frame = NULL; id3_ucs4_t *title = NULL; id3_ucs4_t *artist = NULL; guint16 swapped[129], i; tptr = mple_get_track_ptr( device, tracknum ); if ( tptr == NULL ) { goto out; } /* short-circuit */ if ( tptr->id3data != NULL ) { retval = TRUE; goto out; } datname = g_strdup_printf( device->mptemplate, device->path, tptr->tracknum ); if (( fp = fopen( datname, "rb" )) == NULL ) { goto out; } if (( fread( &(tptr->header), sizeof( mpdat_hdr ), 1, fp )) != 1 ) { goto out; } /* now populate the ID3 structure */ tag = id3_tag_new(); frame = id3_frame_new( "TIT2" ); id3_field_settextencoding( &frame->fields[0], ID3_FIELD_TEXTENCODING_UTF_8 ); /* annoyingly, the id3 library can't handle UTF16BE. On the other hand, this allows me to make sure that the string's null-terminated. */ memset( swapped, 0, 129 ); for ( i = 0; i < 128 && tptr->trackdata.title[i]; i++ ) { swapped[i] = GUINT16_FROM_BE( tptr->trackdata.title[i] ); } title = id3_utf16_ucs4duplicate( swapped ); id3_field_addstring( &frame->fields[1], title ); id3_tag_attachframe( tag, frame ); id3_frame_delete( frame ); frame = id3_frame_new( "TPE1" ); id3_field_settextencoding(&frame->fields[0], ID3_FIELD_TEXTENCODING_UTF_8 ); memset( swapped, 0, 129 ); for ( i = 0; i < 128 && tptr->trackdata.artist[i]; i++ ) { swapped[i] = GUINT16_FROM_BE( tptr->trackdata.artist[i] ); } artist = id3_utf16_ucs4duplicate( swapped ); id3_field_addstring( &frame->fields[1], artist ); id3_tag_attachframe( tag, frame ); id3_frame_delete( frame ); tptr->id3datalen = id3_tag_render( tag, NULL ); tptr->id3data = g_malloc0( tptr->id3datalen ); tptr->id3datalen = id3_tag_render( tag, (id3_byte_t *)tptr->id3data ); retval = TRUE; out: if ( title != NULL ) { free( title ); } if ( artist != NULL ) { free( artist ); } if ( tag != NULL ) { id3_tag_delete( tag ); } if ( fp != NULL ) { fclose( fp ); } if ( datname != NULL ) { g_free( datname ); } return retval; } /* * helper functions to convert track/album data to and from utf8 */ /* * render a UTF16BE string to UTF8 */ char *utf8_from_utf16be( char *inbuffer, size_t maxlen ) { iconv_t cd = (iconv_t) -1; size_t outbytesleft, inbytesleft; size_t status; char *outbuffer = NULL, *outbuf; char *inbuf = inbuffer; /* Find out how many bytes to convert. Inbound data will be an array of 16bit values, either null-terminated or running right up against maxlen. */ for ( inbytesleft = 0; inbytesleft < maxlen; inbytesleft++ ) { if ( inbuf[inbytesleft * 2 + 1] == 0 && inbuf[inbytesleft * 2] == 0 ) { break; } } /* insane, but possible: UTF8 can use up to six bytes to represent a character. The +1 is for a trailing NULL */ outbytesleft = inbytesleft * 6 + 1; outbuffer = g_malloc( outbytesleft ); memset( outbuffer, 0, outbytesleft ); outbuf = outbuffer; /* convert 16bit size to 8bit size */ inbytesleft *= 2; cd = iconv_open( "UTF8", "UTF16BE" ); if ( cd != (iconv_t)(-1)) { status = iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); if ( status != -1 ) { goto out; } } /* if we get here, some part of the conversion failed */ perror( "conv" ); out: if ( cd != (iconv_t) -1 ) { iconv_close( cd ); } return outbuffer; } /* and back again */ guint16 *utf8_to_utf16be( const char *inbuffer, size_t maxlen, size_t *convlen ) { iconv_t cd = (iconv_t) -1; size_t outbytesleft, inbytesleft; size_t status, count; guint16 *outbuffer = NULL; char *outbuf; char *inbuf = (char *)inbuffer; inbytesleft = strlen( inbuffer ); outbytesleft = 2 * maxlen; outbuffer = g_malloc( outbytesleft ); memset( outbuffer, 0, outbytesleft ); outbuf = (char *)outbuffer; count = outbytesleft; cd = iconv_open( "UTF16BE", "UTF8" ); if ( cd != (iconv_t)(-1)) { status = iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft ); if ( convlen != NULL ) { *convlen = count - outbytesleft; } if ( status != -1 ) { goto out; } } /* if we get here, some part of the conversion failed */ perror( "conv" ); out: if ( cd != (iconv_t) -1 ) { iconv_close( cd ); } return outbuffer; } /* * I don't really recommend using these functions; they're fairly * blunt. However, I can't make head nor tail of the documentation on * wide characters, multibyte characters, and so on, which means that * I'm using these until I figure out what I *should* be doing. */ /* convert guint16 character array */ gchar *mple_getstr( guint16 *input ) { wchar_t *wchar = guint16o_wchar( input ); gchar *output = NULL; output = g_malloc0( sizeof( char ) * wcslen( wchar ) + 1 ); if (( output != NULL ) && ( wchar != NULL )) { sprintf( output, "%ls", wchar ); g_free( wchar ); } return output; } /* bluntly convert a guint16 'character' array to a wchar_t character array. This is clamped to an upper limit of 128 chars, because that's as big as they get in this code */ wchar_t *guint16o_wchar( guint16 *input ) { wchar_t *output; size_t i; output = g_malloc0( sizeof( wchar_t ) * 129 ); for ( i = 0; i < 128; i++ ) { output[i] = GUINT16_FROM_BE( input[i] ); output[i + 1] = L'\0'; if ( input[i] == 0 ) { break; } } return output; }