filename | src/vmu/vmuvol.c |
changeset | 1071:182cfe43c09e |
prev | 1056:d0896e6530d6 |
next | 1296:30ecee61f811 |
author | nkeynes |
date | Mon Feb 13 12:26:01 2012 +1000 (12 years ago) |
permissions | -rw-r--r-- |
last change | Add GTK_LIBS to ldadd for testlxpaths, as GLIB_LIBS isn't defined for GTK builds... |
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 }
.