1 // license:BSD-3-Clause
2 // copyright-holders:Tim Schuerewegen
3 /*
4
5 Cybiko Xtreme File System
6
7 (c) 2010 Tim Schuerewegen
8
9 */
10
11 #include "imgtool.h"
12
13 #include <zlib.h>
14
15 struct cybiko_file_system
16 {
17 imgtool::stream *stream;
18 uint32_t page_count, page_size, block_count_boot, block_count_file;
19 uint16_t write_count;
20 };
21
22 struct cybiko_iter
23 {
24 uint16_t block;
25 };
26
27 struct cfs_file
28 {
29 char name[64]; // name of the file
30 uint32_t date; // date/time of the file (seconds since 1900/01/01)
31 uint32_t size; // size of the file
32 uint32_t blocks; // number of blocks occupied by the file
33 };
34
35 enum
36 {
37 BLOCK_TYPE_INVALID,
38 BLOCK_TYPE_BOOT,
39 BLOCK_TYPE_FILE
40 };
41
42 #define MAX_PAGE_SIZE (264 * 2)
43
44 #define INVALID_FILE_ID 0xFFFF
45
46 #define BLOCK_USED(x) (x[0] & 0x80)
47 #define BLOCK_FILE_ID(x) buffer_read_16_be( x + 2)
48 #define BLOCK_PART_ID(x) buffer_read_16_be( x + 4)
49 #define BLOCK_FILENAME(x) (char*)(x + 7)
50
51 #define FILE_HEADER_SIZE 0x48
52
get_cfs(imgtool::image & image)53 static cybiko_file_system *get_cfs(imgtool::image &image)
54 {
55 return (cybiko_file_system*)image.extra_bytes();
56 }
57
58 extern imgtool::datetime cybiko_time_crack(uint32_t cfs_time);
59 extern uint32_t cybiko_time_setup(const imgtool::datetime &t);
60
buffer_read_32_be(uint8_t * buffer)61 static uint32_t buffer_read_32_be( uint8_t *buffer)
62 {
63 return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | (buffer[3] << 0);
64 }
65
buffer_read_16_be(uint8_t * buffer)66 static uint16_t buffer_read_16_be( uint8_t *buffer)
67 {
68 return (buffer[0] << 8) | (buffer[1] << 0);
69 }
70
buffer_write_32_be(uint8_t * buffer,uint32_t data)71 static void buffer_write_32_be( uint8_t *buffer, uint32_t data)
72 {
73 buffer[0] = (data >> 24) & 0xFF;
74 buffer[1] = (data >> 16) & 0xFF;
75 buffer[2] = (data >> 8) & 0xFF;
76 buffer[3] = (data >> 0) & 0xFF;
77 }
78
buffer_write_16_be(uint8_t * buffer,uint16_t data)79 static void buffer_write_16_be( uint8_t *buffer, uint16_t data)
80 {
81 buffer[0] = (data >> 8) & 0xFF;
82 buffer[1] = (data >> 0) & 0xFF;
83 }
84
85 // page = crc (2) + data (x)
86
page_buffer_calc_checksum(uint8_t * data,uint32_t size)87 static uint16_t page_buffer_calc_checksum( uint8_t *data, uint32_t size)
88 {
89 int i;
90 uint32_t val = 0;
91 for (i = 0; i < size; i++)
92 {
93 val = (val ^ data[i] ^ i) << 1;
94 val = val | ((val >> 16) & 0x0001);
95 }
96 return val;
97 }
98
page_buffer_verify(uint8_t * buffer,uint32_t size,int block_type)99 static int page_buffer_verify( uint8_t *buffer, uint32_t size, int block_type)
100 {
101 // checksum
102 if (block_type == BLOCK_TYPE_FILE)
103 {
104 uint32_t checksum_page, checksum_calc;
105 checksum_calc = page_buffer_calc_checksum( buffer + 2, size - 2);
106 checksum_page = buffer_read_16_be( buffer + 0);
107 if (checksum_calc != checksum_page) return false;
108 }
109 // ok
110 return true;
111 }
112
cfs_block_to_page(cybiko_file_system * cfs,int block_type,uint32_t block,uint32_t * page)113 static int cfs_block_to_page( cybiko_file_system *cfs, int block_type, uint32_t block, uint32_t *page)
114 {
115 switch (block_type)
116 {
117 case BLOCK_TYPE_BOOT : if (page) *page = block; return true;
118 case BLOCK_TYPE_FILE : if (page) *page = block + cfs->block_count_boot; return true;
119 default : return false;
120 }
121 }
122
cfs_page_to_block(cybiko_file_system * cfs,uint32_t page,int * block_type,uint32_t * block)123 static int cfs_page_to_block( cybiko_file_system *cfs, uint32_t page, int *block_type, uint32_t *block)
124 {
125 uint32_t tmp = page;
126 // boot block
127 if (tmp < cfs->block_count_boot)
128 {
129 if (block_type) *block_type = BLOCK_TYPE_BOOT;
130 if (block) *block = tmp;
131 return true;
132 }
133 tmp -= cfs->block_count_boot;
134 // file block
135 if (tmp < cfs->block_count_file)
136 {
137 if (block_type) *block_type = BLOCK_TYPE_FILE;
138 if (block) *block = tmp;
139 return true;
140 }
141 tmp -= cfs->block_count_file;
142 // error
143 return false;
144 }
145
cfs_page_read(cybiko_file_system * cfs,uint8_t * buffer,uint32_t page)146 static int cfs_page_read( cybiko_file_system *cfs, uint8_t *buffer, uint32_t page)
147 {
148 if (page >= cfs->page_count) return false;
149 cfs->stream->seek(page * cfs->page_size, SEEK_SET);
150 cfs->stream->read(buffer, cfs->page_size);
151 return true;
152 }
153
cfs_page_write(cybiko_file_system * cfs,uint8_t * buffer,uint32_t page)154 static int cfs_page_write( cybiko_file_system *cfs, uint8_t *buffer, uint32_t page)
155 {
156 if (page >= cfs->page_count) return false;
157 cfs->stream->seek(page * cfs->page_size, SEEK_SET);
158 cfs->stream->write(buffer, cfs->page_size);
159 return true;
160 }
161
cfs_block_read(cybiko_file_system * cfs,uint8_t * buffer,int block_type,uint32_t block)162 static int cfs_block_read( cybiko_file_system *cfs, uint8_t *buffer, int block_type, uint32_t block)
163 {
164 uint8_t buffer_page[MAX_PAGE_SIZE];
165 uint32_t page;
166 if (!cfs_block_to_page( cfs, block_type, block, &page)) return false;
167 if (!cfs_page_read( cfs, buffer_page, page)) return false;
168 memcpy( buffer, buffer_page + 2, cfs->page_size - 2);
169 return true;
170 }
171
cfs_block_write(cybiko_file_system * cfs,uint8_t * buffer,int block_type,uint32_t block)172 static int cfs_block_write( cybiko_file_system *cfs, uint8_t *buffer, int block_type, uint32_t block)
173 {
174 uint8_t buffer_page[MAX_PAGE_SIZE];
175 uint32_t page;
176 uint16_t checksum;
177 memcpy( buffer_page + 2, buffer, cfs->page_size - 2);
178 if (block_type == BLOCK_TYPE_BOOT)
179 {
180 checksum = 0xFFFF;
181 }
182 else
183 {
184 checksum = page_buffer_calc_checksum( buffer_page + 2, cfs->page_size - 2);
185 }
186 buffer_write_16_be( buffer_page + 0, checksum);
187 if (!cfs_block_to_page( cfs, block_type, block, &page)) return false;
188 if (!cfs_page_write( cfs, buffer_page, page)) return false;
189 return true;
190 }
191
cfs_file_delete(cybiko_file_system * cfs,uint16_t file_id)192 static int cfs_file_delete( cybiko_file_system *cfs, uint16_t file_id)
193 {
194 uint8_t buffer[MAX_PAGE_SIZE];
195 int i;
196 for (i=0;i<cfs->block_count_file;i++)
197 {
198 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return false;
199 if (BLOCK_USED(buffer) && (BLOCK_FILE_ID(buffer) == file_id))
200 {
201 buffer[0] &= ~0x80;
202 if (!cfs_block_write( cfs, buffer, BLOCK_TYPE_FILE, i)) return false;
203 }
204 }
205 return true;
206 }
207
cfs_file_info(cybiko_file_system * cfs,uint16_t file_id,cfs_file * file)208 static int cfs_file_info( cybiko_file_system *cfs, uint16_t file_id, cfs_file *file)
209 {
210 uint8_t buffer[MAX_PAGE_SIZE];
211 int i;
212 file->blocks = file->size = 0;
213 for (i=0;i<cfs->block_count_file;i++)
214 {
215 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return false;
216 if (BLOCK_USED(buffer) && (BLOCK_FILE_ID(buffer) == file_id))
217 {
218 if (BLOCK_PART_ID(buffer) == 0)
219 {
220 strcpy( file->name, BLOCK_FILENAME(buffer));
221 file->date = buffer_read_32_be( buffer + 6 + FILE_HEADER_SIZE - 4);
222 }
223 file->size += buffer[1];
224 file->blocks++;
225 }
226 }
227 return (file->blocks > 0) ? true : false;
228 }
229
cfs_file_find(cybiko_file_system * cfs,const char * filename,uint16_t * file_id)230 static int cfs_file_find( cybiko_file_system *cfs, const char *filename, uint16_t *file_id)
231 {
232 uint8_t buffer[MAX_PAGE_SIZE];
233 int i;
234 for (i=0;i<cfs->block_count_file;i++)
235 {
236 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return false;
237 if (BLOCK_USED(buffer) && (BLOCK_PART_ID(buffer) == 0) && (strcmp( filename, BLOCK_FILENAME(buffer)) == 0))
238 {
239 *file_id = i;
240 return true;
241 }
242 }
243 return false;
244 }
245
cfs_verify(cybiko_file_system & cfs)246 static bool cfs_verify(cybiko_file_system &cfs)
247 {
248 uint8_t buffer[MAX_PAGE_SIZE];
249 int i, block_type;
250 for (i = 0; i < cfs.page_count; i++)
251 {
252 if (!cfs_page_read(&cfs, buffer, i)) return false;
253 if (!cfs_page_to_block(&cfs, i, &block_type, NULL)) return false;
254 if (!page_buffer_verify(buffer, cfs.page_size, block_type)) return false;
255 }
256 return true;
257 }
258
cfs_init(cybiko_file_system & cfs,imgtool::stream::ptr && stream)259 static bool cfs_init(cybiko_file_system &cfs, imgtool::stream::ptr &&stream)
260 {
261 cfs.stream = stream.release();
262 cfs.page_count = 2005;
263 cfs.page_size = 258;
264 cfs.block_count_boot = 5;
265 cfs.block_count_file = cfs.page_count - cfs.block_count_boot;
266 cfs.write_count = 0;
267 return true;
268 }
269
cfs_format(cybiko_file_system * cfs)270 static int cfs_format(cybiko_file_system *cfs)
271 {
272 uint8_t buffer[MAX_PAGE_SIZE];
273 int i;
274 // boot blocks
275 memset( buffer, 0xFF, sizeof( buffer));
276 for (i=0;i<cfs->block_count_boot;i++)
277 {
278 if (!cfs_block_write( cfs, buffer, BLOCK_TYPE_BOOT, i)) return false;
279 }
280 // file blocks
281 memset( buffer, 0xFF, sizeof( buffer));
282 buffer[0] &= ~0x80;
283 for (i=0;i<cfs->block_count_file;i++)
284 {
285 if (!cfs_block_write( cfs, buffer, BLOCK_TYPE_FILE, i)) return false;
286 }
287 // padding
288 buffer[0] = 0xFF;
289 for (i=0;i<0x1B56;i++)
290 {
291 cfs->stream->write(buffer, 1);
292 }
293 // ok
294 return true;
295 }
296
cfs_calc_free_blocks(cybiko_file_system * cfs)297 static uint16_t cfs_calc_free_blocks( cybiko_file_system *cfs)
298 {
299 uint8_t buffer[MAX_PAGE_SIZE];
300 int i;
301 uint16_t blocks = 0;
302 for (i=0;i<cfs->block_count_file;i++)
303 {
304 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return 0;
305 if (!BLOCK_USED(buffer)) blocks++;
306 }
307 return blocks;
308 }
309
cfs_calc_free_space(cybiko_file_system * cfs,uint16_t blocks)310 static uint32_t cfs_calc_free_space( cybiko_file_system *cfs, uint16_t blocks)
311 {
312 uint32_t free_space;
313 free_space = blocks * ((cfs->page_size - 2) - 6);
314 if (free_space > 0) free_space -= FILE_HEADER_SIZE;
315 return free_space;
316 }
317
cybiko_image_open(imgtool::image & image,imgtool::stream::ptr && stream)318 static imgtoolerr_t cybiko_image_open(imgtool::image &image, imgtool::stream::ptr &&stream)
319 {
320 cybiko_file_system *cfs = get_cfs(image);
321 // init
322 if (!cfs_init(*cfs, std::move(stream))) return IMGTOOLERR_CORRUPTIMAGE;
323 // verify
324 if (!cfs_verify(*cfs)) return IMGTOOLERR_CORRUPTIMAGE;
325 // ok
326 return IMGTOOLERR_SUCCESS;
327 }
328
cybiko_image_close(imgtool::image & image)329 static void cybiko_image_close(imgtool::image &image)
330 {
331 cybiko_file_system *cfs = get_cfs(image);
332 delete cfs->stream;
333 }
334
cybiko_image_create(imgtool::image & image,imgtool::stream::ptr && stream,util::option_resolution * opts)335 static imgtoolerr_t cybiko_image_create(imgtool::image &image, imgtool::stream::ptr &&stream, util::option_resolution *opts)
336 {
337 cybiko_file_system *cfs = get_cfs(image);
338 // init
339 if (!cfs_init(*cfs, std::move(stream))) return IMGTOOLERR_CORRUPTIMAGE;
340 // format
341 if (!cfs_format(cfs)) return IMGTOOLERR_CORRUPTIMAGE;
342 // ok
343 return IMGTOOLERR_SUCCESS;
344 }
345
cybiko_image_begin_enum(imgtool::directory & enumeration,const char * path)346 static imgtoolerr_t cybiko_image_begin_enum(imgtool::directory &enumeration, const char *path)
347 {
348 cybiko_iter *iter = (cybiko_iter*)enumeration.extra_bytes();
349 iter->block = 0;
350 return IMGTOOLERR_SUCCESS;
351 }
352
cybiko_image_next_enum(imgtool::directory & enumeration,imgtool_dirent & ent)353 static imgtoolerr_t cybiko_image_next_enum(imgtool::directory &enumeration, imgtool_dirent &ent)
354 {
355 imgtool::image &image(enumeration.image());
356 cybiko_file_system *cfs = get_cfs(image);
357 cybiko_iter *iter = (cybiko_iter*)enumeration.extra_bytes();
358 uint8_t buffer[MAX_PAGE_SIZE];
359 uint16_t file_id = INVALID_FILE_ID;
360 cfs_file file;
361 // find next file
362 while (iter->block < cfs->block_count_file)
363 {
364 if (!cfs_block_read(cfs, buffer, BLOCK_TYPE_FILE, iter->block++)) return IMGTOOLERR_READERROR;
365 if (BLOCK_USED(buffer) && (BLOCK_PART_ID(buffer) == 0))
366 {
367 file_id = BLOCK_FILE_ID(buffer);
368 break;
369 }
370 }
371 // get file information
372 if ((file_id != INVALID_FILE_ID) && cfs_file_info(cfs, file_id, &file))
373 {
374 strcpy(ent.filename, file.name);
375 ent.filesize = file.size;
376 ent.lastmodified_time = cybiko_time_crack(file.date);
377 ent.filesize = file.size;
378 }
379 else
380 {
381 ent.eof = 1;
382 }
383 // ok
384 return IMGTOOLERR_SUCCESS;
385 }
386
cybiko_image_free_space(imgtool::partition & partition,uint64_t * size)387 static imgtoolerr_t cybiko_image_free_space(imgtool::partition &partition, uint64_t *size)
388 {
389 imgtool::image &image(partition.image());
390 cybiko_file_system *cfs = get_cfs(image);
391 if (size) *size = cfs_calc_free_space( cfs, cfs_calc_free_blocks( cfs));
392 return IMGTOOLERR_SUCCESS;
393 }
394
cybiko_image_read_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & destf)395 static imgtoolerr_t cybiko_image_read_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &destf)
396 {
397 imgtool::image &image(partition.image());
398 cybiko_file_system *cfs = get_cfs(image);
399 uint8_t buffer[MAX_PAGE_SIZE];
400 uint16_t file_id, part_id = 0, old_part_id;
401 int i;
402 // check filename
403 if (strlen( filename) > 58) return IMGTOOLERR_BADFILENAME;
404 // find file
405 if (!cfs_file_find( cfs, filename, &file_id)) return IMGTOOLERR_FILENOTFOUND;
406 // read file
407 do
408 {
409 old_part_id = part_id;
410 for (i=0;i<cfs->block_count_file;i++)
411 {
412 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return IMGTOOLERR_READERROR;
413 if (BLOCK_USED(buffer) && (BLOCK_FILE_ID(buffer) == file_id) && (BLOCK_PART_ID(buffer) == part_id))
414 {
415 destf.write(buffer + 6 + ((part_id == 0) ? FILE_HEADER_SIZE : 0), buffer[1]);
416 part_id++;
417 }
418 }
419 } while (old_part_id != part_id);
420 // ok
421 return IMGTOOLERR_SUCCESS;
422 }
423
cybiko_image_write_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & sourcef,util::option_resolution * opts)424 static imgtoolerr_t cybiko_image_write_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *opts)
425 {
426 imgtool::image &image(partition.image());
427 cybiko_file_system *cfs = get_cfs(image);
428 uint8_t buffer[MAX_PAGE_SIZE];
429 uint16_t file_id, part_id = 0, free_blocks;
430 uint64_t bytes_left;
431 cfs_file file;
432 int i;
433 // check filename
434 if (strlen( filename) > 58) return IMGTOOLERR_BADFILENAME;
435 // find file
436 if (!cfs_file_find( cfs, filename, &file_id)) file_id = INVALID_FILE_ID;
437 // check free space
438 free_blocks = cfs_calc_free_blocks(cfs);
439 if (file_id != INVALID_FILE_ID)
440 {
441 if (!cfs_file_info(cfs, file_id, &file)) return IMGTOOLERR_UNEXPECTED;
442 free_blocks += file.blocks;
443 }
444 if (cfs_calc_free_space(cfs, free_blocks) < sourcef.size()) return IMGTOOLERR_NOSPACE;
445 // delete file
446 if (file_id != INVALID_FILE_ID)
447 {
448 if (!cfs_file_delete(cfs, file_id)) return IMGTOOLERR_UNEXPECTED;
449 }
450 // create/write destination file
451 bytes_left = sourcef.size();
452 i = 0;
453 while (i < cfs->block_count_file)
454 {
455 if (!cfs_block_read( cfs, buffer, BLOCK_TYPE_FILE, i)) return IMGTOOLERR_READERROR;
456 if (!BLOCK_USED(buffer))
457 {
458 if (part_id == 0) file_id = i;
459 memset( buffer, 0xFF, cfs->page_size - 0x02);
460 buffer[0] = 0x80;
461 buffer[1] = (cfs->page_size - 2) - 6 - ((part_id == 0) ? FILE_HEADER_SIZE : 0);
462 if (bytes_left < buffer[1]) buffer[1] = bytes_left;
463 buffer_write_16_be( buffer + 2, file_id);
464 buffer_write_16_be( buffer + 4, part_id);
465 if (part_id == 0)
466 {
467 buffer[6] = 0x20;
468 strcpy(BLOCK_FILENAME(buffer), filename);
469 buffer_write_32_be( buffer + 6 + FILE_HEADER_SIZE - 4, cybiko_time_setup(imgtool::datetime::now(imgtool::datetime::datetime_type::LOCAL)));
470 sourcef.read(buffer + 6 + FILE_HEADER_SIZE, buffer[1]);
471 }
472 else
473 {
474 sourcef.read(buffer + 6, buffer[1]);
475 }
476 if (!cfs_block_write( cfs, buffer, BLOCK_TYPE_FILE, i)) return IMGTOOLERR_WRITEERROR;
477 bytes_left -= buffer[1];
478 if (bytes_left == 0) break;
479 part_id++;
480 }
481 i++;
482 }
483 // ok
484 return IMGTOOLERR_SUCCESS;
485 }
486
cybiko_image_delete_file(imgtool::partition & partition,const char * filename)487 static imgtoolerr_t cybiko_image_delete_file(imgtool::partition &partition, const char *filename)
488 {
489 imgtool::image &image(partition.image());
490 cybiko_file_system *cfs = get_cfs(image);
491 uint16_t file_id;
492 // check filename
493 if (strlen(filename) > 58) return IMGTOOLERR_BADFILENAME;
494 // find file
495 if (!cfs_file_find(cfs, filename, &file_id)) return IMGTOOLERR_FILENOTFOUND;
496 // delete file
497 if (!cfs_file_delete(cfs, file_id)) return IMGTOOLERR_UNEXPECTED;
498 // ok
499 return IMGTOOLERR_SUCCESS;
500 }
501
cybikoxt_get_info(const imgtool_class * imgclass,uint32_t state,union imgtoolinfo * info)502 void cybikoxt_get_info( const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
503 {
504 switch (state)
505 {
506 // --- the following bits of info are returned as 64-bit signed integers ---
507 case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES : info->i = sizeof( cybiko_file_system); break;
508 case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES : info->i = sizeof( cybiko_iter); break;
509 // case IMGTOOLINFO_INT_SUPPORTS_CREATION_TIME : info->i = 1; break;
510 case IMGTOOLINFO_INT_SUPPORTS_LASTMODIFIED_TIME : info->i = 1; break;
511 // case IMGTOOLINFO_INT_BLOCK_SIZE : info->i = 264; break;
512 // --- the following bits of info are returned as pointers to data or functions ---
513 case IMGTOOLINFO_PTR_OPEN : info->open = cybiko_image_open; break;
514 case IMGTOOLINFO_PTR_CREATE : info->create = cybiko_image_create; break;
515 case IMGTOOLINFO_PTR_CLOSE : info->close = cybiko_image_close; break;
516 case IMGTOOLINFO_PTR_BEGIN_ENUM : info->begin_enum = cybiko_image_begin_enum; break;
517 case IMGTOOLINFO_PTR_NEXT_ENUM : info->next_enum = cybiko_image_next_enum; break;
518 case IMGTOOLINFO_PTR_FREE_SPACE : info->free_space = cybiko_image_free_space; break;
519 case IMGTOOLINFO_PTR_READ_FILE : info->read_file = cybiko_image_read_file; break;
520 case IMGTOOLINFO_PTR_WRITE_FILE : info->write_file = cybiko_image_write_file; break;
521 case IMGTOOLINFO_PTR_DELETE_FILE : info->delete_file = cybiko_image_delete_file; break;
522 // --- the following bits of info are returned as NULL-terminated strings ---
523 case IMGTOOLINFO_STR_NAME : strcpy( info->s = imgtool_temp_str(), "cybikoxt"); break;
524 case IMGTOOLINFO_STR_DESCRIPTION : strcpy( info->s = imgtool_temp_str(), "Cybiko Xtreme File System"); break;
525 case IMGTOOLINFO_STR_FILE : strcpy( info->s = imgtool_temp_str(), __FILE__); break;
526 case IMGTOOLINFO_STR_FILE_EXTENSIONS : strcpy( info->s = imgtool_temp_str(), "bin,nv"); break;
527 case IMGTOOLINFO_STR_EOLN : strcpy( info->s = imgtool_temp_str(), "\r\n"); break;
528 }
529 }
530