1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
|
// license:BSD-3-Clause
// copyright-holders:Antoine Mine
/****************************************************************************
hp48.c
Memory cards for HP48 SX/GX
Antoine Mine' 2014
*****************************************************************************
*/
/*
HP memory cards can contain two kinds of objects:
- attached libraries
- backup objects (encapsulating any kind of object)
Attached libraries appear with a LIB attribute in imgtool's listing, and in
the global "LIBRARY" menu on the HP48.
Backup objects appear with a BAK attribute in imgtool's listing, and
in the relevant "PORTS" menu on the HP48.
Currently, we only support storing host files into backup objects and
retreiving backup objects into files.
For attached library objects in the memory card, we only show their name, but
cannot retrieve their contents.
To install a library contained in a host file, copy it with imgtool to
the card; it creates a backup object visible in the "PORTS" menu;
then evalue the object, store it into some port (e.g, 0 STO),
turn off the HP48 and back on; the library should now be attached and
visible in the "LIBRARY" menu.
(The process is similar to installing a library from a file you get
from the serial interface.)
*/
/*
HP memory cards have size from 32 KB to 4096 KB.
However, cards with size greater than 128 KB are seen as multiple ports of
size 128 KB on the HP48 GX. In imgtool, we see them as distinct 128 KB
partitions.
Unfortunately, at the moment, partitions do not seem to be supported in the
command-line tool...
*/
/*
Here is the format of backup objects (addresses are in 4-bit nibbles):
offset field size value
0 prolog 5 02B62 prolog for overall backup object
5 size 5 len total size in nibble without prolog
10 name length 2 nbcar object name, in characters
12 name 2*nbcar -
12+2*nbcar name length 2 nbcar same value as name length at 10
14+2*nbcar object - -
len-5 prolog 5 02911 prolog for inlined system integer
len - 1 0
len+1 CRC 4 - CRC from 5 to len
total length: len+4
i.e., the backup object is a container containing the object name
and two objects: the backuped object itself and a system integer
that contains the CRC.
HP48 host files start with a "HPHP48-X" header (where X can be any
single letter, which denotes a ROM revision and, implicitly, whether
the program is for the S/SX or the G/GX family).
When storing a file to the memory card, we strip the header and
embed the object into a backup object with the correct CRC.
When copying from the memory card to a host file, we extract the object
from the backup container, stripping its CRC, and add the HPHP48-X header.
*/
/*****************************************************************************
Includes
*****************************************************************************/
#include "imgtool.h"
/*****************************************************************************
Data structures
*****************************************************************************/
struct hp48_card
{
imgtool_stream *stream;
int modified;
/* size, in bytes of card data: from 32 KB to 4 MB */
int size;
/* we store each nibble (4-bit) into its own byte, for simpler addressing;
hence, data has 2*size
*/
UINT8* data;
};
struct hp48_partition
{
/* pointer to the beginning of the partition inside the hp48_card */
UINT8* data;
/* size, in bytes (128 KB or less) */
int size;
};
struct hp48_directory
{
int pos;
};
#define PROLOG_BACKUP 0x02B62
#define PROLOG_LIBRARY 0x02B40
#define PROLOG_SYSINT 0x02911
/* memory cards are composed solely of libraries and backup objects */
#define IS_OBJECT(prolog) ((prolog) == PROLOG_LIBRARY || (prolog) == PROLOG_BACKUP)
/* host files begin with this prefix (up to the last letter) */
static const char* hp48_prefix = "HPHP48-R";
OPTION_GUIDE_START( hp48_create_optionguide )
OPTION_INT('S', "size", "Size in KB" )
OPTION_GUIDE_END
/* size in KB, 128 KB being the default */
static const char hp48_create_optionspec[] = "S32/64/[128]/256/512/1024/2048/4096";
/*****************************************************************************
Utility functions
*****************************************************************************/
/* byes to nibbles */
static void unpack(UINT8* dst, UINT8* src, int nsize)
{
int i;
if ( nsize & 1 )
{
dst[nsize-1] = src[nsize/2] & 0xf;
}
for ( i = nsize/2-1; i >= 0; i-- )
{
dst[2*i+1] = src[i] >> 4;
dst[2*i ] = src[i] & 0xf;
}
}
/* nibbles to bytes */
static void pack(UINT8* dst, UINT8* src, int nsize)
{
int i;
for ( i = 0 ; i < nsize/2; i++ )
{
dst[i] = (src[2*i] & 0xf) | (src[2*i+1] << 4);
}
if ( nsize & 1 )
{
dst[nsize/2] = src[nsize-1] & 0xf;
}
}
static int read20(UINT8* data)
{
return data[0] | (data[1] << 4) | (data[2] << 8) | (data[3] << 12) | (data[4] << 16);
}
static int read8(UINT8* data)
{
return data[0] | (data[1] << 4);
}
static void readstring(char* dst, UINT8* data, int nb)
{
int i;
for ( i = 0; i < nb; i++ )
{
dst[i] = read8( data + 2*i );
}
dst[nb] = 0;
}
static void write20(UINT8* data, int v)
{
data[0] = v & 0xf;
data[1] = (v >> 4) & 0xf;
data[2] = (v >> 8) & 0xf;
data[3] = (v >> 12) & 0xf;
data[4] = (v >> 16) & 0xf;
}
static void write8(UINT8* data, int v)
{
data[0] = v & 0xf;
data[1] = (v >> 4) & 0xf;
}
static void writestring(UINT8* data, const char* str, int nb)
{
int i;
for ( i = 0; i < nb; i++ )
{
write8( data + 2*i, str[i] );
}
}
/* go to the end, return its offset */
static int find_end(hp48_partition* p)
{
int pos = 0;
while (1)
{
if ( pos + 10 > 2*p->size) break;
int prolog = read20( p->data + pos );
if ( !IS_OBJECT( prolog )) break;
pos += read20( p->data + pos + 5 ) + 5;
}
if ( pos > 2*p->size ) pos = 2*p->size;
return pos;
}
/* find the backup object with the given name, returns its offset or -1 (not found) */
static int find_file(hp48_partition* p, const char* filename, int *ptotalsize, int* pstart, int* pcontentsize)
{
UINT8* data = p->data;
int pos = 0;
/* find file */
while (1)
{
if ( pos + 10 > 2*p->size) return -1;
/* get prolog */
int prolog = read20( data+pos );
if ( !IS_OBJECT(prolog)) return -1;
/* get size */
int totalsize = read20( data+pos+5 );
if ( totalsize < 14) return -1;
if ( pos + 5 + totalsize > 2*p->size ) return -1;
if ( prolog == PROLOG_BACKUP )
{
/* get name */
int namelen = read8( data + pos + 10 );
char name[257];
if ( 9 + 2*namelen > totalsize ) return -1;
readstring( name, data + pos + 12, namelen );
/* check name */
if ( !strcmp( name, filename ) )
{
/* found! */
if ( ptotalsize ) *ptotalsize = totalsize;
if ( pstart ) *pstart = pos + 14 + 2*namelen;
if ( pcontentsize ) *pcontentsize = totalsize - (9 + 2*namelen);
return pos;
}
else
{
/* skip */
pos += totalsize + 5;
}
}
else
{
/* skip */
pos += totalsize + 5;
}
}
// never executed
//return -1;
}
/* CRC computing.
This is the same CRC that is computed by the HP48 hardware.
*/
static UINT16 crc(UINT8* data, int len)
{
UINT16 crc = 0;
int i;
for ( i = 0; i < len; i++ )
{
crc = (crc >> 4) ^ (((crc ^ data[i]) & 0xf) * 0x1081);
}
return crc;
}
/*****************************************************************************
Imgtool functions
*****************************************************************************/
static imgtoolerr_t hp48_open(imgtool_image *img, imgtool_stream *stream)
{
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
int size = stream_size( stream );
/* check that size is a power of 2 between 32 KB and 4 MG */
if ( (size < 32 * 1024) ||
(size > 4 * 1024 * 1024) ||
(size & (size-1)) )
{
return IMGTOOLERR_CORRUPTIMAGE;
}
/* store info */
c->stream = stream;
c->modified = 0;
c->size = size;
c->data = (UINT8*) malloc( 2 * size );
if ( !c->data )
{
return IMGTOOLERR_READERROR;
}
/* fully load image */
stream_seek( stream, 0, SEEK_SET );
if ( stream_read( stream, c->data, size ) < size )
{
return IMGTOOLERR_READERROR;
}
unpack( c->data, c->data, 2 * size );
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_create(imgtool_image* img,
imgtool_stream *stream,
util::option_resolution *opts)
{
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
int size;
size = opts->lookup_int('S');
c->stream = stream;
c->modified = 1;
c->size = size * 1024;
c->data = (UINT8*) malloc( 2 * c->size );
if ( !c->data )
{
return IMGTOOLERR_READERROR;
}
/* zeroing the image seems fine */
memset( c->data, 0, 2 * c->size );
return IMGTOOLERR_SUCCESS;
}
static void hp48_close(imgtool_image *img)
{
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
if ( c->modified )
{
/* save image */
pack( c->data, c->data, 2 * c->size );
stream_seek( c->stream, 0, SEEK_SET );
stream_write( c->stream, c->data, c->size );
}
/* clean up */
free( c->data );
stream_close( c->stream );
}
/* each 128 KB chunk is a distinct partition */
#define MAX_PORT_SIZE (128*1024)
void hp48_partition_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info);
static imgtoolerr_t hp48_list_partitions(imgtool_image *img,
imgtool_partition_info *partitions,
size_t len)
{
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
int i;
for ( i = 0; i < len && i * MAX_PORT_SIZE < c->size ; i++ )
{
/* offset and size in bytes */
partitions[i].base_block = i * MAX_PORT_SIZE;
partitions[i].block_count = c->size - partitions[i].base_block;
if ( partitions[i].block_count > MAX_PORT_SIZE )
{
partitions[i].block_count = MAX_PORT_SIZE;
}
partitions[i].get_info = hp48_partition_get_info;
}
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_open_partition(imgtool_partition *part,
UINT64 first_block, UINT64 block_count)
{
imgtool_image* img = imgtool_partition_image( part );
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
if ( first_block + block_count > c->size )
return IMGTOOLERR_INVALIDPARTITION;
/* store partition position */
p->data = c->data + first_block;
p->size = block_count;
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_beginenum(imgtool_directory *enumeration,
const char *path)
{
hp48_directory* d = (hp48_directory*) imgtool_directory_extrabytes( enumeration );
d->pos = 0;
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_nextenum(imgtool_directory *enumeration,
imgtool_dirent *ent)
{
imgtool_partition *part = imgtool_directory_partition( enumeration );
//imgtool_image* img = imgtool_partition_image( part );
//hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
hp48_directory* d = (hp48_directory*) imgtool_directory_extrabytes( enumeration );
UINT8* data = p->data;
int pos = d->pos;
if ( pos < 0 || pos+12 > 2*p->size )
{
ent->eof = 1;
return IMGTOOLERR_SUCCESS;
}
int prolog = read20( data+pos );
if ( IS_OBJECT(prolog) )
{
pos += 5;
int totalsize = read20( data+pos );
pos += 5;
int namelen = read8( data+pos );
pos += 2;
if ( (pos + 2*namelen > 2*p->size) ||
(namelen >= sizeof(ent->filename)) )
{
ent->eof = 1;
return IMGTOOLERR_CORRUPTFILE;
}
readstring( ent->filename, data+pos, namelen );
/* compute size in bytes, removing name, length & CRC fields */
ent->filesize = ((totalsize - 19 - 2*namelen) + 1) / 2;
switch (prolog)
{
case PROLOG_LIBRARY: strncpy( ent->attr, "LIB", sizeof(ent->attr) ); break;
case PROLOG_BACKUP: strncpy( ent->attr, "BAK", sizeof(ent->attr) ); break;
default: strncpy( ent->attr, "?", sizeof(ent->attr) );
}
d->pos = d->pos + totalsize + 5;
}
else
{
/* 0 or unknown object => end */
ent->eof = 1;
}
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_freespace(imgtool_partition *part, UINT64 *size)
{
//imgtool_image* img = imgtool_partition_image( part );
//hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
*size = p->size - (find_end(p)+1)/2;
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_readfile(imgtool_partition *part,
const char *filename,
const char *fork,
imgtool_stream *destf)
{
//imgtool_image* img = imgtool_partition_image( part );
//hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
/* find entry */
int totalsize, start, size;
int pos = find_file(p, filename, &totalsize, &start, &size);
if ( pos == -1 )
{
return IMGTOOLERR_FILENOTFOUND;
}
/* CRC check */
UINT16 objcrc = read20( p->data + pos + totalsize ) >> 4;
UINT16 mycrc = crc( p->data + pos + 5, totalsize - 4);
if ( objcrc != mycrc )
{
return IMGTOOLERR_CORRUPTIMAGE;
}
size -= 10;
/* save header */
stream_write( destf, hp48_prefix, 8);
/* save contents to host file */
int bytesize = (size + 1) / 2;
UINT8* buf = (UINT8*) malloc( bytesize );
if (!buf)
{
return IMGTOOLERR_FILENOTFOUND;
}
pack( buf, p->data + start, size );
stream_write( destf, buf, bytesize );
free( buf );
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_deletefile(imgtool_partition *part,
const char *filename)
{
imgtool_image* img = imgtool_partition_image( part );
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
/* find entry */
int totalsize;
int pos = find_file(p, filename, &totalsize, NULL, NULL );
if ( pos == -1 )
{
return IMGTOOLERR_FILENOTFOUND;
}
/* move */
totalsize += 5;
memmove( p->data+pos, p->data+pos+totalsize, 2*p->size-(pos+totalsize) );
memset( p->data + 2*p->size-totalsize, 0, totalsize);
c->modified = 1;
return IMGTOOLERR_SUCCESS;
}
static imgtoolerr_t hp48_writefile(imgtool_partition *part,
const char *filename,
const char *fork,
imgtool_stream *sourcef,
util::option_resolution *opts)
{
imgtool_image* img = imgtool_partition_image( part );
hp48_card* c = (hp48_card*) imgtool_image_extra_bytes( img );
hp48_partition* p = (hp48_partition*) imgtool_partition_extra_bytes( part );
/* check header */
char head[8];
stream_read( sourcef, head, 8 );
if ( memcmp( head, hp48_prefix, 7) )
{
return IMGTOOLERR_READERROR;
}
/* ensure that the file does not exist */
/* TODO: resize the existing file instead, to keep it in place? */
hp48_deletefile( part, filename );
/* goto end */
//UINT8* data = p->data;
int pos = find_end(p);
int len = strlen( filename );
if ( len > 255 ) len = 255;
/* check size */
int filesize = stream_size( sourcef ) - 8;
if ( pos + 2*filesize + 24 + 2*len > 2 * p->size )
{
return IMGTOOLERR_NOSPACE;
}
/* load file */
UINT8* buf = (UINT8*) malloc( filesize );
if ( !buf ) return IMGTOOLERR_NOSPACE;
stream_read( sourcef, buf, filesize );
/* store backup object */
int org = pos;
int totalsize = 2*filesize + 19 + 2*len;
write20( p->data+pos, PROLOG_BACKUP );
pos +=5;
write20( p->data+pos, totalsize );
pos +=5;
write8( p->data+pos, len );
pos += 2;
writestring( p->data+pos, filename, len );
pos += 2*len;
write8( p->data+pos, len );
pos += 2;
unpack( p->data+pos, buf, 2*filesize );
pos += 2*filesize;
/* store crc */
write20( p->data+pos, PROLOG_SYSINT );
pos += 5;
p->data[pos] = 0;
write20( p->data+pos, crc(p->data+org+5, totalsize-4) << 4 );
free(buf);
c->modified = 1;
return IMGTOOLERR_SUCCESS;
}
/*****************************************************************************
Imgtool module declaration
*****************************************************************************/
void hp48_partition_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
{
switch(state)
{
/* --- the following bits of info are returned as NULL-terminated strings --- */
case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "hp48"); break;
case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "HP48 SX/GX memory card"); break;
case IMGTOOLINFO_STR_FILE: strcpy(info->s = imgtool_temp_str(), __FILE__); break;
case IMGTOOLINFO_STR_FILE_EXTENSIONS: strcpy(info->s = imgtool_temp_str(), "crd"); break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case IMGTOOLINFO_PTR_OPEN_PARTITION: info->open_partition = hp48_open_partition; break;
case IMGTOOLINFO_PTR_CLOSE: info->close = hp48_close; break;
case IMGTOOLINFO_PTR_BEGIN_ENUM: info->begin_enum = hp48_beginenum; break;
case IMGTOOLINFO_PTR_NEXT_ENUM: info->next_enum = hp48_nextenum; break;
case IMGTOOLINFO_PTR_FREE_SPACE: info->free_space = hp48_freespace; break;
case IMGTOOLINFO_PTR_READ_FILE: info->read_file = hp48_readfile; break;
case IMGTOOLINFO_PTR_WRITE_FILE: info->write_file = hp48_writefile; break;
case IMGTOOLINFO_PTR_DELETE_FILE: info->delete_file = hp48_deletefile; break;
case IMGTOOLINFO_INT_PARTITION_EXTRA_BYTES: info->i = sizeof(hp48_partition); break;
case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES: info->i = sizeof(hp48_directory); break;
}
}
void hp48_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
{
switch(state)
{
/* --- the following bits of info are returned as NULL-terminated strings --- */
case IMGTOOLINFO_STR_NAME: strcpy(info->s = imgtool_temp_str(), "hp48"); break;
case IMGTOOLINFO_STR_DESCRIPTION: strcpy(info->s = imgtool_temp_str(), "HP48 SX/GX memory card"); break;
case IMGTOOLINFO_STR_FILE: strcpy(info->s = imgtool_temp_str(), __FILE__); break;
case IMGTOOLINFO_STR_FILE_EXTENSIONS: strcpy(info->s = imgtool_temp_str(), "crd"); break;
case IMGTOOLINFO_STR_CREATEIMAGE_OPTSPEC: strcpy(info->s = imgtool_temp_str(), hp48_create_optionspec); break;
case IMGTOOLINFO_PTR_CREATEIMAGE_OPTGUIDE: info->createimage_optguide = &hp48_create_optionguide; break;
/* --- the following bits of info are returned as pointers to data or functions --- */
case IMGTOOLINFO_PTR_OPEN: info->open = hp48_open; break;
case IMGTOOLINFO_PTR_CREATE: info->create = hp48_create; break;
case IMGTOOLINFO_PTR_CLOSE: info->close = hp48_close; break;
case IMGTOOLINFO_PTR_LIST_PARTITIONS: info->list_partitions = hp48_list_partitions; break;
case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES: info->i = sizeof(hp48_card); break;
}
}
|