Search
lxdream.org :: lxdream/src/vmu/vmuvol.c
lxdream 0.9.1
released Jun 29
Download Now
filename src/vmu/vmuvol.c
changeset 1071:182cfe43c09e
prev1056:d0896e6530d6
next1296:30ecee61f811
author nkeynes
date Wed Sep 08 08:43:15 2010 +1000 (13 years ago)
permissions -rw-r--r--
last change Actually report the error when the command-line disc or bin couldn't be loaded
view annotate diff log raw
     1 /**
     2  * $Id$
     3  *
     4  * VMU volume (ie block device) support
     5  *
     6  * Copyright (c) 2009 Nathan Keynes.
     7  *
     8  * This program is free software; you can redistribute it and/or modify
     9  * it under the terms of the GNU General Public License as published by
    10  * the Free Software Foundation; either version 2 of the License, or
    11  * (at your option) any later version.
    12  *
    13  * This program is distributed in the hope that it will be useful,
    14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16  * GNU General Public License for more details.
    17  */
    19 #include <glib/gmem.h>
    20 #include <glib/gstrfuncs.h>
    21 #include <string.h>
    22 #include <unistd.h>
    23 #include <stdio.h>
    24 #include <fcntl.h>
    25 #include <errno.h>
    27 #include "vmu/vmuvol.h"
    28 #include "dream.h"
    29 #include "lxpaths.h"
    31 #define VMU_MAX_PARTITIONS 256
    32 #define VMU_MAX_BLOCKS 65536 /* Actually slightly less than this, but it'll do */
    34 typedef struct vmu_partition {
    35     struct vmu_volume_metadata metadata;
    36     uint32_t block_count;
    37     char *blocks;
    38 } *vmu_partition_t;
    40 struct vmu_volume {
    41     const gchar *display_name;
    42     vmu_partnum_t part_count;
    43     gboolean dirty;
    44     struct vmu_partition part[0];
    45 };
    47 /* On-VMU structures, courtesy of Marcus Comstedt */ 
    48 struct vmu_superblock {
    49     char magic[16];
    50     uint8_t colour_flag;
    51     uint8_t bgra[4];
    52     uint8_t pad1[27];
    53     char timestamp[8];
    54     char pad2[8];
    55     char unknown[6];
    56     uint16_t fat_block;
    57     uint16_t fat_size;
    58     uint16_t dir_block;
    59     uint16_t dir_size;
    60     uint16_t icon_shape;
    61     uint16_t user_size;
    62     /* remainder unknown */
    63 };
    65 struct vmu_direntry {
    66     uint8_t filetype;
    67     uint8_t copy_flag;
    68     uint16_t blkno;
    69     char filename[12];
    70     char timestamp[8];
    71     uint16_t blksize; /* Size in blocks*/
    72     uint16_t hdroff; /* Header offset in blocks */
    73     char pad[4];
    74 };
    76 #define MD(vmu,ptno) ((vmu)->part[ptno].metadata)
    78 #define VMU_BLOCK(vmu,ptno,blkno) (&(vmu)->part[ptno].blocks[(blkno)*VMU_BLOCK_SIZE])
    80 #define VMU_FAT_ENTRY(vmu,pt,ent)  ((uint16_t *)VMU_BLOCK(vmu, pt, (MD(vmu,pt).fat_block - ((ent)>>8))))[(ent)&0xFF]
    82 #define FAT_EMPTY 0xFFFC
    83 #define FAT_EOF   0xFFFA
    85 static const struct vmu_volume_metadata default_metadata = { 255, 255, 254, 1, 253, 13, 0, 200, {31, 0, 128} };
    87 vmu_volume_t vmu_volume_new_default( const gchar *display_name )
    88 {
    89     vmu_volume_t vol = g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition) );
    90     vol->part_count = 1;
    91     vol->dirty = FALSE;
    92     memcpy( &vol->part[0].metadata, &default_metadata, sizeof(struct vmu_volume_metadata) );
    93     vol->part[0].block_count = VMU_DEFAULT_VOL_BLOCKS;
    94     vol->part[0].blocks = g_malloc0( VMU_DEFAULT_VOL_BLOCKS * VMU_BLOCK_SIZE );
    95     vol->display_name = display_name == NULL ? NULL : g_strdup(display_name);
    96     vmu_volume_format( vol, 0, TRUE );
    97     return vol;
    98 }
   100 void vmu_volume_destroy( vmu_volume_t vol )
   101 {
   102     int i;
   103     if( vol == NULL )
   104         return;
   106     for( i=0; i<vol->part_count; i++ ) {
   107         g_free( vol->part[i].blocks );
   108         vol->part[i].blocks = NULL;
   109     }
   110     if( vol->display_name ) {
   111         g_free( (char *)vol->display_name );
   112         vol->display_name = NULL;
   113     }
   114     g_free(vol);
   115 }
   117 void vmu_volume_format( vmu_volume_t vol, vmu_partnum_t pt, gboolean quick )
   118 {
   119     if( pt >= vol->part_count ) {
   120         return;
   121     }
   123     if( !quick ) {
   124         /* Wipe it completely first */
   125         memset( vol->part[pt].blocks, 0, (vol->part[pt].block_count) * VMU_BLOCK_SIZE );
   126     }
   128     struct vmu_volume_metadata *meta = &vol->part[pt].metadata;
   129     unsigned int fatblkno = meta->fat_block;
   130     unsigned int dirblkno = meta->dir_block;
   132     /* Write superblock */
   133     struct vmu_superblock *super = (struct vmu_superblock *)VMU_BLOCK(vol,pt, meta->super_block);
   134     memset( super->magic, 0x55, 16 );
   135     memset( &super->colour_flag, 0, 240 ); /* Blank the rest for now */
   136     super->fat_block = meta->fat_block;
   137     super->fat_size = meta->fat_size;
   138     super->dir_block = meta->dir_block;
   139     super->user_size = meta->user_size;
   141     /* Write file allocation tables */
   142     int i,j;
   143     for( j=0; j<meta->fat_size; j++ ) {
   144         uint16_t *fat = (uint16_t *)VMU_BLOCK(vol,pt,fatblkno-j); 
   145         for( i=0; i<256; i++ ) {
   146             fat[i] = FAT_EMPTY;
   147         }
   148     }
   150     /* Fill in the system allocations in the FAT */
   151     for( i=0; i<meta->fat_size-1; i++ ) {
   152         VMU_FAT_ENTRY(vol,pt,fatblkno-i) = fatblkno-i-1;
   153     }
   154     VMU_FAT_ENTRY(vol,pt,fatblkno - i) = FAT_EOF;
   155     for( i=0; i<meta->dir_size-1; i++ ) {
   156         VMU_FAT_ENTRY(vol,pt,dirblkno-i) = dirblkno-i-1;
   157     }
   158     VMU_FAT_ENTRY(vol,pt,dirblkno-i) = FAT_EOF;
   160     /* If quick-format, blank the directory. Otherwise it's already been done */
   161     if( quick ) {
   162         memset( VMU_BLOCK(vol,pt,dirblkno-meta->dir_size+1),
   163                 0, meta->dir_size * VMU_BLOCK_SIZE );
   164     }
   165 }
   167 /*************************** File load/save ********************************/
   169 /**
   170  * Current file has 1 META chunk for all volume metadata, followed by a
   171  * DATA chunk for each partition's block data. The META chunk is required to
   172  * occur before any DATA blocks.
   173  * Unknown chunks are skipped to allow for forwards compatibility if/when
   174  * we add the VMU runtime side of things
   175  */
   177 struct vmu_file_header {
   178     char magic[16];
   179     uint32_t version;
   180     uint32_t head_len;
   181     uint32_t part_count;
   182     uint32_t display_name_len;
   183     char display_name[0];
   184 };
   186 struct vmu_chunk_header {
   187     char name[4];
   188     uint32_t length;
   189 };
   192 gboolean vmu_volume_save( const gchar *filename, vmu_volume_t vol, gboolean create_only )
   193 {
   194     struct vmu_file_header head;
   195     struct vmu_chunk_header chunk;
   196     int i;
   198     gchar *tempfile = get_filename_at(filename, ".XXXXXXX");
   199     int fd = mkstemp( tempfile );
   200     if( fd == -1 ) {
   201         g_free(tempfile);
   202         return FALSE;
   203     }
   205     FILE *f = fdopen( fd, "w+" );
   208     /* File header */
   209     memcpy( head.magic, VMU_FILE_MAGIC, 16 );
   210     head.version = VMU_FILE_VERSION;
   211     head.part_count = vol->part_count;
   212     head.display_name_len = vol->display_name == NULL ? 0 : (strlen(vol->display_name)+1);
   213     head.head_len = sizeof(head) + head.display_name_len;
   214     fwrite( &head, sizeof(head), 1, f );
   215     if( vol->display_name != NULL ) {
   216         fwrite( vol->display_name, head.display_name_len, 1, f );
   217     }
   219     /* METAdata chunk */
   220     memcpy( chunk.name, "META", 4 );
   221     chunk.length = sizeof(struct vmu_volume_metadata) * vol->part_count;
   222     fwrite( &chunk, sizeof(chunk), 1, f );
   223     for( i=0; i < vol->part_count; i++ ) {
   224         fwrite( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
   225     }
   227     /* partition DATA chunks */
   228     for( i=0; i< vol->part_count; i++ ) {
   229         memcpy( chunk.name, "DATA", 4 );
   230         chunk.length = 0;
   231         if( fwrite( &chunk, sizeof(chunk), 1, f ) != 1 ) goto cleanup;
   232         long posn = ftell(f);
   233         if( fwrite( &vol->part[i].block_count, sizeof(vol->part[i].block_count), 1, f ) != 1 ) goto cleanup;
   234         fwrite_gzip( vol->part[i].blocks, vol->part[i].block_count, VMU_BLOCK_SIZE, f );
   235         long end = ftell(f);
   236         fseek( f, posn - sizeof(chunk.length), SEEK_SET );
   237         chunk.length = end-posn;
   238         if( fwrite( &chunk.length, sizeof(chunk.length), 1, f ) != 1 ) goto cleanup;
   239         fseek( f, end, SEEK_SET );
   240     }
   241     fclose(f);
   242     f = NULL;
   244     if( rename(tempfile, filename) != 0 )
   245         goto cleanup;
   247     /* All good */
   248     vol->dirty = FALSE;
   249     g_free(tempfile);
   250     return TRUE;
   252 cleanup:
   253     if( f != NULL )
   254         fclose(f);
   255     unlink(tempfile);
   256     g_free(tempfile);
   257     return FALSE;
   258 }
   260 vmu_volume_t vmu_volume_load( const gchar *filename )
   261 {
   262     struct vmu_file_header head;
   263     struct vmu_chunk_header chunk;
   264     vmu_volume_t vol;
   265     int i;
   267     FILE *f = fopen( filename, "ro" );
   268     if( f == NULL ) {
   269         ERROR( "Unable to open VMU file '%s': %s", filename, strerror(errno) );
   270         return FALSE;
   271     }
   273     if( fread( &head, sizeof(head), 1, f ) != 1 ||
   274         memcmp(head.magic, VMU_FILE_MAGIC, 16) != 0 ||
   275         head.part_count > VMU_MAX_PARTITIONS || 
   276         head.head_len < (sizeof(head) + head.display_name_len) )  {
   277         fclose(f);
   278         ERROR( "Unable to load VMU '%s': bad file header", filename );
   279         return NULL;
   280     }
   282     vol = (vmu_volume_t)g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition)*head.part_count );
   283     vol->part_count = head.part_count;
   284     vol->dirty = FALSE;
   285     if( head.display_name_len != 0 ) {
   286         vol->display_name = g_malloc( head.display_name_len );
   287         fread( (char *)vol->display_name, head.display_name_len, 1, f );
   288     }
   289     fseek( f, head.head_len, SEEK_SET );
   291     gboolean have_meta = FALSE;
   292     int next_part = 0;
   293     while( !feof(f) && fread( &chunk, sizeof(chunk), 1, f ) == 1 ) {
   294         if( memcmp( &chunk.name, "META", 4 ) == 0 ) {
   295             if( have_meta || chunk.length != head.part_count * sizeof(struct vmu_volume_metadata) ) {
   296                 vmu_volume_destroy(vol);
   297                 fclose(f);
   298                 ERROR( "Unable to load VMU '%s': bad metadata size (expected %d but was %d)", filename,
   299                        head.part_count * sizeof(struct vmu_volume_metadata), chunk.length );
   300                 return NULL;
   301             }
   302             for( i=0; i<head.part_count; i++ ) {
   303                 fread( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
   304             }
   305             have_meta = TRUE;
   306         } else if( memcmp( &chunk.name, "DATA", 4 ) == 0 ) {
   307             uint32_t block_count;
   308             fread( &block_count, sizeof(block_count), 1, f );
   309             if( next_part >= vol->part_count || block_count >= VMU_MAX_BLOCKS ) {
   310                 // Too many partitions / blocks
   311                 vmu_volume_destroy(vol);
   312                 fclose(f);
   313                 ERROR( "Unable to load VMU '%s': too large (%d/%d)", filename, next_part, block_count );
   314                 return NULL;
   315             }
   316             vol->part[next_part].block_count = block_count;
   317             vol->part[next_part].blocks = g_malloc(block_count*VMU_BLOCK_SIZE);
   318             fread_gzip(vol->part[next_part].blocks, VMU_BLOCK_SIZE, block_count, f );
   319             next_part++;
   320         } else {
   321             // else skip unknown block
   322             fseek( f, SEEK_CUR, chunk.length );
   323             WARN( "Unexpected VMU data chunk: '%4.4s'", chunk.name );
   324         }
   325     }
   327     fclose(f);
   329     if( !have_meta || next_part != vol->part_count ) {
   330         vmu_volume_destroy( vol );
   331         return NULL;
   332     }
   334     return vol;
   335 }
   337 /*************************** Accessing data ********************************/
   338 const char *vmu_volume_get_display_name( vmu_volume_t vol ) 
   339 {
   340     return vol->display_name;
   341 }
   343 void vmu_volume_set_display_name( vmu_volume_t vol, const gchar *name )
   344 {
   345     if( vol->display_name != NULL ) {
   346         g_free( (char *)vol->display_name );
   347     }
   348     if( name == NULL ) {
   349         vol->display_name = NULL;
   350     } else {
   351         vol->display_name = g_strdup(name);
   352     }
   353 }
   355 gboolean vmu_volume_is_dirty( vmu_volume_t vol )
   356 {
   357     return vol->dirty;
   358 }
   360 gboolean vmu_volume_read_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *out )
   361 {
   362     if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
   363         return FALSE;
   364     }
   366     memcpy( out, VMU_BLOCK(vol,pt,block), VMU_BLOCK_SIZE );
   367     return TRUE;
   368 }
   370 gboolean vmu_volume_write_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *in )
   371 {
   372     if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
   373         return FALSE;
   374     }
   375     memcpy( VMU_BLOCK(vol,pt,block), in, VMU_BLOCK_SIZE );
   376     vol->dirty = TRUE;
   377     return TRUE;
   378 }
   380 gboolean vmu_volume_write_phase( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned int phase, unsigned char *in )
   381 {
   382     if( pt >= vol->part_count || block >= vol->part[pt].block_count || phase >= 4 ) {
   383         return FALSE;
   384     }
   385     memcpy( VMU_BLOCK(vol,pt,block) + (phase*128), in, VMU_BLOCK_SIZE/4 );
   386     vol->dirty = TRUE;
   387     return TRUE;
   388 }
   390 const struct vmu_volume_metadata *vmu_volume_get_metadata( vmu_volume_t vol, vmu_partnum_t partition )
   391 {
   392     if( partition >= vol->part_count ) {
   393         return NULL;
   394     } else {
   395         return &vol->part[partition].metadata;
   396     }
   397 }
.