#include #include #include #include #include #include #include #include #include #include "libnw.h" #include "mple_v2.h" #include "common.h" #include "util.h" /* functions for dealing with common file structures */ typedef struct _mple_v2_obj_ptr { gchar magic[4]; guint32 offset; guint32 length; guint32 padding; } mple_v2_obj_ptr; typedef struct _mple_v2_obj_hdr { gchar magic[4]; guint16 count; guint16 size; gchar padding[8]; } mple_v2_obj_hdr; /* * common utility functions to pull standardish bits out of the * metadata files */ static FILE *mple_v2_get_filehandle( mple_v2_device_t *device, gchar *filename, gchar *mode ) { FILE *fp = NULL; gchar *path = NULL; assert( device != NULL ); if ( device->files == NULL ) { perror( "implement lazy hashtable build" ); goto out; } if (( path = (gchar *)g_hash_table_lookup( device->files, filename )) != NULL) { gchar *hashed = (gchar *)g_hash_table_lookup( device->files, filename ); if ( hashed == NULL ) { if ( strcmp( mode, "wb" ) == 0 ) { hashed = g_strup( filename ); g_hash_table_insert( device->files, g_strdup( hashed ), g_strdup( hashed )); } else { goto out; } } path = g_strdup_printf( "%s/%s", device->omgpath, hashed ); if (( fp = fopen( path, mode )) == NULL ) { goto out; } dprintf( stderr, "%s: got %s filehandle for %s\n", __FUNCTION__, mode, g_basename( path )); } else { errno = ENOENT; goto out; } out: if ( path != NULL ) { g_free( path ); } return fp; } static mple_v2_obj_hdr *mple_v2_get_object( mple_v2_device_t *device, gchar *filename, gchar *type, guint32 n ) { mple_v2_obj_hdr *retval = NULL; guint32 count = 0; FILE *fp = NULL; if (( fp = mple_v2_get_filehandle( device, filename, "rb" )) == NULL ) { goto out; } if ( fseek( fp, 0, SEEK_SET ) != 0 ) { errno = ferror( fp ); goto out; } while( !feof( fp )) { mple_v2_obj_ptr buf; guint32 *bmagic, *tmagic; gchar *ptr; if ( fread( (gchar *)&buf, sizeof( mple_v2_obj_ptr ), 1, fp ) != 1 ) { errno = ferror( fp ); break; } bmagic = (guint32 *)&buf.magic; tmagic = (guint32 *)type; if ( *bmagic == *tmagic ) { if ( count != n ) { count++; continue; } if ( fseek( fp, GUINT32_FROM_BE( buf.offset ), SEEK_SET ) != 0 ) { errno = ferror( fp ); goto out; } /* read the header */ retval = g_new0( mple_v2_obj_hdr, 1 ); if ( fread( retval, sizeof( mple_v2_obj_hdr ), 1, fp ) != 1 ) { if ( feof( fp )) { errno = ENODATA; } else { errno = ferror( fp ); } g_free( retval ); retval = NULL; goto out; } errno = 0; retval->count = GUINT16_FROM_BE( retval->count ); retval->size = GUINT16_FROM_BE( retval->size ); /* resize the block, and read in the rest of the data (i.e. all the records */ retval = g_realloc( retval, sizeof( mple_v2_obj_hdr ) + retval->count * retval->size ); ptr = (gchar *)retval + sizeof( mple_v2_obj_hdr ); if ( fread( ptr, retval->size, retval->count, fp ) != retval->count ) { if ( feof( fp )) { errno = ENODATA; } else { errno = ferror( fp ); } g_free( retval ); retval = NULL; goto out; } break; } } if ( retval == NULL ) { errno = ENODATA; } out: if ( fp != NULL ) { fclose( fp ); } return retval; } /* various types of file magic */ /* 01TREEXX.DAT */ static gchar tree_magic[] = { 'T', 'R', 'E', 'E', 0x01, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* 04CNTINF.DAT */ static gchar cnif_magic[] = { 'C', 'N', 'I', 'F', 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /* record structures. these are a bit of a mess as I try to figure out what's what and what's the best way of handling them */ typedef struct _gtlb_rec { guint16 recnum; guint16 group; gchar padding[12]; guint16 tagcount; guint16 zeros; guint32 tags[]; } gtlb_rec; typedef struct _gpfb_rec { gchar padding[8]; guint32 duration; /* duration in ms, although not 100% sure */ guint16 count; guint16 size; } gpfb_rec; typedef struct _gplb_hdr { gchar magic[4]; guint16 count; guint16 size; guint32 count2; /* no idea */ guint32 zeros; } gplb_hdr; typedef struct _gplb_rec { guint16 idx03; /* index in the corresponding 03GINFxx.DAT file */ guint16 unk1; /* always 0100? */ guint16 start; /* start range in the TPLB object */ guint16 unk2; /* always 0? */ } gplb_rec; typedef struct _tplb_rec { guint8 magic[4]; guint16 count; /* number of tracks in playlist */ guint16 size; /* size of track record */ guint16 zero; /* seems to be 0x0000 always */ guint16 files; /* number of files on device ? */ guint32 zeroes; /* seems to be 0x0000 0x0000 always */ guint16 records[]; } tplb_rec; typedef struct _cnfb_tag { struct { gchar frametype[4]; /* ID3 frame name */ guint8 fieldtype; /* type of ID3 field ? */ guint8 fieldcoding; /* field coding, assuming the above is 0 */ } header; guint16 data[]; } cnfb_tag; typedef struct _cnfb_dat { struct { gchar type[4]; /* not really sure what this is */ guint32 songtag; /* some sort of tag? */ guint32 duration; /* duration in ms? not sure about this */ guint16 count; /* number of tags records */ guint16 size; /* size of each tag record */ } header; gchar tagblock[]; } cnfb_dat; typedef struct _cnfb_rec { mple_v2_obj_hdr header; gchar recblock[]; } cnfb_rec; /* * free up all memory associated with DEVICE * do not use the pointer after you've called this! * NB cleans up only the MPLE portion (private cleanup) */ void mple_v2_dev_free( nw_device_t *dev ) { mple_v2_device_t *device = (mple_v2_device_t *)dev; dprintf( stderr, "%s: enter (dev = %p)\n", __FUNCTION__, dev ); if ( device != NULL ) { if ( device->omgpath != NULL ) { g_free( device->omgpath ); } if ( device->omapath != NULL ) { g_free( device->omapath ); } if ( device->dvidpath != NULL ) { g_free( device->dvidpath ); } } /* clean up files */ if ( device->files != NULL ) { g_hash_table_destroy(device->files); } dprintf( stderr, "%s: exit\n", __FUNCTION__ ); } /* XXX stubs with fixed values to allow random bits of the test harness to run */ static guint16 mple_v2_get_next_track_number( nw_device_t *dev ) { dprintf( stderr, "ERROR %s not implemented\n", __FUNCTION__ ); return 0xa0; } /* * synchronize the in-memory version of DEVICE to the device */ static gboolean mple_v2_dev_sync( nw_device_t *dev ) { mple_v2_device_t *device = (mple_v2_device_t *)dev; gboolean retval = TRUE; FILE *fp = NULL; guint i = 0; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); /* write 00GTRLST.DAT */ /* noop right now as this file appears to be full of magic */ /* write 01TREEXX.DAT */ for ( i = 1; i <= 4; i++ ) { gchar *filename = g_strdup_printf( "01TREE%02X.DAT", i ); if (( fp = mple_v2_get_filehandle( device, filename, "wb" )) == NULL ) { g_free( filename ); retval = FALSE; goto out; } fwrite( tree_magic, sizeof( tree_magic ), 1, fp ); { mple_v2_obj_ptr ptr; gplb_hdr grec; memcpy( ptr.magic, "GPLB", 4 ); ptr.offset = GUINT32_TO_BE( sizeof( cnif_magic ) + sizeof( ptr ) * 2 ); ptr.length = GUINT32_TO_BE( sizeof( grec )); ptr.padding = 0; fwrite( &ptr, sizeof( ptr ), 1, fp ); /* now the actual record */ memcpy( grec.magic, "GPLB", 4 ); grec.count = 0; grec.size = 0; grec.count2 = 0; grec.zeros = 0; fwrite( &grec, sizeof( grec ), 1, fp ); } g_free( filename ); } /* write 02TREINF.DAT */ /* write 03GINFXX.DAT */ /* write 04CNTINF.DAT */ /* xxx should maybe write a backup and then move it into place? */ if (( fp = mple_v2_get_filehandle( device, "04CNTINF.DAT", "wb" )) == NULL ) { retval = FALSE; goto out; } fwrite( cnif_magic, sizeof( cnif_magic ), 1, fp ); /* let's pretend it's empty first */ { mple_v2_obj_ptr ptr; cnfb_rec rec; memcpy( ptr.magic, "CNFB", 4 ); ptr.offset = GUINT32_TO_BE( sizeof( cnif_magic ) + sizeof( mple_v2_obj_ptr )); ptr.length = GUINT32_TO_BE( sizeof( rec.header )); ptr.padding = 0; fwrite( &ptr, sizeof( ptr ), 1, fp ); /* now the actual record */ memcpy( rec.header.magic, "CNFB", 4 ); rec.header.count = 0; rec.header.size = 0; /* doesn't really matter */ memset( rec.header.padding, 0, sizeof( rec.header.padding )); fwrite( &rec, sizeof( rec.header ), 1, fp ); } fclose( fp ); fp = NULL; /* write 05CIDLST.DAT */ out: if ( fp != NULL ) { fclose( fp ); } dprintf( stderr, "%s: exit %s\n", __FUNCTION__, retval == TRUE ? "true" : "false"); return retval; } static int mple_v2_stat( nw_device_t *dev, guint32 tracknum, struct stat *buf ) { /* a bit excessive XXX */ GDir *dir = NULL; GError *error = NULL; const gchar *readpath = NULL; gchar *omaname; mple_v2_device_t *device = (mple_v2_device_t *)dev; if (( dir = g_dir_open( device->omapath, 0, &error )) == NULL ) { return -1; } omaname = g_strdup_printf( "%08X.OMA", ( 0x10000000 | tracknum )); while (( readpath = g_dir_read_name( dir )) != NULL ) { if ( !strcasecmp( readpath, omaname )) { g_free( omaname ); omaname = g_strdup_printf( "%s/%s", device->omapath, readpath ); break; } } /* whooo */ return stat( omaname, buf ); } static int mple_v2_seek( NWFILE *nfp, long offset, int whence ) { dprintf( stderr, "ERROR %s not implemented\n", __FUNCTION__ ); return 0; } static long mple_v2_tell( NWFILE *nfp ) { dprintf( stderr, "ERROR %s not implemented\n", __FUNCTION__ ); return 10; } static int mple_v2_close( NWFILE *nfp ) { dprintf( stderr, "ERROR %s not implemented\n", __FUNCTION__ ); return 0; } /* * These are API funcs mainly to support making nw_track_t opaque. */ /* * duration of track in ms */ static guint32 mple_v2_get_track_time( nw_track_t *track ) { return track->time; } /* * size of (extracted) file in bytes */ static guint32 mple_v2_get_track_size( nw_track_t *track ) { return track->size; } /* * number of mp3 frames in file */ static guint32 mple_v2_get_track_frames( nw_track_t *track ) { dprintf( stderr, "%s: enter\n", __FUNCTION__ ); /* ok, so it's possible you'll have a track with no frames. but it's unlikely. So we'll use that as a gate for lazy code. */ if ( track->frames == 0 ) { NWFILE *nfp = track->device->fopen( track->device, 0, track->tracknum, "", "r" ); if ( nfp != NULL ) { track->device->fclose( nfp ); } } dprintf( stderr, "%s: exit\n", __FUNCTION__ ); return track->frames; } /* * initialise a v2 device */ nw_device_t *mple_v2_dev_init( gchar *devpath ) { nw_device_t *dev = NULL; nw_attr_t *path_attr; mple_v2_device_t *device = NULL; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); device = g_new0( mple_v2_device_t, 1 ); dev = (nw_device_t *)device; dev->api = NW_MPLE_V2; path_attr = g_new0( nw_attr_t, 1 ); path_attr->type = NW_STRING; path_attr->data.gc = g_strdup( devpath ); nw_set_attr( dev, "mountpoint", path_attr ); dev->dev_free = mple_v2_dev_free; dev->parse_directory = mple_v2_parse_directory; device->files = g_hash_table_new( g_str_hash, g_str_equal ); dev->dev_sync = mple_v2_dev_sync; /* track interface */ dev->get_next_track_number = mple_v2_get_next_track_number; dev->get_track_size = mple_v2_get_track_size; dev->get_track_time = mple_v2_get_track_time; dev->get_track_frames = mple_v2_get_track_frames; /* filesystem interface */ dev->stat = mple_v2_stat; dev->fopen = mple_v2_open; dev->fclose = mple_v2_close; dev->fseek = mple_v2_seek; dev->ftell = mple_v2_tell; dev->fread = mple_v2_read; dev->fwrite = mple_v2_write; dev->autosync = TRUE; dev->dirty = FALSE; dprintf( stderr, "%s: exit\n", __FUNCTION__ ); return dev; } /* * figure out the contents of the device! */ GList *mple_v2_parse_directory( nw_device_t *dev ) { mple_v2_device_t *device = (mple_v2_device_t *)dev; GDir *dir = NULL; GError *error = NULL; GList *retval = NULL; GList *tracknums = NULL; gchar *path = NULL; const gchar *readpath = NULL; FILE *fp = NULL; guint32 i; guint16 maxFILES = 0, foundFILES = 0; mple_v2_obj_hdr *hdr = NULL; guint32 offset = 0; nw_attr_t *path_attr = NULL; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); errno = 0; if ( dev == NULL || (( path_attr = nw_get_attr( dev, "mountpoint" )) == NULL )) { errno = EINVAL; goto out; } /* * required files: * omgaudio/[whole bunch of files].dat * * preferred files: * omgaudio/10f00/[files].oma * - not sure where the 10f00 comes from, either. * - if this is absent, we may still have empty folders * mp3fm/dvid.dat * * the case-insensitive file compare stuff is WAY overkill but * intended for my test environment where the file case varies... */ if (( dir = g_dir_open( NW_STR_ATTR( path_attr ), 0, &error )) == NULL ) { goto out; } while(( readpath = g_dir_read_name( dir )) != NULL ) { if ( !strcasecmp( readpath, "OMGAUDIO" )) { device->omgpath = g_strdup_printf( "%s/%s", NW_STR_ATTR( path_attr ), readpath ); /* leak */ } else if ( !strcasecmp( readpath, "MP3FM" )) { device->dvidpath = g_strdup_printf( "%s/%s", NW_STR_ATTR( path_attr ), readpath ); } } if ( device->omgpath == NULL ) { dprintf( stderr, "%s: can't find OMGAUDIO directory in %s\n", __FUNCTION__, NW_STR_ATTR( path_attr ) ); errno = ENOENT; goto out; } g_dir_close( dir ); dir = NULL; if (( dir = g_dir_open( device->omgpath, 0, &error )) == NULL ) { goto out; } while(( readpath = g_dir_read_name( dir )) != NULL ) { if ( !strcasecmp( readpath, "10F00" )) { device->omapath = g_strdup_printf( "%s/%s", device->omgpath, readpath ); } /* identify other files */ g_hash_table_insert( device->files, g_strup( g_strdup( readpath )), /* leak */ g_strdup( readpath )); dprintf( stderr, " hashing %s\n", readpath ); } g_dir_close( dir ); dir = NULL; /* We'd like a DvID file, as that allows us to do the unscrambling without needing the USB query */ if ( device->dvidpath != NULL ) { if (( dir = g_dir_open( device->dvidpath, 0, &error )) == NULL ) { dprintf( stderr, "failed to open %s\n", device->dvidpath ); goto out; } while(( readpath = g_dir_read_name( dir )) != NULL ) { if ( !strcasecmp( readpath, "DvID.dat" )) { path = g_strdup_printf( "%s/%s", device->dvidpath, readpath ); g_free( device->dvidpath ); device->dvidpath = path; path = NULL; /* see cleanup */ break; } } g_dir_close( dir ); dir = NULL; /* fetch the DvID key */ if (( fp = fopen( device->dvidpath, "rb" )) != NULL ) { if ( fseek( fp, 0x0b, SEEK_SET ) == 0 ) { if ( fread( &device->dvid, sizeof( guint32 ), 1, fp ) != 1 ) { errno = ENODATA; goto out; } } else { errno = ENODATA; goto out; } fclose( fp ); fp = NULL; } else { /* fopen failed. XXX check if it's because dvidpath is a directory, in which case we don't mind */ dprintf( stderr, "failed opening %s\n", device->dvidpath ); } } /* now we start parsing stuff */ /* 00GTRLST.DAT: for now just dump it */ #ifdef DEBUG hdr = mple_v2_get_object( device, "00GTRLST.DAT", "SYSB", 0 ); if ( hdr != NULL ) { guint8 *p = (guint8 *)hdr; for ( i = 0; i < hdr->size; i++ ) { /* dprintf( stderr, "%02x ", p[i] ); if ( i && !(i % 16)) { dprintf( stderr, "\n" ); } */ } /*dprintf( stderr, "\n" );*/ dprintf( stderr, "%s: type/version: %02x\n", __FUNCTION__, p[8] ); } else { dprintf( stderr, "can't find 00GTRLST.DAT: %s\n", strerror( errno )); } hdr = mple_v2_get_object( device, "00GTRLST.DAT", "GTLB", 0 ); if ( hdr != NULL ) { /*guint8 *p = (guint8 *)hdr;*/ for ( i = 0; i < hdr->size; i++ ) { /* dprintf( stderr, "%02x ", p[i] ); if ( i && !(i % 16)) { dprintf( stderr, "\n" ); } */ } /*dprintf( stderr, "\n" );*/ } else { dprintf( stderr, "can't find 00GTRLST.DAT: %s\n", strerror( errno )); } #endif /* Get the number of files on the device - not sure this is 100% reliable, though */ hdr = mple_v2_get_object( device, "01TREE01.DAT", "TPLB", 0 ); if ( hdr != NULL ) { maxFILES = GUINT16_FROM_BE(((tplb_rec *)hdr)->files ); g_free( hdr ); hdr = NULL; } else { dprintf( stderr, "%s: failed to get initial TPLB object\n", __FUNCTION__ ); goto out; } /* get the file numbers */ for ( i = 1; i < 0xFF && foundFILES < maxFILES ; i++ ) { gchar *file = g_strdup_printf( "01TREE%02X.DAT", i ); tplb_rec *rec = (tplb_rec *)mple_v2_get_object( device, file, "TPLB", 0 ); if ( rec != NULL ) { guint16 j; for ( j = 0; j < rec->count; j++ ) { guint16 *track = g_new0( guint16, 1 ); if ( foundFILES++ == maxFILES ) { break; } *track = GUINT16_FROM_BE( rec->records[j] ); tracknums = g_list_append( tracknums, track ); } } else { if ( errno == ENOENT ) { /* we don't care; only the first four files need to exist */ } else { dprintf( stderr, "failed to get tplb rec: %s\n", strerror( errno )); } } } /* 03GINFXX.DAT seems to contain the on-device folder name early on as TIT2 */ /* 04CNTINF.DAT seems to be the *actual* contents of the device. The other files appear to get updated lazily, i.e. old data isn't removed. */ hdr = mple_v2_get_object( device, "04CNTINF.DAT", "CNFB", 0 ); if ( hdr == NULL ) { dprintf( stderr, "failed to get CNFB object: %s\n", strerror( errno )); goto out; } offset = 0; for ( i = 0; i < maxFILES; i++ ) { cnfb_rec *rec = (cnfb_rec *)hdr; cnfb_dat *dat = (cnfb_dat *)&(rec->recblock[offset]); guint16 j; nw_track_t *tptr; guint16 *k; /* if this happens, your index files are inconsistent! */ if (( i + 1 ) > rec->header.count ) { continue; } dprintf( stderr, "track %d of %d\n", i + 1, maxFILES ); tptr = g_new0( nw_track_t, 1 ); tptr->device = dev; tptr->time = GUINT32_FROM_BE( dat->header.duration ); /* may be wrong? */ tptr->id3tag = id3_tag_new(); k = (guint16 *)(g_list_nth( tracknums, i )->data); tptr->tracknum = *k; /* don't call this, it triggers an infinite loop */ /* tptr->frames = nw_track_frames( tptr ); */ tptr->frames = 0; /* * v2 devices do not appear to store the original filename in any * useful way. Instead, we'll just fake one. */ tptr->filename = g_strdup_printf( "file%d.mp3", tptr->tracknum ); /* xxx do a readdir/hashtable thing here to avoid case problems */ tptr->fullpath = g_strdup_printf( "%s/%08X.OMA", device->omapath, ( 0x10000000 | tptr->tracknum )); offset += sizeof( dat->header ); for ( j = 0; j < GUINT16_FROM_BE( dat->header.count ); j++ ) { struct id3_frame *frame; cnfb_tag *tag = (cnfb_tag *)&(dat->tagblock[j * GUINT16_FROM_BE( dat->header.size )]); gchar frametype[5]; offset += GUINT16_FROM_BE( dat->header.size ); bzero( frametype, 5 ); memcpy( frametype, tag->header.frametype, 4 ); frame = id3_frame_new( frametype ); /* this code is built for text frames only. */ if ( frametype[0] == 'T' ) { /* is this right? */ guint16 c; id3_utf16_t *utf16string; id3_ucs4_t *ucsstring; id3_utf8_t *utf8string; /* once more, annoyance at id3tag's failure to deal with UTF16BE */ id3_field_settextencoding( &frame->fields[0], ID3_FIELD_TEXTENCODING_UTF_16 ); utf16string = g_malloc0( GUINT16_FROM_BE( dat->header.size ) - sizeof( tag->header )); for ( c = 0; c < ( GUINT16_FROM_BE( dat->header.size ) - sizeof( tag->header )) / 2; c++ ) { utf16string[c] = GUINT16_FROM_BE( tag->data[c] ); } ucsstring = id3_utf16_ucs4duplicate( utf16string ); /* leak */ utf8string = id3_ucs4_utf8duplicate( ucsstring ); /* xxx questionable, although it seems to work */ if ( memcmp( frametype, ID3_FRAME_ALBUM, 4 ) == 0 ) { guint16 folder; GList *node; nw_folder_t *fptr = NULL; id3_utf8_t *utf8string = id3_ucs4_utf8duplicate( ucsstring ); tptr->parent = NULL; for ( folder = 0; ( node = g_list_nth( retval, folder )) != NULL; folder++ ) { fptr = node->data; if ( !strcmp( fptr->name, (gchar *)utf8string )) { tptr->parent = fptr; break; } } if ( tptr->parent == NULL ) { /* didn't find a matching album, so add one to the list */ fptr = g_new0( nw_folder_t, 1 ); fptr->device = dev; fptr->name = (gchar *)id3_ucs4_utf8duplicate( ucsstring ); fptr->tracklist = NULL; retval = g_list_append( retval, fptr ); tptr->parent = fptr; } g_free( utf8string ); } else if ( memcmp( frametype, ID3_FRAME_TITLE, 4 ) == 0 ) { id3_utf8_t *utf8string = id3_ucs4_utf8duplicate( ucsstring ); nw_set_tag( tptr, ID3_FRAME_TITLE, utf8string ); } else { dprintf( stderr, "%s: skipping frame %s\n", __FUNCTION__, frametype ); } id3_field_addstring( &frame->fields[1], ucsstring ); id3_tag_attachframe( tptr->id3tag, frame ); id3_frame_delete( frame ); } else { dprintf( stderr, "%s: skipping frame %s\n", __FUNCTION__, frametype ); } } /* only add to tracklist if parent found */ if ( tptr->parent ) { tptr->parent->tracklist = g_list_append( tptr->parent->tracklist, tptr ); } } errno = 0; out: if ( dir != NULL ) { g_dir_close( dir ); } if ( path != NULL ) { g_free( path ); } if ( fp != NULL ) { fclose( fp ); } dev->folderlist = retval; dprintf( stderr, "%s: exit, retval is %p\n", __FUNCTION__, retval ); return retval; } /* XXX stub */ guint32 mple_v2_key( mple_v2_device_t *device, guint32 tracknum ) { dprintf( stderr, "ERROR %s not implemented\n", __FUNCTION__ ); return ( 0x2465 + tracknum * 0x5296E435 ) ^ device->dvid; } /* * I'm not sure about the need for this. All the v1 version does is * ensure the header block has been pulled, and then reads the ID3 * tags. We're doing the second part separately already, and the first * part I find slightly troubling as I may end up with an infinite * loop if I'm not careful. Redesign required, perhaps. */ gboolean mple_v2_get_track_header( nw_device_t *dev, guint16 tracknum ) { gchar *omaname = NULL; FILE *fp = NULL; mple_v2_device_t *device = (mple_v2_device_t *)dev; /* XXX stuff that might possibly need to go here is in _open right now */ omaname = g_strdup_printf( "%s/%08X.OMA", device->omapath, ( 0x10000000 | tracknum )); if (( fp = fopen( omaname, "rb" )) != NULL ) { dprintf( stderr, "found %s\n", omaname ); fclose( fp ); } return TRUE; } /* currently hugely ripped from mple v1 while I try and get this all together */ NWFILE *mple_v2_open( nw_device_t *dev, guint32 folder, guint32 tracknum, const gchar *filename, char *mode ) { MPLEV2FILE *mfp = NULL; nw_track_t *tptr = NULL; gchar *omaname = NULL; nw_track_t *nw_track = NULL; mple_v2_device_t *device = (mple_v2_device_t *)dev; dprintf( stderr, "%s: enter\n", __FUNCTION__ ); assert( dev != NULL ); tptr = nw_get_track_ptr( dev, tracknum ); if ( mode[0] == 'r' && tptr == NULL ) { errno = ENOENT; goto out; } if ( tptr != NULL ) { if ( !mple_v2_get_track_header( dev, tracknum )) { goto out; } } if ( tptr == NULL && mode[0] == 'w' ) { nw_track = g_new0( nw_track_t, 1 ); nw_track->device = dev; if ( filename == NULL || strlen( filename ) == 0 ) { errno = EINVAL; goto out; } if ( folder == 0 ) { if ( dev->folderlist == NULL ) { if ( dev->autosync ) { if ( dev->parse_directory( dev ) == NULL ) { if ( errno == 0 ) { errno = ENOENT; } goto out; } } else { errno = ENOENT; goto out; } } folder = 1; } nw_track->filename = g_strdup( g_basename( filename )); if ( nw_add_to_folder( dev, folder, nw_track, tracknum, 0 ) == 0 ) { dprintf( stderr, "adding to folder: %s\n", strerror( errno )); goto out; } tptr = nw_get_track_ptr( dev, tracknum ); } mfp = g_new0( MPLEV2FILE, 1 ); mfp->nwfile.tptr = tptr; mfp->nwfile.ptr = 0; mfp->nwfile.buffer = NULL; mfp->nwfile.bufsize = 0; /*mple_v2_build_conv_array( tptr->tracknum, mfp->conv, device->msn & 0xff ) */ if ( mode[0] != 'r' ) { mfp->nwfile.writing = TRUE; } if ( mfp->nwfile.writing ) { mfp->nwfile.mp3f = g_new0( mp3file, 1 ); mfp->nwfile.mp3f->fp = NULL; mfp->nwfile.mp3f->fsizeold = 0; mfp->nwfile.mp3f->bsbuf = mfp->nwfile.mp3f->bsspace[1]; mfp->nwfile.mp3f->bsnum = 0; } /* XXX this should be in parse_directory */ if ( device->omapath == NULL ) { device->omapath = g_strdup_printf( "%s/%s", device->omgpath, "010F00" ); } if ( mfp->nwfile.writing ) { if ( mkpath( device->omapath, 0755 ) != TRUE ) { goto out; } } /* now see if our file is present */ if ( mode[0] == 'r' ) { GDir *dir = NULL; GError *error = NULL; const gchar *readpath = NULL; if (( dir = g_dir_open( device->omapath, 0, &error )) == NULL ) { goto out; } omaname = g_strdup_printf( "%08X.OMA", ( 0x10000000 | tptr->tracknum )); while (( readpath = g_dir_read_name( dir )) != NULL ) { if ( !strcasecmp( readpath, omaname )) { g_free( omaname ); omaname = g_strdup_printf( "%s/%s", device->omapath, readpath ); break; } } } else { omaname = g_strdup_printf( "%s/%08X.OMA", device->omapath, ( 0x10000000 | tptr->tracknum )); } /* hacky - if there's no slash in omaname, we didn't find a useful match */ if ( !index( omaname, '/' )) { goto out; } mfp->nwfile.fp = fopen( omaname, "r" /* mode xxx */ ); if ( mfp->nwfile.fp == NULL ) { dprintf( stderr, "%s: null file handle for %s\n", __FUNCTION__, omaname ); } g_free( omaname ); if ( mfp->nwfile.fp == NULL ) { if ( mfp->nwfile.writing == TRUE ) { g_free( tptr ); } g_free( mfp ); mfp = NULL; } else { if ( mfp->nwfile.writing == TRUE ) { /* XXX write the header dude */ } else { /* scoop up the header */ struct id3_tag *tag; signed long taglen; guint8 *tagbuf; tagbuf = g_malloc0( 10 ); /* minimum buffer size to get tags */ if ( fread( tagbuf, 1, 10, mfp->nwfile.fp ) != 10 ) { perror( "fread ea3 header" ); /* XXX */ goto out; } /* Sony: ID3 -> ea3. plonkers. */ tagbuf[0] = 'I'; tagbuf[1] = 'D'; if (( taglen = id3_tag_query( tagbuf, 10 )) > 0 ) { tagbuf = g_realloc( tagbuf, taglen ); if ( taglen > 10 ) { if ( fread( &tagbuf[10], 1, taglen - 10, mfp->nwfile.fp ) != taglen - 10 ) { perror( "reading the rest of the tag" ); goto out; } } tag = id3_tag_parse( tagbuf, taglen ); if ( tag == NULL || id3_tag_findframe( tag, "", 0 ) == NULL ) { dprintf( stderr, "no/null tag found\n" ); if ( tag != NULL ) { id3_tag_delete( tag ); tag = NULL; } } else { libnw_update_metadata( mfp->nwfile.tptr, tag ); } /* now read the auxilliary information */ /* which we haven't fully figured out */ fread( tagbuf, 1, 6, mfp->nwfile.fp ); /* EA3\x2\xN\xN */ taglen = tagbuf[4] * 256 + tagbuf[5]; tagbuf = realloc( tagbuf, taglen ); fread( &tagbuf[6], 1, taglen - 6, mfp->nwfile.fp ); { ea3_hdr *foo = (ea3_hdr *)tagbuf; tptr->frames = GUINT32_FROM_BE( foo->frames ); if ( GUINT32_FROM_BE( foo->thing2 ) != 0x380D910 ) { dprintf( stderr, "thing2 anomaly: expexted 0380d910, found %08x\n", foo->thing2 ); } /* size: stat the file subtract 3072 ('ea3' tag) subtract foo->size add size of rendered ID3 tag */ } } } } out: dprintf( stderr, "%s: exit mfp = %p\n", __FUNCTION__, mfp ); return (NWFILE *)mfp; } size_t mple_v2_write( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { size_t retval = -1; /* MPLEV2FILE *mfp = (MPLEV2FILE *)nfp;*/ retval = nmemb; goto out; out: return nmemb; } size_t mple_v2_read( void *ptr, size_t size, size_t nmemb, NWFILE *nfp ) { size_t retval = 0, nread; nread = fread( ptr, size, nmemb, nfp->fp ); /* patch ID3 tag at start of file */ if ( nfp->ptr == 0 ) { if ( nread * size > 2 ) { ((gchar *)ptr)[0] = 'I'; ((gchar *)ptr)[1] = 'D'; } } nfp->ptr += size * nmemb; retval = nread; goto out; out: return retval; }