Search
lxdream.org :: lxdream/src/vmu/vmuvol.c :: diff
lxdream 0.9.1
released Jun 29
Download Now
filename src/vmu/vmuvol.c
changeset 1034:7044e01148f0
next1035:e3093fd7d1da
author nkeynes
date Wed Jun 24 02:41:12 2009 +0000 (13 years ago)
permissions -rw-r--r--
last change Add initial VMU support
file annotate diff log raw
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/vmu/vmuvol.c Wed Jun 24 02:41:12 2009 +0000
1.3 @@ -0,0 +1,375 @@
1.4 +/**
1.5 + * $Id: vmuvol.h 869 2008-09-08 07:56:33Z nkeynes $
1.6 + *
1.7 + * VMU volume (ie block device) support
1.8 + *
1.9 + * Copyright (c) 2009 Nathan Keynes.
1.10 + *
1.11 + * This program is free software; you can redistribute it and/or modify
1.12 + * it under the terms of the GNU General Public License as published by
1.13 + * the Free Software Foundation; either version 2 of the License, or
1.14 + * (at your option) any later version.
1.15 + *
1.16 + * This program is distributed in the hope that it will be useful,
1.17 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.18 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.19 + * GNU General Public License for more details.
1.20 + */
1.21 +
1.22 +#include <glib/gmem.h>
1.23 +#include <glib/gstrfuncs.h>
1.24 +#include <string.h>
1.25 +#include <stdio.h>
1.26 +#include <fcntl.h>
1.27 +#include <errno.h>
1.28 +
1.29 +#include "vmu/vmuvol.h"
1.30 +#include "dream.h"
1.31 +
1.32 +#define VMU_MAX_PARTITIONS 256
1.33 +#define VMU_MAX_BLOCKS 65536 /* Actually slightly less than this, but it'll do */
1.34 +
1.35 +typedef struct vmu_partition {
1.36 + struct vmu_volume_metadata metadata;
1.37 + uint32_t block_count;
1.38 + char *blocks;
1.39 +} *vmu_partition_t;
1.40 +
1.41 +struct vmu_volume {
1.42 + const gchar *display_name;
1.43 + vmu_partnum_t part_count;
1.44 + gboolean dirty;
1.45 + struct vmu_partition part[0];
1.46 +};
1.47 +
1.48 +/* On-VMU structures, courtesy of Marcus Comstedt */
1.49 +struct vmu_superblock {
1.50 + char magic[16];
1.51 + uint8_t colour_flag;
1.52 + uint8_t bgra[4];
1.53 + uint8_t pad1[27];
1.54 + char timestamp[8];
1.55 + char pad2[8];
1.56 + char unknown[6];
1.57 + uint16_t fat_block;
1.58 + uint16_t fat_size;
1.59 + uint16_t dir_block;
1.60 + uint16_t dir_size;
1.61 + uint16_t icon_shape;
1.62 + uint16_t user_size;
1.63 + /* remainder unknown */
1.64 +};
1.65 +
1.66 +struct vmu_direntry {
1.67 + uint8_t filetype;
1.68 + uint8_t copy_flag;
1.69 + uint16_t blkno;
1.70 + char filename[12];
1.71 + char timestamp[8];
1.72 + uint16_t blksize; /* Size in blocks*/
1.73 + uint16_t hdroff; /* Header offset in blocks */
1.74 + char pad[4];
1.75 +};
1.76 +
1.77 +#define MD(vmu,ptno) ((vmu)->part[ptno].metadata)
1.78 +
1.79 +#define VMU_BLOCK(vmu,ptno,blkno) (&(vmu)->part[ptno].blocks[(blkno)*VMU_BLOCK_SIZE])
1.80 +
1.81 +#define VMU_FAT_ENTRY(vmu,pt,ent) ((uint16_t *)VMU_BLOCK(vmu, pt, (MD(vmu,pt).fat_block - ((ent)>>8))))[(ent)&0xFF]
1.82 +
1.83 +#define FAT_EMPTY 0xFFFC
1.84 +#define FAT_EOF 0xFFFA
1.85 +
1.86 +static const struct vmu_volume_metadata default_metadata = { 255, 255, 254, 1, 253, 13, 0, 200, 31, 0, 128 };
1.87 +
1.88 +vmu_volume_t vmu_volume_new_default( const gchar *display_name )
1.89 +{
1.90 + vmu_volume_t vol = g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition) );
1.91 + vol->part_count = 1;
1.92 + vol->dirty = FALSE;
1.93 + memcpy( &vol->part[0].metadata, &default_metadata, sizeof(struct vmu_volume_metadata) );
1.94 + vol->part[0].block_count = VMU_DEFAULT_VOL_BLOCKS;
1.95 + vol->part[0].blocks = g_malloc0( VMU_DEFAULT_VOL_BLOCKS * VMU_BLOCK_SIZE );
1.96 + vol->display_name = display_name == NULL ? NULL : g_strdup(display_name);
1.97 + vmu_volume_format( vol, 0, TRUE );
1.98 + return vol;
1.99 +}
1.100 +
1.101 +void vmu_volume_destroy( vmu_volume_t vol )
1.102 +{
1.103 + int i;
1.104 + if( vol == NULL )
1.105 + return;
1.106 +
1.107 + for( i=0; i<vol->part_count; i++ ) {
1.108 + g_free( vol->part[i].blocks );
1.109 + vol->part[i].blocks = NULL;
1.110 + }
1.111 + if( vol->display_name ) {
1.112 + g_free( (char *)vol->display_name );
1.113 + vol->display_name = NULL;
1.114 + }
1.115 + g_free(vol);
1.116 +}
1.117 +
1.118 +void vmu_volume_format( vmu_volume_t vol, vmu_partnum_t pt, gboolean quick )
1.119 +{
1.120 + if( pt >= vol->part_count ) {
1.121 + return;
1.122 + }
1.123 +
1.124 + if( !quick ) {
1.125 + /* Wipe it completely first */
1.126 + memset( vol->part[pt].blocks, 0, (vol->part[pt].block_count) * VMU_BLOCK_SIZE );
1.127 + }
1.128 +
1.129 + struct vmu_volume_metadata *meta = &vol->part[pt].metadata;
1.130 + unsigned int fatblkno = meta->fat_block;
1.131 + unsigned int dirblkno = meta->dir_block;
1.132 +
1.133 + /* Write superblock */
1.134 + struct vmu_superblock *super = (struct vmu_superblock *)VMU_BLOCK(vol,pt, meta->super_block);
1.135 + memset( super->magic, 0x55, 16 );
1.136 + memset( &super->colour_flag, 0, 240 ); /* Blank the rest for now */
1.137 + super->fat_block = meta->fat_block;
1.138 + super->fat_size = meta->fat_size;
1.139 + super->dir_block = meta->dir_block;
1.140 + super->user_size = meta->user_size;
1.141 +
1.142 + /* Write file allocation tables */
1.143 + int i,j;
1.144 + for( j=0; j<meta->fat_size; j++ ) {
1.145 + uint16_t *fat = (uint16_t *)VMU_BLOCK(vol,pt,fatblkno-j);
1.146 + for( i=0; i<256; i++ ) {
1.147 + fat[i] = FAT_EMPTY;
1.148 + }
1.149 + }
1.150 +
1.151 + /* Fill in the system allocations in the FAT */
1.152 + for( i=0; i<meta->fat_size-1; i++ ) {
1.153 + VMU_FAT_ENTRY(vol,pt,fatblkno-i) = fatblkno-i-1;
1.154 + }
1.155 + VMU_FAT_ENTRY(vol,pt,fatblkno - i) = FAT_EOF;
1.156 + for( i=0; i<meta->dir_size-1; i++ ) {
1.157 + VMU_FAT_ENTRY(vol,pt,dirblkno-i) = dirblkno-i-1;
1.158 + }
1.159 + VMU_FAT_ENTRY(vol,pt,dirblkno-i) = FAT_EOF;
1.160 +
1.161 + /* If quick-format, blank the directory. Otherwise it's already been done */
1.162 + if( quick ) {
1.163 + memset( VMU_BLOCK(vol,pt,dirblkno-meta->dir_size+1),
1.164 + 0, meta->dir_size * VMU_BLOCK_SIZE );
1.165 + }
1.166 +}
1.167 +
1.168 +/*************************** File load/save ********************************/
1.169 +
1.170 +/**
1.171 + * Current file has 1 META chunk for all volume metadata, followed by a
1.172 + * DATA chunk for each partition's block data. The META chunk is required to
1.173 + * occur before any DATA blocks.
1.174 + * Unknown chunks are skipped to allow for forwards compatibility if/when
1.175 + * we add the VMU runtime side of things
1.176 + */
1.177 +
1.178 +struct vmu_file_header {
1.179 + char magic[16];
1.180 + uint32_t version;
1.181 + uint32_t head_len;
1.182 + uint32_t part_count;
1.183 + uint32_t display_name_len;
1.184 + char display_name[0];
1.185 +};
1.186 +
1.187 +struct vmu_chunk_header {
1.188 + char name[4];
1.189 + uint32_t length;
1.190 +};
1.191 +
1.192 +
1.193 +
1.194 +gboolean vmu_volume_save( const gchar *filename, vmu_volume_t vol, gboolean create_only )
1.195 +{
1.196 + struct vmu_file_header head;
1.197 + struct vmu_chunk_header chunk;
1.198 + int i;
1.199 +
1.200 + FILE *f = fopen( filename, (create_only ? "wx" : "w") ); /* Portable? */
1.201 + if( f == NULL ) {
1.202 + return FALSE;
1.203 + }
1.204 +
1.205 + /* File header */
1.206 + memcpy( head.magic, VMU_FILE_MAGIC, 16 );
1.207 + head.version = VMU_FILE_VERSION;
1.208 + head.part_count = vol->part_count;
1.209 + head.display_name_len = vol->display_name == NULL ? 0 : (strlen(vol->display_name)+1);
1.210 + head.head_len = sizeof(head) + head.display_name_len;
1.211 + fwrite( &head, sizeof(head), 1, f );
1.212 + if( vol->display_name != NULL ) {
1.213 + fwrite( vol->display_name, head.display_name_len, 1, f );
1.214 + }
1.215 +
1.216 + /* METAdata chunk */
1.217 + memcpy( chunk.name, "META", 4 );
1.218 + chunk.length = sizeof(struct vmu_volume_metadata) * vol->part_count;
1.219 + fwrite( &chunk, sizeof(chunk), 1, f );
1.220 + for( i=0; i < vol->part_count; i++ ) {
1.221 + fwrite( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
1.222 + }
1.223 +
1.224 + /* partition DATA chunks */
1.225 + for( i=0; i< vol->part_count; i++ ) {
1.226 + memcpy( chunk.name, "DATA", 4 );
1.227 + chunk.length = 0;
1.228 + fwrite( &chunk, sizeof(chunk), 1, f );
1.229 + long posn = ftell(f);
1.230 + fwrite( &vol->part[i].block_count, sizeof(vol->part[i].block_count), 1, f );
1.231 + fwrite_gzip( vol->part[i].blocks, vol->part[i].block_count, VMU_BLOCK_SIZE, f );
1.232 + long end = ftell(f);
1.233 + fseek( f, posn - sizeof(chunk.length), SEEK_SET );
1.234 + chunk.length = end-posn;
1.235 + fwrite( &chunk.length, sizeof(chunk.length), 1, f );
1.236 + fseek( f, end, SEEK_SET );
1.237 + }
1.238 + fclose(f);
1.239 + vol->dirty = FALSE;
1.240 + return TRUE;
1.241 +}
1.242 +
1.243 +vmu_volume_t vmu_volume_load( const gchar *filename )
1.244 +{
1.245 + struct vmu_file_header head;
1.246 + struct vmu_chunk_header chunk;
1.247 + vmu_volume_t vol;
1.248 + int i;
1.249 +
1.250 + FILE *f = fopen( filename, "ro" );
1.251 + if( f == NULL ) {
1.252 + ERROR( "Unable to open VMU file '%s': %s", filename, strerror(errno) );
1.253 + return FALSE;
1.254 + }
1.255 +
1.256 + if( fread( &head, sizeof(head), 1, f ) != 1 ||
1.257 + memcmp(head.magic, VMU_FILE_MAGIC, 16) != 0 ||
1.258 + head.part_count > VMU_MAX_PARTITIONS ||
1.259 + head.head_len < (sizeof(head) + head.display_name_len) ) {
1.260 + fclose(f);
1.261 + ERROR( "Unable to load VMU '%s': bad file header", filename );
1.262 + return NULL;
1.263 + }
1.264 +
1.265 + vol = (vmu_volume_t)g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition)*head.part_count );
1.266 + vol->part_count = head.part_count;
1.267 + vol->dirty = FALSE;
1.268 + if( head.display_name_len != 0 ) {
1.269 + vol->display_name = g_malloc( head.display_name_len );
1.270 + fread( (char *)vol->display_name, head.display_name_len, 1, f );
1.271 + }
1.272 + fseek( f, head.head_len, SEEK_SET );
1.273 +
1.274 + gboolean have_meta = FALSE;
1.275 + int next_part = 0;
1.276 + while( !feof(f) && fread( &chunk, sizeof(chunk), 1, f ) == 1 ) {
1.277 + if( memcmp( &chunk.name, "META", 4 ) == 0 ) {
1.278 + if( have_meta || chunk.length != head.part_count * sizeof(struct vmu_volume_metadata) ) {
1.279 + vmu_volume_destroy(vol);
1.280 + fclose(f);
1.281 + ERROR( "Unable to load VMU '%s': bad metadata size (expected %d but was %d)", filename,
1.282 + head.part_count * sizeof(struct vmu_volume_metadata), chunk.length );
1.283 + return NULL;
1.284 + }
1.285 + for( i=0; i<head.part_count; i++ ) {
1.286 + fread( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
1.287 + }
1.288 + have_meta = TRUE;
1.289 + } else if( memcmp( &chunk.name, "DATA", 4 ) == 0 ) {
1.290 + uint32_t block_count;
1.291 + fread( &block_count, sizeof(block_count), 1, f );
1.292 + if( next_part >= vol->part_count || block_count >= VMU_MAX_BLOCKS ) {
1.293 + // Too many partitions / blocks
1.294 + vmu_volume_destroy(vol);
1.295 + fclose(f);
1.296 + ERROR( "Unable to load VMU '%s': too large (%d/%d)", filename, next_part, block_count );
1.297 + return NULL;
1.298 + }
1.299 + vol->part[next_part].block_count = block_count;
1.300 + vol->part[next_part].blocks = g_malloc(block_count*VMU_BLOCK_SIZE);
1.301 + fread_gzip(vol->part[next_part].blocks, VMU_BLOCK_SIZE, block_count, f );
1.302 + next_part++;
1.303 + } else {
1.304 + // else skip unknown block
1.305 + fseek( f, SEEK_CUR, chunk.length );
1.306 + WARN( "Unexpected VMU data chunk: '%4.4s'", chunk.name );
1.307 + }
1.308 + }
1.309 +
1.310 + fclose(f);
1.311 +
1.312 + if( !have_meta || next_part != vol->part_count ) {
1.313 + vmu_volume_destroy( vol );
1.314 + return NULL;
1.315 + }
1.316 +
1.317 + return vol;
1.318 +}
1.319 +
1.320 +/*************************** Accessing data ********************************/
1.321 +const char *vmu_volume_get_display_name( vmu_volume_t vol )
1.322 +{
1.323 + return vol->display_name;
1.324 +}
1.325 +
1.326 +void vmu_volume_set_display_name( vmu_volume_t vol, const gchar *name )
1.327 +{
1.328 + if( vol->display_name != NULL ) {
1.329 + g_free( (char *)vol->display_name );
1.330 + }
1.331 + if( name == NULL ) {
1.332 + vol->display_name = NULL;
1.333 + } else {
1.334 + vol->display_name = g_strdup(name);
1.335 + }
1.336 +}
1.337 +
1.338 +gboolean vmu_volume_is_dirty( vmu_volume_t vol )
1.339 +{
1.340 + return vol->dirty;
1.341 +}
1.342 +
1.343 +gboolean vmu_volume_read_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *out )
1.344 +{
1.345 + if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
1.346 + return FALSE;
1.347 + }
1.348 +
1.349 + memcpy( out, VMU_BLOCK(vol,pt,block), VMU_BLOCK_SIZE );
1.350 + return TRUE;
1.351 +}
1.352 +
1.353 +gboolean vmu_volume_write_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *in )
1.354 +{
1.355 + if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
1.356 + return FALSE;
1.357 + }
1.358 + memcpy( VMU_BLOCK(vol,pt,block), in, VMU_BLOCK_SIZE );
1.359 + vol->dirty = TRUE;
1.360 +}
1.361 +
1.362 +gboolean vmu_volume_write_phase( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned int phase, unsigned char *in )
1.363 +{
1.364 + if( pt >= vol->part_count || block >= vol->part[pt].block_count || phase >= 4 ) {
1.365 + return FALSE;
1.366 + }
1.367 + memcpy( VMU_BLOCK(vol,pt,block) + (phase*128), in, VMU_BLOCK_SIZE/4 );
1.368 + vol->dirty = TRUE;
1.369 +}
1.370 +
1.371 +const struct vmu_volume_metadata *vmu_volume_get_metadata( vmu_volume_t vol, vmu_partnum_t partition )
1.372 +{
1.373 + if( partition >= vol->part_count ) {
1.374 + return NULL;
1.375 + } else {
1.376 + return &vol->part[partition].metadata;
1.377 + }
1.378 +}
.