4 * VMU volume (ie block device) support
6 * Copyright (c) 2009 Nathan Keynes.
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.
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.
19 #include <glib/gmem.h>
20 #include <glib/gstrfuncs.h>
26 #include "vmu/vmuvol.h"
29 #define VMU_MAX_PARTITIONS 256
30 #define VMU_MAX_BLOCKS 65536 /* Actually slightly less than this, but it'll do */
32 typedef struct vmu_partition {
33 struct vmu_volume_metadata metadata;
39 const gchar *display_name;
40 vmu_partnum_t part_count;
42 struct vmu_partition part[0];
45 /* On-VMU structures, courtesy of Marcus Comstedt */
46 struct vmu_superblock {
60 /* remainder unknown */
69 uint16_t blksize; /* Size in blocks*/
70 uint16_t hdroff; /* Header offset in blocks */
74 #define MD(vmu,ptno) ((vmu)->part[ptno].metadata)
76 #define VMU_BLOCK(vmu,ptno,blkno) (&(vmu)->part[ptno].blocks[(blkno)*VMU_BLOCK_SIZE])
78 #define VMU_FAT_ENTRY(vmu,pt,ent) ((uint16_t *)VMU_BLOCK(vmu, pt, (MD(vmu,pt).fat_block - ((ent)>>8))))[(ent)&0xFF]
80 #define FAT_EMPTY 0xFFFC
81 #define FAT_EOF 0xFFFA
83 static const struct vmu_volume_metadata default_metadata = { 255, 255, 254, 1, 253, 13, 0, 200, 31, 0, 128 };
85 vmu_volume_t vmu_volume_new_default( const gchar *display_name )
87 vmu_volume_t vol = g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition) );
90 memcpy( &vol->part[0].metadata, &default_metadata, sizeof(struct vmu_volume_metadata) );
91 vol->part[0].block_count = VMU_DEFAULT_VOL_BLOCKS;
92 vol->part[0].blocks = g_malloc0( VMU_DEFAULT_VOL_BLOCKS * VMU_BLOCK_SIZE );
93 vol->display_name = display_name == NULL ? NULL : g_strdup(display_name);
94 vmu_volume_format( vol, 0, TRUE );
98 void vmu_volume_destroy( vmu_volume_t vol )
104 for( i=0; i<vol->part_count; i++ ) {
105 g_free( vol->part[i].blocks );
106 vol->part[i].blocks = NULL;
108 if( vol->display_name ) {
109 g_free( (char *)vol->display_name );
110 vol->display_name = NULL;
115 void vmu_volume_format( vmu_volume_t vol, vmu_partnum_t pt, gboolean quick )
117 if( pt >= vol->part_count ) {
122 /* Wipe it completely first */
123 memset( vol->part[pt].blocks, 0, (vol->part[pt].block_count) * VMU_BLOCK_SIZE );
126 struct vmu_volume_metadata *meta = &vol->part[pt].metadata;
127 unsigned int fatblkno = meta->fat_block;
128 unsigned int dirblkno = meta->dir_block;
130 /* Write superblock */
131 struct vmu_superblock *super = (struct vmu_superblock *)VMU_BLOCK(vol,pt, meta->super_block);
132 memset( super->magic, 0x55, 16 );
133 memset( &super->colour_flag, 0, 240 ); /* Blank the rest for now */
134 super->fat_block = meta->fat_block;
135 super->fat_size = meta->fat_size;
136 super->dir_block = meta->dir_block;
137 super->user_size = meta->user_size;
139 /* Write file allocation tables */
141 for( j=0; j<meta->fat_size; j++ ) {
142 uint16_t *fat = (uint16_t *)VMU_BLOCK(vol,pt,fatblkno-j);
143 for( i=0; i<256; i++ ) {
148 /* Fill in the system allocations in the FAT */
149 for( i=0; i<meta->fat_size-1; i++ ) {
150 VMU_FAT_ENTRY(vol,pt,fatblkno-i) = fatblkno-i-1;
152 VMU_FAT_ENTRY(vol,pt,fatblkno - i) = FAT_EOF;
153 for( i=0; i<meta->dir_size-1; i++ ) {
154 VMU_FAT_ENTRY(vol,pt,dirblkno-i) = dirblkno-i-1;
156 VMU_FAT_ENTRY(vol,pt,dirblkno-i) = FAT_EOF;
158 /* If quick-format, blank the directory. Otherwise it's already been done */
160 memset( VMU_BLOCK(vol,pt,dirblkno-meta->dir_size+1),
161 0, meta->dir_size * VMU_BLOCK_SIZE );
165 /*************************** File load/save ********************************/
168 * Current file has 1 META chunk for all volume metadata, followed by a
169 * DATA chunk for each partition's block data. The META chunk is required to
170 * occur before any DATA blocks.
171 * Unknown chunks are skipped to allow for forwards compatibility if/when
172 * we add the VMU runtime side of things
175 struct vmu_file_header {
180 uint32_t display_name_len;
181 char display_name[0];
184 struct vmu_chunk_header {
191 gboolean vmu_volume_save( const gchar *filename, vmu_volume_t vol, gboolean create_only )
193 struct vmu_file_header head;
194 struct vmu_chunk_header chunk;
197 FILE *f = fopen( filename, (create_only ? "wx" : "w") ); /* Portable? */
203 memcpy( head.magic, VMU_FILE_MAGIC, 16 );
204 head.version = VMU_FILE_VERSION;
205 head.part_count = vol->part_count;
206 head.display_name_len = vol->display_name == NULL ? 0 : (strlen(vol->display_name)+1);
207 head.head_len = sizeof(head) + head.display_name_len;
208 fwrite( &head, sizeof(head), 1, f );
209 if( vol->display_name != NULL ) {
210 fwrite( vol->display_name, head.display_name_len, 1, f );
214 memcpy( chunk.name, "META", 4 );
215 chunk.length = sizeof(struct vmu_volume_metadata) * vol->part_count;
216 fwrite( &chunk, sizeof(chunk), 1, f );
217 for( i=0; i < vol->part_count; i++ ) {
218 fwrite( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
221 /* partition DATA chunks */
222 for( i=0; i< vol->part_count; i++ ) {
223 memcpy( chunk.name, "DATA", 4 );
225 fwrite( &chunk, sizeof(chunk), 1, f );
226 long posn = ftell(f);
227 fwrite( &vol->part[i].block_count, sizeof(vol->part[i].block_count), 1, f );
228 fwrite_gzip( vol->part[i].blocks, vol->part[i].block_count, VMU_BLOCK_SIZE, f );
230 fseek( f, posn - sizeof(chunk.length), SEEK_SET );
231 chunk.length = end-posn;
232 fwrite( &chunk.length, sizeof(chunk.length), 1, f );
233 fseek( f, end, SEEK_SET );
240 vmu_volume_t vmu_volume_load( const gchar *filename )
242 struct vmu_file_header head;
243 struct vmu_chunk_header chunk;
247 FILE *f = fopen( filename, "ro" );
249 ERROR( "Unable to open VMU file '%s': %s", filename, strerror(errno) );
253 if( fread( &head, sizeof(head), 1, f ) != 1 ||
254 memcmp(head.magic, VMU_FILE_MAGIC, 16) != 0 ||
255 head.part_count > VMU_MAX_PARTITIONS ||
256 head.head_len < (sizeof(head) + head.display_name_len) ) {
258 ERROR( "Unable to load VMU '%s': bad file header", filename );
262 vol = (vmu_volume_t)g_malloc0( sizeof(struct vmu_volume) + sizeof(struct vmu_partition)*head.part_count );
263 vol->part_count = head.part_count;
265 if( head.display_name_len != 0 ) {
266 vol->display_name = g_malloc( head.display_name_len );
267 fread( (char *)vol->display_name, head.display_name_len, 1, f );
269 fseek( f, head.head_len, SEEK_SET );
271 gboolean have_meta = FALSE;
273 while( !feof(f) && fread( &chunk, sizeof(chunk), 1, f ) == 1 ) {
274 if( memcmp( &chunk.name, "META", 4 ) == 0 ) {
275 if( have_meta || chunk.length != head.part_count * sizeof(struct vmu_volume_metadata) ) {
276 vmu_volume_destroy(vol);
278 ERROR( "Unable to load VMU '%s': bad metadata size (expected %d but was %d)", filename,
279 head.part_count * sizeof(struct vmu_volume_metadata), chunk.length );
282 for( i=0; i<head.part_count; i++ ) {
283 fread( &vol->part[i].metadata, sizeof(struct vmu_volume_metadata), 1, f );
286 } else if( memcmp( &chunk.name, "DATA", 4 ) == 0 ) {
287 uint32_t block_count;
288 fread( &block_count, sizeof(block_count), 1, f );
289 if( next_part >= vol->part_count || block_count >= VMU_MAX_BLOCKS ) {
290 // Too many partitions / blocks
291 vmu_volume_destroy(vol);
293 ERROR( "Unable to load VMU '%s': too large (%d/%d)", filename, next_part, block_count );
296 vol->part[next_part].block_count = block_count;
297 vol->part[next_part].blocks = g_malloc(block_count*VMU_BLOCK_SIZE);
298 fread_gzip(vol->part[next_part].blocks, VMU_BLOCK_SIZE, block_count, f );
301 // else skip unknown block
302 fseek( f, SEEK_CUR, chunk.length );
303 WARN( "Unexpected VMU data chunk: '%4.4s'", chunk.name );
309 if( !have_meta || next_part != vol->part_count ) {
310 vmu_volume_destroy( vol );
317 /*************************** Accessing data ********************************/
318 const char *vmu_volume_get_display_name( vmu_volume_t vol )
320 return vol->display_name;
323 void vmu_volume_set_display_name( vmu_volume_t vol, const gchar *name )
325 if( vol->display_name != NULL ) {
326 g_free( (char *)vol->display_name );
329 vol->display_name = NULL;
331 vol->display_name = g_strdup(name);
335 gboolean vmu_volume_is_dirty( vmu_volume_t vol )
340 gboolean vmu_volume_read_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *out )
342 if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
346 memcpy( out, VMU_BLOCK(vol,pt,block), VMU_BLOCK_SIZE );
350 gboolean vmu_volume_write_block( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned char *in )
352 if( pt >= vol->part_count || block >= vol->part[pt].block_count ) {
355 memcpy( VMU_BLOCK(vol,pt,block), in, VMU_BLOCK_SIZE );
359 gboolean vmu_volume_write_phase( vmu_volume_t vol, vmu_partnum_t pt, unsigned int block, unsigned int phase, unsigned char *in )
361 if( pt >= vol->part_count || block >= vol->part[pt].block_count || phase >= 4 ) {
364 memcpy( VMU_BLOCK(vol,pt,block) + (phase*128), in, VMU_BLOCK_SIZE/4 );
368 const struct vmu_volume_metadata *vmu_volume_get_metadata( vmu_volume_t vol, vmu_partnum_t partition )
370 if( partition >= vol->part_count ) {
373 return &vol->part[partition].metadata;
.