nkeynes@1034: /** nkeynes@1035: * $Id$ nkeynes@1034: * nkeynes@1034: * VMU volume (ie block device) support nkeynes@1034: * nkeynes@1034: * Copyright (c) 2009 Nathan Keynes. nkeynes@1034: * nkeynes@1034: * This program is free software; you can redistribute it and/or modify nkeynes@1034: * it under the terms of the GNU General Public License as published by nkeynes@1034: * the Free Software Foundation; either version 2 of the License, or nkeynes@1034: * (at your option) any later version. nkeynes@1034: * nkeynes@1034: * This program is distributed in the hope that it will be useful, nkeynes@1034: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@1034: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@1034: * GNU General Public License for more details. nkeynes@1034: */ nkeynes@1034: nkeynes@1034: #include nkeynes@1034: #include nkeynes@1034: #include nkeynes@1034: #include nkeynes@1034: #include nkeynes@1034: #include nkeynes@1034: nkeynes@1034: #include "vmu/vmuvol.h" nkeynes@1034: #include "dream.h" nkeynes@1034: nkeynes@1034: #define VMU_MAX_PARTITIONS 256 nkeynes@1034: #define VMU_MAX_BLOCKS 65536 /* Actually slightly less than this, but it'll do */ nkeynes@1034: nkeynes@1034: typedef struct vmu_partition { nkeynes@1034: struct vmu_volume_metadata metadata; nkeynes@1034: uint32_t block_count; nkeynes@1034: char *blocks; nkeynes@1034: } *vmu_partition_t; nkeynes@1034: nkeynes@1034: struct vmu_volume { nkeynes@1034: const gchar *display_name; nkeynes@1034: vmu_partnum_t part_count; nkeynes@1034: gboolean dirty; nkeynes@1034: struct vmu_partition part[0]; nkeynes@1034: }; nkeynes@1034: nkeynes@1034: /* On-VMU structures, courtesy of Marcus Comstedt */ nkeynes@1034: struct vmu_superblock { nkeynes@1034: char magic[16]; nkeynes@1034: uint8_t colour_flag; nkeynes@1034: uint8_t bgra[4]; nkeynes@1034: uint8_t pad1[27]; nkeynes@1034: char timestamp[8]; nkeynes@1034: char pad2[8]; nkeynes@1034: char unknown[6]; nkeynes@1034: uint16_t fat_block; nkeynes@1034: uint16_t fat_size; nkeynes@1034: uint16_t dir_block; nkeynes@1034: uint16_t dir_size; nkeynes@1034: uint16_t icon_shape; nkeynes@1034: uint16_t user_size; nkeynes@1034: /* remainder unknown */ nkeynes@1034: }; nkeynes@1034: nkeynes@1034: struct vmu_direntry { nkeynes@1034: uint8_t filetype; nkeynes@1034: uint8_t copy_flag; nkeynes@1034: uint16_t blkno; nkeynes@1034: char filename[12]; nkeynes@1034: char timestamp[8]; nkeynes@1034: uint16_t blksize; /* Size in blocks*/ nkeynes@1034: uint16_t hdroff; /* Header offset in blocks */ nkeynes@1034: char pad[4]; nkeynes@1034: }; nkeynes@1034: nkeynes@1034: #define MD(vmu,ptno) ((vmu)->part[ptno].metadata) nkeynes@1034: nkeynes@1034: #define VMU_BLOCK(vmu,ptno,blkno) (&(vmu)->part[ptno].blocks[(blkno)*VMU_BLOCK_SIZE]) nkeynes@1034: nkeynes@1034: #define VMU_FAT_ENTRY(vmu,pt,ent) ((uint16_t *)VMU_BLOCK(vmu, pt, (MD(vmu,pt).fat_block - ((ent)>>8))))[(ent)&0xFF] nkeynes@1034: nkeynes@1034: #define FAT_EMPTY 0xFFFC nkeynes@1034: #define FAT_EOF 0xFFFA nkeynes@1034: nkeynes@1034: static const struct vmu_volume_metadata default_metadata = { 255, 255, 254, 1, 253, 13, 0, 200, 31, 0, 128 }; nkeynes@1034: nkeynes@1034: vmu_volume_t vmu_volume_new_default( const gchar *display_name ) nkeynes@1034: { nkeynes@1034: vmu_volume_t vol = g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition) ); nkeynes@1034: vol->part_count = 1; nkeynes@1034: vol->dirty = FALSE; nkeynes@1034: memcpy( &vol->part[0].metadata, &default_metadata, sizeof(struct vmu_volume_metadata) ); nkeynes@1034: vol->part[0].block_count = VMU_DEFAULT_VOL_BLOCKS; nkeynes@1034: vol->part[0].blocks = g_malloc0( VMU_DEFAULT_VOL_BLOCKS * VMU_BLOCK_SIZE ); nkeynes@1034: vol->display_name = display_name == NULL ? NULL : g_strdup(display_name); nkeynes@1034: vmu_volume_format( vol, 0, TRUE ); nkeynes@1034: return vol; nkeynes@1034: } nkeynes@1034: nkeynes@1034: void vmu_volume_destroy( vmu_volume_t vol ) nkeynes@1034: { nkeynes@1034: int i; nkeynes@1034: if( vol == NULL ) nkeynes@1034: return; nkeynes@1034: nkeynes@1034: for( i=0; ipart_count; i++ ) { nkeynes@1034: g_free( vol->part[i].blocks ); nkeynes@1034: vol->part[i].blocks = NULL; nkeynes@1034: } nkeynes@1034: if( vol->display_name ) { nkeynes@1034: g_free( (char *)vol->display_name ); nkeynes@1034: vol->display_name = NULL; nkeynes@1034: } nkeynes@1034: g_free(vol); nkeynes@1034: } nkeynes@1034: nkeynes@1034: void vmu_volume_format( vmu_volume_t vol, vmu_partnum_t pt, gboolean quick ) nkeynes@1034: { nkeynes@1034: if( pt >= vol->part_count ) { nkeynes@1034: return; nkeynes@1034: } nkeynes@1034: nkeynes@1034: if( !quick ) { nkeynes@1034: /* Wipe it completely first */ nkeynes@1034: memset( vol->part[pt].blocks, 0, (vol->part[pt].block_count) * VMU_BLOCK_SIZE ); nkeynes@1034: } nkeynes@1034: nkeynes@1034: struct vmu_volume_metadata *meta = &vol->part[pt].metadata; nkeynes@1034: unsigned int fatblkno = meta->fat_block; nkeynes@1034: unsigned int dirblkno = meta->dir_block; nkeynes@1034: nkeynes@1034: /* Write superblock */ nkeynes@1034: struct vmu_superblock *super = (struct vmu_superblock *)VMU_BLOCK(vol,pt, meta->super_block); nkeynes@1034: memset( super->magic, 0x55, 16 ); nkeynes@1034: memset( &super->colour_flag, 0, 240 ); /* Blank the rest for now */ nkeynes@1034: super->fat_block = meta->fat_block; nkeynes@1034: super->fat_size = meta->fat_size; nkeynes@1034: super->dir_block = meta->dir_block; nkeynes@1034: super->user_size = meta->user_size; nkeynes@1034: nkeynes@1034: /* Write file allocation tables */ nkeynes@1034: int i,j; nkeynes@1034: for( j=0; jfat_size; j++ ) { nkeynes@1034: uint16_t *fat = (uint16_t *)VMU_BLOCK(vol,pt,fatblkno-j); nkeynes@1034: for( i=0; i<256; i++ ) { nkeynes@1034: fat[i] = FAT_EMPTY; nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: /* Fill in the system allocations in the FAT */ nkeynes@1034: for( i=0; ifat_size-1; i++ ) { nkeynes@1034: VMU_FAT_ENTRY(vol,pt,fatblkno-i) = fatblkno-i-1; nkeynes@1034: } nkeynes@1034: VMU_FAT_ENTRY(vol,pt,fatblkno - i) = FAT_EOF; nkeynes@1034: for( i=0; idir_size-1; i++ ) { nkeynes@1034: VMU_FAT_ENTRY(vol,pt,dirblkno-i) = dirblkno-i-1; nkeynes@1034: } nkeynes@1034: VMU_FAT_ENTRY(vol,pt,dirblkno-i) = FAT_EOF; nkeynes@1034: nkeynes@1034: /* If quick-format, blank the directory. Otherwise it's already been done */ nkeynes@1034: if( quick ) { nkeynes@1034: memset( VMU_BLOCK(vol,pt,dirblkno-meta->dir_size+1), nkeynes@1034: 0, meta->dir_size * VMU_BLOCK_SIZE ); nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: /*************************** File load/save ********************************/ nkeynes@1034: nkeynes@1034: /** nkeynes@1034: * Current file has 1 META chunk for all volume metadata, followed by a nkeynes@1034: * DATA chunk for each partition's block data. The META chunk is required to nkeynes@1034: * occur before any DATA blocks. nkeynes@1034: * Unknown chunks are skipped to allow for forwards compatibility if/when nkeynes@1034: * we add the VMU runtime side of things nkeynes@1034: */ nkeynes@1034: nkeynes@1034: struct vmu_file_header { nkeynes@1034: char magic[16]; nkeynes@1034: uint32_t version; nkeynes@1034: uint32_t head_len; nkeynes@1034: uint32_t part_count; nkeynes@1034: uint32_t display_name_len; nkeynes@1034: char display_name[0]; nkeynes@1034: }; nkeynes@1034: nkeynes@1034: struct vmu_chunk_header { nkeynes@1034: char name[4]; nkeynes@1034: uint32_t length; nkeynes@1034: }; nkeynes@1034: nkeynes@1034: nkeynes@1034: nkeynes@1034: gboolean vmu_volume_save( const gchar *filename, vmu_volume_t vol, gboolean create_only ) nkeynes@1034: { nkeynes@1034: struct vmu_file_header head; nkeynes@1034: struct vmu_chunk_header chunk; nkeynes@1034: int i; nkeynes@1034: nkeynes@1034: FILE *f = fopen( filename, (create_only ? "wx" : "w") ); /* Portable? */ nkeynes@1034: if( f == NULL ) { nkeynes@1034: return FALSE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: /* File header */ nkeynes@1034: memcpy( head.magic, VMU_FILE_MAGIC, 16 ); nkeynes@1034: head.version = VMU_FILE_VERSION; nkeynes@1034: head.part_count = vol->part_count; nkeynes@1034: head.display_name_len = vol->display_name == NULL ? 0 : (strlen(vol->display_name)+1); nkeynes@1034: head.head_len = sizeof(head) + head.display_name_len; nkeynes@1034: fwrite( &head, sizeof(head), 1, f ); nkeynes@1034: if( vol->display_name != NULL ) { nkeynes@1034: fwrite( vol->display_name, head.display_name_len, 1, f ); nkeynes@1034: } nkeynes@1034: nkeynes@1034: /* METAdata chunk */ nkeynes@1034: memcpy( chunk.name, "META", 4 ); nkeynes@1034: chunk.length = sizeof(struct vmu_volume_metadata) * vol->part_count; nkeynes@1034: fwrite( &chunk, sizeof(chunk), 1, f ); nkeynes@1034: for( i=0; i < vol->part_count; i++ ) { nkeynes@1034: fwrite( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f ); nkeynes@1034: } nkeynes@1034: nkeynes@1034: /* partition DATA chunks */ nkeynes@1034: for( i=0; i< vol->part_count; i++ ) { nkeynes@1034: memcpy( chunk.name, "DATA", 4 ); nkeynes@1034: chunk.length = 0; nkeynes@1034: fwrite( &chunk, sizeof(chunk), 1, f ); nkeynes@1034: long posn = ftell(f); nkeynes@1034: fwrite( &vol->part[i].block_count, sizeof(vol->part[i].block_count), 1, f ); nkeynes@1034: fwrite_gzip( vol->part[i].blocks, vol->part[i].block_count, VMU_BLOCK_SIZE, f ); nkeynes@1034: long end = ftell(f); nkeynes@1034: fseek( f, posn - sizeof(chunk.length), SEEK_SET ); nkeynes@1034: chunk.length = end-posn; nkeynes@1034: fwrite( &chunk.length, sizeof(chunk.length), 1, f ); nkeynes@1034: fseek( f, end, SEEK_SET ); nkeynes@1034: } nkeynes@1034: fclose(f); nkeynes@1034: vol->dirty = FALSE; nkeynes@1034: return TRUE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: vmu_volume_t vmu_volume_load( const gchar *filename ) nkeynes@1034: { nkeynes@1034: struct vmu_file_header head; nkeynes@1034: struct vmu_chunk_header chunk; nkeynes@1034: vmu_volume_t vol; nkeynes@1034: int i; nkeynes@1034: nkeynes@1034: FILE *f = fopen( filename, "ro" ); nkeynes@1034: if( f == NULL ) { nkeynes@1034: ERROR( "Unable to open VMU file '%s': %s", filename, strerror(errno) ); nkeynes@1034: return FALSE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: if( fread( &head, sizeof(head), 1, f ) != 1 || nkeynes@1034: memcmp(head.magic, VMU_FILE_MAGIC, 16) != 0 || nkeynes@1034: head.part_count > VMU_MAX_PARTITIONS || nkeynes@1034: head.head_len < (sizeof(head) + head.display_name_len) ) { nkeynes@1034: fclose(f); nkeynes@1034: ERROR( "Unable to load VMU '%s': bad file header", filename ); nkeynes@1034: return NULL; nkeynes@1034: } nkeynes@1034: nkeynes@1034: vol = (vmu_volume_t)g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition)*head.part_count ); nkeynes@1034: vol->part_count = head.part_count; nkeynes@1034: vol->dirty = FALSE; nkeynes@1034: if( head.display_name_len != 0 ) { nkeynes@1034: vol->display_name = g_malloc( head.display_name_len ); nkeynes@1034: fread( (char *)vol->display_name, head.display_name_len, 1, f ); nkeynes@1034: } nkeynes@1034: fseek( f, head.head_len, SEEK_SET ); nkeynes@1034: nkeynes@1034: gboolean have_meta = FALSE; nkeynes@1034: int next_part = 0; nkeynes@1034: while( !feof(f) && fread( &chunk, sizeof(chunk), 1, f ) == 1 ) { nkeynes@1034: if( memcmp( &chunk.name, "META", 4 ) == 0 ) { nkeynes@1034: if( have_meta || chunk.length != head.part_count * sizeof(struct vmu_volume_metadata) ) { nkeynes@1034: vmu_volume_destroy(vol); nkeynes@1034: fclose(f); nkeynes@1034: ERROR( "Unable to load VMU '%s': bad metadata size (expected %d but was %d)", filename, nkeynes@1034: head.part_count * sizeof(struct vmu_volume_metadata), chunk.length ); nkeynes@1034: return NULL; nkeynes@1034: } nkeynes@1034: for( i=0; ipart[i].metadata, sizeof(struct vmu_volume_metadata), 1, f ); nkeynes@1034: } nkeynes@1034: have_meta = TRUE; nkeynes@1034: } else if( memcmp( &chunk.name, "DATA", 4 ) == 0 ) { nkeynes@1034: uint32_t block_count; nkeynes@1034: fread( &block_count, sizeof(block_count), 1, f ); nkeynes@1034: if( next_part >= vol->part_count || block_count >= VMU_MAX_BLOCKS ) { nkeynes@1034: // Too many partitions / blocks nkeynes@1034: vmu_volume_destroy(vol); nkeynes@1034: fclose(f); nkeynes@1034: ERROR( "Unable to load VMU '%s': too large (%d/%d)", filename, next_part, block_count ); nkeynes@1034: return NULL; nkeynes@1034: } nkeynes@1034: vol->part[next_part].block_count = block_count; nkeynes@1034: vol->part[next_part].blocks = g_malloc(block_count*VMU_BLOCK_SIZE); nkeynes@1034: fread_gzip(vol->part[next_part].blocks, VMU_BLOCK_SIZE, block_count, f ); nkeynes@1034: next_part++; nkeynes@1034: } else { nkeynes@1034: // else skip unknown block nkeynes@1034: fseek( f, SEEK_CUR, chunk.length ); nkeynes@1034: WARN( "Unexpected VMU data chunk: '%4.4s'", chunk.name ); nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: fclose(f); nkeynes@1034: nkeynes@1034: if( !have_meta || next_part != vol->part_count ) { nkeynes@1034: vmu_volume_destroy( vol ); nkeynes@1034: return NULL; nkeynes@1034: } nkeynes@1034: nkeynes@1034: return vol; nkeynes@1034: } nkeynes@1034: nkeynes@1034: /*************************** Accessing data ********************************/ nkeynes@1034: const char *vmu_volume_get_display_name( vmu_volume_t vol ) nkeynes@1034: { nkeynes@1034: return vol->display_name; nkeynes@1034: } nkeynes@1034: nkeynes@1034: void vmu_volume_set_display_name( vmu_volume_t vol, const gchar *name ) nkeynes@1034: { nkeynes@1034: if( vol->display_name != NULL ) { nkeynes@1034: g_free( (char *)vol->display_name ); nkeynes@1034: } nkeynes@1034: if( name == NULL ) { nkeynes@1034: vol->display_name = NULL; nkeynes@1034: } else { nkeynes@1034: vol->display_name = g_strdup(name); nkeynes@1034: } nkeynes@1034: } nkeynes@1034: nkeynes@1034: gboolean vmu_volume_is_dirty( vmu_volume_t vol ) nkeynes@1034: { nkeynes@1034: return vol->dirty; nkeynes@1034: } nkeynes@1034: nkeynes@1034: gboolean vmu_volume_read_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *out ) nkeynes@1034: { nkeynes@1034: if( pt >= vol->part_count || block >= vol->part[pt].block_count ) { nkeynes@1034: return FALSE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: memcpy( out, VMU_BLOCK(vol,pt,block), VMU_BLOCK_SIZE ); nkeynes@1034: return TRUE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: gboolean vmu_volume_write_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *in ) nkeynes@1034: { nkeynes@1034: if( pt >= vol->part_count || block >= vol->part[pt].block_count ) { nkeynes@1034: return FALSE; nkeynes@1034: } nkeynes@1034: memcpy( VMU_BLOCK(vol,pt,block), in, VMU_BLOCK_SIZE ); nkeynes@1034: vol->dirty = TRUE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: gboolean vmu_volume_write_phase( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned int phase, unsigned char *in ) nkeynes@1034: { nkeynes@1034: if( pt >= vol->part_count || block >= vol->part[pt].block_count || phase >= 4 ) { nkeynes@1034: return FALSE; nkeynes@1034: } nkeynes@1034: memcpy( VMU_BLOCK(vol,pt,block) + (phase*128), in, VMU_BLOCK_SIZE/4 ); nkeynes@1034: vol->dirty = TRUE; nkeynes@1034: } nkeynes@1034: nkeynes@1034: const struct vmu_volume_metadata *vmu_volume_get_metadata( vmu_volume_t vol, vmu_partnum_t partition ) nkeynes@1034: { nkeynes@1034: if( partition >= vol->part_count ) { nkeynes@1034: return NULL; nkeynes@1034: } else { nkeynes@1034: return &vol->part[partition].metadata; nkeynes@1034: } nkeynes@1034: }