1 // license:BSD-3-Clause
2 // copyright-holders:F. Ulivi
3 /*********************************************************************
4
5 hp9845_tape.cpp
6
7 HP-9845 tape format
8
9 This imgtool module manipulates HTI files. These are image files
10 of the DC-100 tape cartridges that are simulated for the HP9845B
11 driver.
12 HP9845 filesystem for tapes has the following features:
13 * File names are 1 to 6 characters long.
14 * Case is significant in file names.
15 * There is no file "extension", file type is encoded separately
16 in file metadata.
17 * There are 8 file types. File type is encoded in 5 bits.
18 Only 8 out of the 32 possible values are valid.
19 * This module handles the file type as a fake file extension.
20 For example, a file named "TEST" having DATA type is get/put/shown
21 as "TEST.DATA".
22 * File type is deduced from host file extension when putting files
23 into image. File type can be overridden by the "ftype" option.
24 This table summarizes the file types.
25
26 ftype Fake Type of file BASIC commands
27 switch extension for this file type
28 ========================================================
29 U BKUP "Database backup"
30 No idea
31 D DATA Generic record-based data file
32 SAVE/GET/PRINT#/READ#
33 P PROG Program file (tokenized BASIC & other data)
34 STORE/LOAD
35 K KEYS KEY file (definition of soft keys)
36 STORE KEY/LOAD KEY
37 T BDAT Binary data file
38 ?
39 A ALL Full dump of system state
40 STORE ALL/LOAD ALL
41 B BPRG Binary program file
42 STORE BIN/LOAD BIN
43 O OPRM Option ROM specific file
44 ?
45
46 * Files are always stored in units of 256-byte physical records.
47 * An important metadata of files is WPR: Words Per Record. This
48 is a numeric value that sets the length of each logical record of
49 the file (in units of 16-bit words). It defaults to 128 (i.e.
50 logical and physical records are the same thing). It can be
51 set by the "wpr" option when putting files into the image.
52 * There is no fragmentation map in the filesystem: each file
53 always occupy a contiguous set of physical records. This fact
54 could prevent the putting of a file into an image when there
55 is no single block of free records big enough to hold the file
56 even though the total amount of free space would be sufficient.
57
58 Notes on commands
59 =================
60
61 **** dir command ****
62 The format of the "attr" part of file listing is as follows:
63 %c '*' if file has the protection bit set, else ' '
64 %02x Hexadecimal value of file type (00-1f)
65 %c '?' if file type is not valid, else ' '
66 %4u Number of logical records
67 %4u WPR * 2 (i.e. bytes per logical record)
68 %3u First physical record of file
69
70 **** get command ****
71 A file can be extracted from an image with or without an explicit
72 extension. If an extension is given, it must match the one corresponding
73 to file type.
74 The "9845data" filter can be used on DATA files (see below).
75
76 **** getall command ****
77 Files are extracted with their "fake" extension.
78
79 **** put command ****
80 File type can be specified explicitly through the "ftype" option.
81 If this option is "auto" (the default), type is deduced from file
82 extension, if present. When extension is not given or it doesn't
83 match any known type, file type is set to "DATA".
84 WPR can be set through the "wpr" option. If it's 0 (the default),
85 WPR is set to 128.
86 The "9845data" filter can be used on DATA files (see below).
87
88 **** del command ****
89 File extension is ignored, if present.
90
91 "9845data" filter
92 =================
93
94 This filter can be applied to DATA files whose content is made
95 of strings only. BASIC programs that are saved with "SAVE" command
96 have this format.
97 This filter translates a DATA file into a standard ASCII text file
98 and viceversa.
99 Keep in mind that this translation is NOT lossless because all
100 non-ASCII & non printable characters are substituted with spaces.
101 This kind of characters must be removed because they may confuse
102 the line-by-line reading of file when translating in the opposite
103 direction.
104 The 9845 system has the capability to insert formatting characters
105 directly in the text strings to be displayed on screen. These
106 characters set things like inverse video or underline.
107 Turning a DATA file into a text file through this filter removes
108 these special characters.
109
110 *********************************************************************/
111 #include <bitset>
112
113 #include "imgtool.h"
114 #include "formats/imageutl.h"
115 #include "formats/hti_tape.h"
116
117 // Constants
118 #define SECTOR_LEN 256 // Bytes in a sector
119 #define WORDS_PER_SECTOR (SECTOR_LEN / 2) // 16-bit words in a sector payload
120 #define SECTORS_PER_TRACK 426 // Sectors in a track
121 #define TRACKS_NO 2 // Number of tracks
122 #define TOT_SECTORS (SECTORS_PER_TRACK * TRACKS_NO) // Total number of sectors
123 #define DIR_WORD_0 0x0500 // First word of directories
124 #define DIR_WORD_1 0xffff // Second word of directories
125 #define DIR_LAST_WORD 0xffff // Last word of directories
126 #define FIRST_DIR_SECTOR 1 // First directory sector
127 #define SECTORS_PER_DIR 2 // Sectors per copy of directory
128 #define MAX_DIR_ENTRIES 42 // And the answer is.... the maximum number of entries in the directory!
129 #define DIR_COPIES 2 // Count of directory copies
130 #define CHARS_PER_FNAME 6 // Maximum characters in a filename
131 #define CHARS_PER_EXT 4 // Characters in file extension. Extension is encoded as file type, it's not actually stored in directory as characters.
132 #define CHARS_PER_FNAME_EXT (CHARS_PER_FNAME + 1 + CHARS_PER_EXT) // Characters in filename + extension
133 #define PAD_WORD 0xffff // Word value for padding
134 #define FIRST_FILE_SECTOR (FIRST_DIR_SECTOR + SECTORS_PER_DIR * DIR_COPIES) // First file sector
135 #define START_POS ((tape_pos_t)(72.25 * hti_format_t::ONE_INCH_POS)) // Start position on each track
136 #define DZ_WORDS 350 // Words in deadzone
137 #define IRG_SIZE hti_format_t::ONE_INCH_POS // Size of inter-record-gap: 1"
138 #define IFG_SIZE ((tape_pos_t)(2.5 * hti_format_t::ONE_INCH_POS)) // Size of inter-file-gap: 2.5"
139 #define HDR_W0_ZERO_MASK 0x4000 // Mask of zero bits in word 0 of header
140 #define RES_FREE_FIELD 0x2000 // Mask of "reserved free field" bit
141 #define FILE_ID_BIT 0x8000 // Mask of "file identifier" bit
142 #define SECTOR_IN_USE 0x1800 // Mask of "empty record indicator" (== !sector in use indicator)
143 #define SIF_FILE_NO 1 // SIF file #
144 #define SIF_FILE_NO_MASK 0x07ff // Mask of SIF file #
145 #define SIF_FREE_FIELD 0 // SIF free field
146 #define SIF_FREE_FIELD_MASK 0xf000 // Mask of SIF free field
147 #define BYTES_AVAILABLE 0xff00 // "bytes available" field = 256
148 #define BYTES_AVAILABLE_MASK 0xff00 // Mask of "bytes available" field
149 #define BYTES_USED 0x00ff // "bytes used" field = 256
150 #define BYTES_USED_MASK 0x00ff // Mask of "bytes used" field
151 #define FORMAT_SECT_SIZE ((tape_pos_t)(2.67 * hti_format_t::ONE_INCH_POS)) // Size of sectors including padding: 2.67"
152 #define PREAMBLE_WORD 0x0001 // Value of preamble word
153 #define WORDS_PER_HEADER_N_SECTOR (WORDS_PER_SECTOR + 5)
154 #define MIN_IRG_SIZE ((tape_pos_t)(16 * 1024)) // Minimum size of IRG gaps: 0.017"
155
156 // File types
157 #define BKUP_FILETYPE 0
158 #define BKUP_ATTR_STR "BKUP"
159 #define DATA_FILETYPE 1
160 #define DATA_ATTR_STR "DATA"
161 #define PROG_FILETYPE 2
162 #define PROG_ATTR_STR "PROG"
163 #define KEYS_FILETYPE 3
164 #define KEYS_ATTR_STR "KEYS"
165 #define BDAT_FILETYPE 4
166 #define BDAT_ATTR_STR "BDAT"
167 #define ALL_FILETYPE 5
168 #define ALL_ATTR_STR "ALL"
169 #define BPRG_FILETYPE 6
170 #define BPRG_ATTR_STR "BPRG"
171 #define OPRM_FILETYPE 7
172 #define OPRM_ATTR_STR "OPRM"
173
174 // Record type identifiers
175 #define REC_TYPE_EOR 0x1e // End-of-record
176 #define REC_TYPE_FULLSTR 0x3c // A whole (un-split) string
177 #define REC_TYPE_EOF 0x3e // End-of-file
178 #define REC_TYPE_1STSTR 0x1c // First part of a string
179 #define REC_TYPE_MIDSTR 0x0c // Middle part(s) of a string
180 #define REC_TYPE_ENDSTR 0x2c // Last part of a string
181
182 // End-of-lines
183 #define EOLN (CRLF == 1 ? "\r" : (CRLF == 2 ? "\n" : (CRLF == 3 ? "\r\n" : NULL)))
184
185 // Words stored on tape
186 using tape_word_t = hti_format_t::tape_word_t;
187
188 // Tape position, 1 unit = 1 inch / (968 * 1024)
189 using tape_pos_t = hti_format_t::tape_pos_t;
190
191 /********************************************************************************
192 * Directory entries
193 ********************************************************************************/
194 typedef struct {
195 uint8_t filename[ CHARS_PER_FNAME ]; // Filename (left justified, 0 padded on the right)
196 bool protection; // File protection
197 uint8_t filetype; // File type (00-1f)
198 uint16_t filepos; // File position (# of 1st sector)
199 uint16_t n_recs; // Number of records
200 uint16_t wpr; // Word-per-record
201 unsigned n_sects; // Count of sectors
202 } dir_entry_t;
203
204 /********************************************************************************
205 * Tape image
206 ********************************************************************************/
207 class tape_image_t {
208 public:
209 tape_image_t(void);
210
is_dirty(void) const211 bool is_dirty(void) const { return dirty; }
212
213 void format_img(void);
214
215 imgtoolerr_t load_from_file(imgtool::stream *stream);
216 imgtoolerr_t save_to_file(imgtool::stream *stream);
217
218 unsigned free_sectors(void) const;
219
220 void set_sector(unsigned s_no , const tape_word_t *s_data);
221 void unset_sector(unsigned s_no);
222 bool get_sector(unsigned s_no , tape_word_t *s_data);
223
224 bool get_dir_entry(unsigned idx , const dir_entry_t*& entry) const;
225 bool find_file(const char *filename , bool ignore_ext , unsigned& idx) const;
226
227 void delete_dir_entry(unsigned idx);
228
229 bool find_free_block(unsigned blocks , unsigned& first_s) const;
230
231 bool alloc_new_file(unsigned blocks , dir_entry_t*& entry);
232
233 static void tape_word_to_bytes(tape_word_t w , uint8_t& bh , uint8_t& bl);
234 static void bytes_to_tape_word(uint8_t bh , uint8_t bl , tape_word_t& w);
235
236 static void get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark);
237 static void split_filename_and_ext(const char *filename , char *fname , char *ext);
238
239 private:
240 bool dirty;
241 // Tape image
242 tape_word_t img[ TOT_SECTORS ][ WORDS_PER_SECTOR ];
243 // Map of sectors in use
244 std::bitset<TOT_SECTORS> alloc_map;
245 // Directory
246 std::vector<dir_entry_t> dir;
247
248 static void wipe_sector(tape_word_t *s);
249 void dump_dir_sect(const tape_word_t *dir_sect , unsigned dir_sect_idx);
250 void fill_and_dump_dir_sect(tape_word_t *dir_sect , unsigned& idx , unsigned& dir_sect_idx , tape_word_t w) ;
251 void encode_dir(void);
252 bool read_sector_words(unsigned& sect_no , unsigned& sect_idx , size_t word_no , tape_word_t *out) const;
253 static bool filename_char_check(uint8_t c);
254 static bool filename_check(const uint8_t *filename);
255 bool decode_dir(void);
256 void save_words(hti_format_t& img , unsigned track , tape_pos_t& pos , const tape_word_t *block , unsigned block_len);
257 static tape_word_t checksum(const tape_word_t *block , unsigned block_len);
258 };
259
260 /********************************************************************************
261 * Image state
262 ********************************************************************************/
263 typedef struct {
264 imgtool::stream *stream;
265 tape_image_t *img;
266 } tape_state_t;
267
268 /********************************************************************************
269 * Directory enumeration
270 ********************************************************************************/
271 typedef struct {
272 unsigned dir_idx;
273 } dir_state_t;
274
275 /********************************************************************************
276 * Internal functions
277 ********************************************************************************/
tape_image_t(void)278 tape_image_t::tape_image_t(void)
279 : dirty(false)
280 {
281 }
282
format_img(void)283 void tape_image_t::format_img(void)
284 {
285 // Deallocate all sectors
286 alloc_map.reset();
287
288 // Create an empty directory
289 dir.clear();
290
291 dirty = true;
292 }
293
my_seekproc(void * file,int64_t offset,int whence)294 static int my_seekproc(void *file, int64_t offset, int whence)
295 {
296 reinterpret_cast<imgtool::stream *>(file)->seek(offset, whence);
297 return 0;
298 }
299
my_readproc(void * file,void * buffer,size_t length)300 static size_t my_readproc(void *file, void *buffer, size_t length)
301 {
302 return reinterpret_cast<imgtool::stream *>(file)->read(buffer, length);
303 }
304
my_writeproc(void * file,const void * buffer,size_t length)305 static size_t my_writeproc(void *file, const void *buffer, size_t length)
306 {
307 reinterpret_cast<imgtool::stream *>(file)->write(buffer, length);
308 return length;
309 }
310
my_filesizeproc(void * file)311 static uint64_t my_filesizeproc(void *file)
312 {
313 return reinterpret_cast<imgtool::stream *>(file)->size();
314 }
315
316 static const struct io_procs my_stream_procs = {
317 nullptr,
318 my_seekproc,
319 my_readproc,
320 my_writeproc,
321 my_filesizeproc
322 };
323
load_from_file(imgtool::stream * stream)324 imgtoolerr_t tape_image_t::load_from_file(imgtool::stream *stream)
325 {
326 hti_format_t inp_image;
327 inp_image.set_bits_per_word(16);
328
329 io_generic io;
330 io.file = (void *)stream;
331 io.procs = &my_stream_procs;
332 io.filler = 0;
333
334 if (!inp_image.load_tape(&io)) {
335 return IMGTOOLERR_READERROR;
336 }
337
338 unsigned exp_sector = 0;
339 unsigned last_sector_on_track = SECTORS_PER_TRACK;
340 for (unsigned track = 0; track < TRACKS_NO; track++ , last_sector_on_track += SECTORS_PER_TRACK) {
341 tape_pos_t pos = 0;
342 // Loader state:
343 // 0 Wait for DZ
344 // 1 Wait for sector data
345 // 2 Wait for gap
346 unsigned state = 0;
347
348 while (exp_sector != last_sector_on_track) {
349 switch (state) {
350 case 0:
351 case 1:
352 {
353 hti_format_t::track_iterator_t it;
354
355 if (!inp_image.next_data(track , pos , true , false , it)) {
356 // No more data on tape
357 return IMGTOOLERR_CORRUPTIMAGE;
358 }
359 if (state == 1) {
360 // Extract record data
361
362 // The top 8 bits are ignored by TACO when aligning with preamble
363 unsigned bit_idx = 7;
364 if (!inp_image.sync_with_record(track , it , bit_idx)) {
365 // Couldn't align
366 return IMGTOOLERR_CORRUPTIMAGE;
367 }
368 tape_word_t buffer[ WORDS_PER_HEADER_N_SECTOR ];
369 for (unsigned i = 0; i < WORDS_PER_HEADER_N_SECTOR; i++) {
370 auto res = inp_image.next_word(track , it , bit_idx , buffer[ i ]);
371 if (res != hti_format_t::ADV_CONT_DATA) {
372 return IMGTOOLERR_CORRUPTIMAGE;
373 }
374 }
375 if (buffer[ 3 ] != checksum(&buffer[ 0 ], 3) ||
376 buffer[ 4 + WORDS_PER_SECTOR ] != checksum(&buffer[ 4 ], WORDS_PER_SECTOR)) {
377 return IMGTOOLERR_CORRUPTIMAGE;
378 }
379 // Check record content
380 if (exp_sector != (buffer[ 1 ] & 0xfff)) {
381 return IMGTOOLERR_CORRUPTIMAGE;
382 }
383 if (((buffer[ 0 ] & FILE_ID_BIT) != 0) != (exp_sector == 0)) {
384 return IMGTOOLERR_CORRUPTIMAGE;
385 }
386 if ((buffer[ 0 ] & (HDR_W0_ZERO_MASK | RES_FREE_FIELD | SIF_FILE_NO_MASK)) != (RES_FREE_FIELD | SIF_FILE_NO)) {
387 return IMGTOOLERR_CORRUPTIMAGE;
388 }
389 if ((buffer[ 1 ] & SIF_FREE_FIELD_MASK) != SIF_FREE_FIELD) {
390 return IMGTOOLERR_CORRUPTIMAGE;
391 }
392 bool in_use = (buffer[ 0 ] & SECTOR_IN_USE) != 0;
393 if ((buffer[ 2 ] & BYTES_AVAILABLE_MASK) != BYTES_AVAILABLE ||
394 (in_use && (buffer[ 2 ] & BYTES_USED_MASK) != BYTES_USED) ||
395 (!in_use && (buffer[ 2 ] & BYTES_USED_MASK) != 0)) {
396 return IMGTOOLERR_CORRUPTIMAGE;
397 }
398 if (in_use) {
399 set_sector(exp_sector, &buffer[ 4 ]);
400 } else {
401 unset_sector(exp_sector);
402 }
403 exp_sector++;
404 }
405 pos = it->first;
406 state = 2;
407 }
408 break;
409
410 case 2:
411 // Find next gap
412 if (!inp_image.next_gap(track , pos , true , MIN_IRG_SIZE)) {
413 return IMGTOOLERR_CORRUPTIMAGE;
414 }
415 state = 1;
416 break;
417
418 }
419 }
420 }
421
422 if (!decode_dir()) {
423 return IMGTOOLERR_CORRUPTDIR;
424 }
425
426 dirty = false;
427
428 return IMGTOOLERR_SUCCESS;
429 }
430
save_words(hti_format_t & img,unsigned track,tape_pos_t & pos,const tape_word_t * block,unsigned block_len)431 void tape_image_t::save_words(hti_format_t& img , unsigned track , tape_pos_t& pos , const tape_word_t *block , unsigned block_len)
432 {
433 // Preamble
434 tape_pos_t length;
435 img.write_word(track , pos , PREAMBLE_WORD , length);
436 pos += length;
437 // Words
438 for (unsigned i = 0; i < block_len; i++) {
439 img.write_word(track , pos , *block++ , length);
440 pos += length;
441 }
442 }
443
checksum(const tape_word_t * block,unsigned block_len)444 tape_word_t tape_image_t::checksum(const tape_word_t *block , unsigned block_len)
445 {
446 tape_word_t csum = 0;
447 for (unsigned i = 0; i < block_len; i++) {
448 csum += *block++;
449 }
450 return csum & 0xffff;
451 }
452
save_to_file(imgtool::stream * stream)453 imgtoolerr_t tape_image_t::save_to_file(imgtool::stream *stream)
454 {
455 // Encode copies of directory into sectors
456 encode_dir();
457
458 // Store sectors into image
459 hti_format_t out_image;
460
461 unsigned rec_no = 0;
462 for (unsigned track = 0; track < TRACKS_NO; track++) {
463 tape_pos_t pos = START_POS;
464
465 // Start of either track
466 // Deadzone + 1" of gap
467 tape_word_t deadzone[ DZ_WORDS ];
468 for (auto& dz : deadzone) {
469 dz = PAD_WORD;
470 }
471 save_words(out_image, track, pos, deadzone, DZ_WORDS);
472 pos += IRG_SIZE;
473
474 for (unsigned i = 0; i < SECTORS_PER_TRACK; i++ , rec_no++) {
475 bool in_use = alloc_map[ rec_no ];
476 // Sector header
477 tape_word_t sector[ WORDS_PER_HEADER_N_SECTOR ];
478
479 // Header word 0: file identifier bit, reserved free-field bit, empty record indicator & file #
480 sector[ 0 ] = RES_FREE_FIELD | SIF_FILE_NO;
481 if (rec_no == 0) {
482 sector[ 0 ] |= FILE_ID_BIT;
483 }
484 if (in_use) {
485 sector[ 0 ] |= SECTOR_IN_USE;
486 }
487 // Header word 1: free-field & sector #
488 sector[ 1 ] = SIF_FREE_FIELD | rec_no;
489 // Header word 2: bytes available & bytes used
490 sector[ 2 ] = BYTES_AVAILABLE;
491 if (in_use) {
492 sector[ 2 ] |= BYTES_USED;
493 }
494 // Checksum of header
495 sector[ 3 ] = checksum(§or[ 0 ] , 3);
496 // Sector payload
497 if (in_use) {
498 memcpy(§or[ 4 ] , &img[ rec_no ][ 0 ] , SECTOR_LEN);
499 } else {
500 for (unsigned j = 4; j < (4 + WORDS_PER_SECTOR); j++) {
501 sector[ j ] = PAD_WORD;
502 }
503 }
504 // Checksum of payload
505 sector[ 4 + WORDS_PER_SECTOR ] = checksum(§or[ 4 ] , WORDS_PER_SECTOR);
506
507 tape_pos_t start_pos = pos;
508 save_words(out_image, track, pos, sector, WORDS_PER_HEADER_N_SECTOR);
509
510 // Pad sector up to FORMAT_SECT_SIZE
511 while ((pos - start_pos) < FORMAT_SECT_SIZE) {
512 tape_pos_t length;
513 out_image.write_word(track , pos , PAD_WORD , length);
514 pos += length;
515 }
516
517 // Gap between sectors
518 if (rec_no == 0) {
519 pos += IFG_SIZE;
520 } else {
521 pos += IRG_SIZE;
522 }
523 }
524 }
525
526 io_generic io;
527 io.file = (void *)stream;
528 io.procs = &my_stream_procs;
529 io.filler = 0;
530
531 out_image.save_tape(&io);
532
533 return IMGTOOLERR_SUCCESS;
534 }
535
free_sectors(void) const536 unsigned tape_image_t::free_sectors(void) const
537 {
538 std::bitset<TOT_SECTORS> tmp(alloc_map);
539
540 // Reserve sectors that cannot be allocated to files
541 for (unsigned i = 0; i < FIRST_FILE_SECTOR; i++) {
542 tmp[ i ] = true;
543 }
544
545 return TOT_SECTORS - tmp.count();
546 }
547
set_sector(unsigned s_no,const tape_word_t * s_data)548 void tape_image_t::set_sector(unsigned s_no , const tape_word_t *s_data)
549 {
550 if (s_no < TOT_SECTORS) {
551 memcpy(&img[ s_no ][ 0 ] , s_data , SECTOR_LEN);
552 alloc_map.set(s_no);
553 dirty = true;
554 }
555 }
556
unset_sector(unsigned s_no)557 void tape_image_t::unset_sector(unsigned s_no)
558 {
559 if (s_no < TOT_SECTORS) {
560 alloc_map.reset(s_no);
561 dirty = true;
562 }
563 }
564
get_sector(unsigned s_no,tape_word_t * s_data)565 bool tape_image_t::get_sector(unsigned s_no , tape_word_t *s_data)
566 {
567 if (s_no < TOT_SECTORS && alloc_map[ s_no ]) {
568 memcpy(s_data , &img[ s_no ][ 0 ] , SECTOR_LEN);
569 return true;
570 } else {
571 return false;
572 }
573 }
574
get_dir_entry(unsigned idx,const dir_entry_t * & entry) const575 bool tape_image_t::get_dir_entry(unsigned idx , const dir_entry_t*& entry) const
576 {
577 if (idx >= dir.size()) {
578 return false;
579 } else {
580 entry = &dir[ idx ];
581 return true;
582 }
583 }
584
find_file(const char * filename,bool ignore_ext,unsigned & idx) const585 bool tape_image_t::find_file(const char *filename , bool ignore_ext , unsigned& idx) const
586 {
587 char fname[ CHARS_PER_FNAME_EXT + 1 ];
588 char ext[ CHARS_PER_EXT + 1 ];
589
590 split_filename_and_ext(filename, fname, ext);
591
592 bool has_ext = !ignore_ext && *ext != '\0';
593
594 if (has_ext) {
595 strcat(fname , ".");
596 strcat(fname , ext);
597 }
598
599 for (auto i = dir.cbegin(); i < dir.cend(); i++) {
600 char full_fname[ CHARS_PER_FNAME_EXT + 1 ];
601 bool qmark;
602
603 get_filename_and_ext(*i, has_ext, full_fname, qmark);
604
605 if (strcmp(fname , full_fname) == 0) {
606 idx = i - dir.cbegin();
607 return true;
608 }
609 }
610
611 return false;
612 }
613
delete_dir_entry(unsigned idx)614 void tape_image_t::delete_dir_entry(unsigned idx)
615 {
616 const dir_entry_t& ent = dir[ idx ];
617
618 // Release all sectors of file
619 for (unsigned i = ent.filepos; i < ent.filepos + ent.n_sects; i++) {
620 unset_sector(i);
621 }
622
623 dir.erase(dir.begin() + idx);
624 dirty = true;
625 }
626
find_free_block(unsigned blocks,unsigned & first_s) const627 bool tape_image_t::find_free_block(unsigned blocks , unsigned& first_s) const
628 {
629 if (blocks >= (TOT_SECTORS - FIRST_FILE_SECTOR)) {
630 return false;
631 }
632
633 std::bitset<TOT_SECTORS> scanner;
634
635 for (unsigned i = FIRST_FILE_SECTOR; i < (FIRST_FILE_SECTOR + blocks); i++) {
636 scanner[ i ] = true;
637 }
638
639 for (unsigned i = FIRST_FILE_SECTOR; i <= (TOT_SECTORS - blocks); i++) {
640 std::bitset<TOT_SECTORS> tmp_map(alloc_map & scanner);
641 if (tmp_map.none()) {
642 first_s = i;
643 return true;
644 }
645 scanner <<= 1;
646 }
647
648 return false;
649 }
650
alloc_new_file(unsigned blocks,dir_entry_t * & entry)651 bool tape_image_t::alloc_new_file(unsigned blocks , dir_entry_t*& entry)
652 {
653 if (dir.size() >= MAX_DIR_ENTRIES) {
654 return false;
655 }
656
657 dir_entry_t new_entry;
658
659 memset(&new_entry.filename[ 0 ] , 0 , sizeof(new_entry.filename));
660 new_entry.protection = 0;
661 new_entry.filetype = 0;
662 new_entry.n_recs = 0;
663 new_entry.wpr = 0;
664
665 unsigned first_s;
666
667 if (!find_free_block(blocks, first_s)) {
668 return false;
669 }
670
671 new_entry.filepos = (uint16_t)first_s;
672 new_entry.n_sects = blocks;
673
674 dir.push_back(new_entry);
675 entry = &dir.back();
676 dirty = true;
677
678 return true;
679 }
680
split_filename_and_ext(const char * filename,char * fname,char * ext)681 void tape_image_t::split_filename_and_ext(const char *filename , char *fname , char *ext)
682 {
683 char *fname_fence = fname + CHARS_PER_FNAME;
684
685 while (fname < fname_fence && *filename != '\0' && *filename != '.') {
686 *fname++ = *filename++;
687 }
688
689 *fname = '\0';
690
691 while (*filename != '\0' && *filename != '.') {
692 filename++;
693 }
694
695 if (*filename == '\0') {
696 *ext = '\0';
697 } else {
698 filename++;
699 strncpy(ext , filename , CHARS_PER_EXT);
700 ext[ CHARS_PER_EXT ] = '\0';
701 }
702 }
703
wipe_sector(tape_word_t * s)704 void tape_image_t::wipe_sector(tape_word_t *s)
705 {
706 for (unsigned i = 0; i < WORDS_PER_SECTOR; i++) {
707 s[ i ] = PAD_WORD;
708 }
709 }
710
tape_word_to_bytes(tape_word_t w,uint8_t & bh,uint8_t & bl)711 void tape_image_t::tape_word_to_bytes(tape_word_t w , uint8_t& bh , uint8_t& bl)
712 {
713 bh = (uint8_t)(w >> 8);
714 bl = (uint8_t)(w & 0xff);
715 }
716
bytes_to_tape_word(uint8_t bh,uint8_t bl,tape_word_t & w)717 void tape_image_t::bytes_to_tape_word(uint8_t bh , uint8_t bl , tape_word_t& w)
718 {
719 w = ((tape_word_t)bh << 8) | ((tape_word_t)bl);
720 }
721
dump_dir_sect(const tape_word_t * dir_sect,unsigned dir_sect_idx)722 void tape_image_t::dump_dir_sect(const tape_word_t *dir_sect , unsigned dir_sect_idx)
723 {
724 for (unsigned i = 0; i < DIR_COPIES; i++) {
725 set_sector(FIRST_DIR_SECTOR + i * SECTORS_PER_DIR + dir_sect_idx, dir_sect);
726 }
727 }
728
fill_and_dump_dir_sect(tape_word_t * dir_sect,unsigned & idx,unsigned & dir_sect_idx,tape_word_t w)729 void tape_image_t::fill_and_dump_dir_sect(tape_word_t *dir_sect , unsigned& idx , unsigned& dir_sect_idx , tape_word_t w)
730 {
731 // Dump sector once it's full
732 if (idx >= WORDS_PER_SECTOR) {
733 dump_dir_sect(dir_sect, dir_sect_idx);
734 wipe_sector(dir_sect);
735 idx = 0;
736 dir_sect_idx++;
737 }
738 dir_sect[ idx++ ] = w;
739 }
740
encode_dir(void)741 void tape_image_t::encode_dir(void)
742 {
743 tape_word_t dir_sect[ WORDS_PER_SECTOR ];
744
745 wipe_sector(dir_sect);
746
747 unsigned idx = 0;
748 unsigned dir_sect_idx = 0;
749
750 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_WORD_0);
751 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_WORD_1);
752
753 for (const dir_entry_t& ent : dir) {
754 tape_word_t tmp;
755
756 // Filename
757 bytes_to_tape_word(ent.filename[ 0 ], ent.filename[ 1 ], tmp);
758 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp);
759 bytes_to_tape_word(ent.filename[ 2 ], ent.filename[ 3 ], tmp);
760 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp);
761 bytes_to_tape_word(ent.filename[ 4 ], ent.filename[ 5 ], tmp);
762 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp);
763 // Protection, file type & file position
764 tmp = ((tape_word_t)ent.filetype << 10) | (tape_word_t)ent.filepos;
765 if (ent.protection) {
766 tmp |= 0x8000;
767 }
768 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, tmp);
769 // File size (# of records)
770 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, ent.n_recs);
771 // Words per record
772 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, ent.wpr);
773 }
774
775 // Terminator
776 fill_and_dump_dir_sect(dir_sect, idx, dir_sect_idx, DIR_LAST_WORD);
777
778 // Dump last partial sector
779 dump_dir_sect(dir_sect, dir_sect_idx);
780
781 // Unset unused sectors
782 for (unsigned i = dir_sect_idx + 1; i < SECTORS_PER_DIR; i++) {
783 for (unsigned j = 0; j < DIR_COPIES; j++) {
784 unset_sector(FIRST_DIR_SECTOR + i + j * SECTORS_PER_DIR);
785 }
786 }
787 }
788
read_sector_words(unsigned & sect_no,unsigned & sect_idx,size_t word_no,tape_word_t * out) const789 bool tape_image_t::read_sector_words(unsigned& sect_no , unsigned& sect_idx , size_t word_no , tape_word_t *out) const
790 {
791 while (word_no > 0) {
792 if (sect_idx >= WORDS_PER_SECTOR) {
793 sect_idx = 0;
794 sect_no++;
795 if (sect_no >= TOT_SECTORS || !alloc_map[ sect_no ]) {
796 return false;
797 }
798 }
799 *out++ = img[ sect_no ][ sect_idx ];
800 sect_idx++;
801 word_no--;
802 }
803
804 return true;
805 }
806
filename_char_check(uint8_t c)807 bool tape_image_t::filename_char_check(uint8_t c)
808 {
809 // Colons and quotation marks are forbidden in file names
810 return 0x20 < c && c < 0x7f && c != ':' && c != '"';
811 }
812
filename_check(const uint8_t * filename)813 bool tape_image_t::filename_check(const uint8_t *filename)
814 {
815 bool ended = false;
816
817 for (unsigned i = 0; i < 6; i++) {
818 uint8_t c = *filename++;
819
820 if (ended) {
821 if (c != 0) {
822 return false;
823 }
824 } else if (c == 0) {
825 ended = true;
826 } else if (!filename_char_check(c)) {
827 return false;
828 }
829 }
830
831 return true;
832 }
833
834 static const char *const filetype_attrs[] = {
835 BKUP_ATTR_STR, // 0
836 DATA_ATTR_STR, // 1
837 PROG_ATTR_STR, // 2
838 KEYS_ATTR_STR, // 3
839 BDAT_ATTR_STR, // 4
840 ALL_ATTR_STR, // 5
841 BPRG_ATTR_STR, // 6
842 OPRM_ATTR_STR // 7
843 };
844
get_filename_and_ext(const dir_entry_t & ent,bool inc_ext,char * out,bool & qmark)845 void tape_image_t::get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark)
846 {
847 strncpy(&out[ 0 ] , (const char*)&ent.filename[ 0 ] , CHARS_PER_FNAME);
848 out[ CHARS_PER_FNAME ] = '\0';
849
850 // Decode filetype
851 uint8_t type_low = ent.filetype & 7;
852 uint8_t type_hi = (ent.filetype >> 3) & 3;
853
854 const char *filetype_str = filetype_attrs[ type_low ];
855
856 // Same logic used by hp9845b to add a question mark next to filetype
857 qmark = (type_low == DATA_FILETYPE && type_hi == 3) ||
858 (type_low != DATA_FILETYPE && type_hi != 2);
859
860 if (inc_ext) {
861 strcat(out , ".");
862 strcat(out , filetype_str);
863 }
864 }
865
decode_dir(void)866 bool tape_image_t::decode_dir(void)
867 {
868 unsigned sect_no = FIRST_DIR_SECTOR - 1;
869 unsigned sect_idx = SECTOR_LEN;
870
871 dir.clear();
872
873 tape_word_t tmp;
874
875 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
876 return false;
877 }
878 if (tmp != DIR_WORD_0) {
879 return false;
880 }
881 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
882 return false;
883 }
884 if (tmp != DIR_WORD_1) {
885 return false;
886 }
887
888 // This is to check for overlapping files
889 std::bitset<TOT_SECTORS> sect_in_use;
890
891 while (1) {
892 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
893 return false;
894 }
895 if (tmp == DIR_LAST_WORD) {
896 // End of directory
897 break;
898 }
899
900 if (dir.size() >= MAX_DIR_ENTRIES) {
901 // Too many entries
902 return false;
903 }
904
905 dir_entry_t new_entry;
906
907 // Filename
908 tape_word_to_bytes(tmp, new_entry.filename[ 0 ], new_entry.filename[ 1 ]);
909 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
910 return false;
911 }
912 tape_word_to_bytes(tmp, new_entry.filename[ 2 ], new_entry.filename[ 3 ]);
913 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
914 return false;
915 }
916 tape_word_to_bytes(tmp, new_entry.filename[ 4 ], new_entry.filename[ 5 ]);
917 if (!filename_check(new_entry.filename)) {
918 return false;
919 }
920
921 // Protection, file type & file position
922 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
923 return false;
924 }
925 new_entry.protection = (tmp & 0x8000) != 0;
926 new_entry.filetype = ((tmp >> 10) & 0x1f);
927 new_entry.filepos = tmp & 0x3ff;
928 if (new_entry.filepos < FIRST_FILE_SECTOR || new_entry.filepos >= TOT_SECTORS) {
929 return false;
930 }
931
932 // File size (# of records)
933 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
934 return false;
935 }
936 new_entry.n_recs = tmp;
937
938 // Words per record
939 if (!read_sector_words(sect_no, sect_idx, 1, &tmp)) {
940 return false;
941 }
942 new_entry.wpr = tmp;
943 if (new_entry.wpr < 1) {
944 return false;
945 }
946
947 new_entry.n_sects = ((unsigned)new_entry.wpr * new_entry.n_recs * 2 + SECTOR_LEN - 1) / SECTOR_LEN;
948 if (new_entry.n_sects < 1 || (new_entry.n_sects + new_entry.filepos) > TOT_SECTORS) {
949 return false;
950 }
951
952 for (unsigned i = new_entry.filepos; i < new_entry.n_sects + new_entry.filepos; i++) {
953 if (sect_in_use[ i ]) {
954 return false;
955 }
956 sect_in_use[ i ] = true;
957 }
958
959 dir.push_back(new_entry);
960 }
961
962 // Check for inconsistency between alloc_map & sect_in_use
963 for (unsigned i = 0; i < FIRST_FILE_SECTOR; i++) {
964 sect_in_use[ i ] = alloc_map[ i ];
965 }
966
967 std::bitset<TOT_SECTORS> tmp_map(~alloc_map & sect_in_use);
968 if (tmp_map.any()) {
969 // There is at least 1 sector that is in use by a file but it's empty/unallocated
970 return false;
971 }
972
973 alloc_map = sect_in_use;
974
975 return true;
976 }
977
get_tape_state(imgtool::image & img)978 static tape_state_t& get_tape_state(imgtool::image &img)
979 {
980 tape_state_t *ts = (tape_state_t*)img.extra_bytes();
981
982 return *ts;
983 }
984
get_tape_image(tape_state_t & ts)985 static tape_image_t& get_tape_image(tape_state_t& ts)
986 {
987 if (ts.img == nullptr) {
988 ts.img = new tape_image_t;
989 }
990
991 return *(ts.img);
992 }
993
994 /********************************************************************************
995 * Imgtool functions
996 ********************************************************************************/
hp9845_tape_open(imgtool::image & image,imgtool::stream::ptr && stream)997 static imgtoolerr_t hp9845_tape_open(imgtool::image &image, imgtool::stream::ptr &&stream)
998 {
999 tape_state_t& state = get_tape_state(image);
1000
1001 state.stream = stream.release();
1002
1003 tape_image_t& tape_image = get_tape_image(state);
1004
1005 imgtoolerr_t err = tape_image.load_from_file(state.stream);
1006 if (err)
1007 return err;
1008
1009 return IMGTOOLERR_SUCCESS;
1010 }
1011
hp9845_tape_create(imgtool::image & image,imgtool::stream::ptr && stream,util::option_resolution * opts)1012 static imgtoolerr_t hp9845_tape_create(imgtool::image &image, imgtool::stream::ptr &&stream, util::option_resolution *opts)
1013 {
1014 tape_state_t& state = get_tape_state(image);
1015
1016 state.stream = stream.release();
1017
1018 tape_image_t& tape_image = get_tape_image(state);
1019
1020 tape_image.format_img();
1021
1022 return IMGTOOLERR_SUCCESS;
1023 }
1024
hp9845_tape_close(imgtool::image & image)1025 static void hp9845_tape_close(imgtool::image &image)
1026 {
1027 tape_state_t& state = get_tape_state(image);
1028 tape_image_t& tape_image = get_tape_image(state);
1029
1030 if (tape_image.is_dirty()) {
1031 (void)tape_image.save_to_file(state.stream);
1032 }
1033
1034 delete state.stream;
1035
1036 // Free tape_image
1037 delete &tape_image;
1038 }
1039
hp9845_tape_begin_enum(imgtool::directory & enumeration,const char * path)1040 static imgtoolerr_t hp9845_tape_begin_enum (imgtool::directory &enumeration, const char *path)
1041 {
1042 dir_state_t *ds = (dir_state_t*)enumeration.extra_bytes();
1043
1044 ds->dir_idx = 0;
1045
1046 return IMGTOOLERR_SUCCESS;
1047 }
1048
hp9845_tape_next_enum(imgtool::directory & enumeration,imgtool_dirent & ent)1049 static imgtoolerr_t hp9845_tape_next_enum (imgtool::directory &enumeration, imgtool_dirent &ent)
1050 {
1051 tape_state_t& state = get_tape_state(enumeration.image());
1052 tape_image_t& tape_image = get_tape_image(state);
1053 dir_state_t *ds = (dir_state_t*)enumeration.extra_bytes();
1054
1055 const dir_entry_t *entry = nullptr;
1056
1057 if (!tape_image.get_dir_entry(ds->dir_idx, entry)) {
1058 ent.eof = 1;
1059 } else {
1060 ds->dir_idx++;
1061
1062 bool qmark;
1063
1064 tape_image_t::get_filename_and_ext(*entry, true, ent.filename, qmark);
1065
1066 // "filename" and "attr" fields try to look like the output of the "CAT" command
1067 snprintf(ent.attr , sizeof(ent.attr) , "%c %02x%c %4u %4u %3u" , entry->protection ? '*' : ' ' , entry->filetype , qmark ? '?' : ' ' , entry->n_recs , entry->wpr * 2 , entry->filepos);
1068
1069 ent.filesize = entry->n_sects * SECTOR_LEN;
1070 }
1071 return IMGTOOLERR_SUCCESS;
1072 }
1073
hp9845_tape_free_space(imgtool::partition & partition,uint64_t * size)1074 static imgtoolerr_t hp9845_tape_free_space(imgtool::partition &partition, uint64_t *size)
1075 {
1076 tape_state_t& state = get_tape_state(partition.image());
1077 tape_image_t& tape_image = get_tape_image(state);
1078
1079 *size = tape_image.free_sectors() * SECTOR_LEN;
1080
1081 return IMGTOOLERR_SUCCESS;
1082 }
1083
hp9845_tape_read_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & destf)1084 static imgtoolerr_t hp9845_tape_read_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &destf)
1085 {
1086 tape_state_t& state = get_tape_state(partition.image());
1087 tape_image_t& tape_image = get_tape_image(state);
1088
1089 unsigned idx;
1090
1091 if (!tape_image.find_file(filename , false , idx)) {
1092 return IMGTOOLERR_FILENOTFOUND;
1093 }
1094
1095 const dir_entry_t *ent = nullptr;
1096
1097 tape_image.get_dir_entry(idx, ent);
1098
1099 unsigned sect_no = ent->filepos;
1100 unsigned n_sects = ent->n_sects;
1101 tape_word_t buff_w[ WORDS_PER_SECTOR ];
1102 uint8_t buff_b[ SECTOR_LEN ];
1103
1104 while (n_sects--) {
1105 if (!tape_image.get_sector(sect_no++, &buff_w[ 0 ])) {
1106 return IMGTOOLERR_READERROR;
1107 }
1108 for (unsigned i = 0; i < WORDS_PER_SECTOR; i++) {
1109 tape_image_t::tape_word_to_bytes(buff_w[ i ], buff_b[ i * 2 ], buff_b[ i * 2 + 1 ]);
1110 }
1111
1112 destf.write(buff_b , SECTOR_LEN);
1113 }
1114
1115 return IMGTOOLERR_SUCCESS;
1116 }
1117
hp9845_tape_write_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & sourcef,util::option_resolution * opts)1118 static imgtoolerr_t hp9845_tape_write_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *opts)
1119 {
1120 tape_state_t& state = get_tape_state(partition.image());
1121 tape_image_t& tape_image = get_tape_image(state);
1122
1123 unsigned idx;
1124
1125 if (tape_image.find_file(filename , true , idx)) {
1126 // When overwriting a file, delete its old version first
1127 tape_image.delete_dir_entry(idx);
1128 }
1129
1130 unsigned blocks = (unsigned)((sourcef.size() + SECTOR_LEN - 1) / SECTOR_LEN);
1131
1132 if (!blocks) {
1133 fprintf(stderr , "Null file, not writing..\n");
1134 return IMGTOOLERR_SUCCESS;
1135 }
1136
1137 dir_entry_t *ent = nullptr;
1138
1139 if (!tape_image.alloc_new_file(blocks, ent)) {
1140 return IMGTOOLERR_NOSPACE;
1141 }
1142
1143 unsigned s_no = ent->filepos;
1144
1145 char fname[ CHARS_PER_FNAME + 1 ];
1146 char ext[ CHARS_PER_EXT + 1 ];
1147
1148 tape_image_t::split_filename_and_ext(filename, fname, ext);
1149
1150 strncpy((char*)&ent->filename[ 0 ] , fname , CHARS_PER_FNAME);
1151
1152 for (unsigned i = 0; i < blocks; i++) {
1153 tape_word_t buff_w[ WORDS_PER_SECTOR ];
1154 uint8_t buff_b[ SECTOR_LEN ];
1155
1156 memset(&buff_b[ 0 ] , 0 , sizeof(buff_b));
1157
1158 if (sourcef.read(buff_b , SECTOR_LEN) != SECTOR_LEN && i != (blocks - 1)) {
1159 return IMGTOOLERR_READERROR;
1160 }
1161 for (unsigned j = 0; j < WORDS_PER_SECTOR; j++) {
1162 tape_image_t::bytes_to_tape_word(buff_b[ 2 * j ], buff_b[ 2 * j + 1 ], buff_w[ j ]);
1163 }
1164 tape_image.set_sector(s_no, buff_w);
1165 s_no++;
1166 }
1167
1168 int wpr = opts->lookup_int('W');
1169 if (wpr == 0) {
1170 wpr = WORDS_PER_SECTOR;
1171 } else if (wpr > (blocks * WORDS_PER_SECTOR)) {
1172 fprintf(stderr , "WPR value too large, using %u\n" , WORDS_PER_SECTOR);
1173 wpr = WORDS_PER_SECTOR;
1174 }
1175 ent->wpr = (uint16_t)wpr;
1176
1177 ent->n_recs = (uint16_t)((blocks * WORDS_PER_SECTOR) / wpr);
1178
1179 unsigned type_low;
1180
1181 if (opts->lookup_int('T') == 0) {
1182 // File type defaults to DATA if no extension is given or extension is invalid
1183 type_low = DATA_FILETYPE;
1184 for (unsigned i = 0; i < 8; i++) {
1185 if (strcmp(filetype_attrs[ i ] , ext) == 0) {
1186 type_low = i;
1187 break;
1188 }
1189 }
1190 } else {
1191 type_low = opts->lookup_int('T') - 1;
1192 }
1193
1194 // See tape_image_t::get_filename_and_ext for the logic behind file type
1195 if (type_low == DATA_FILETYPE) {
1196 ent->filetype = (uint8_t)type_low + (1U << 3);
1197 } else {
1198 ent->filetype = (uint8_t)type_low + (2U << 3);
1199 }
1200
1201 return IMGTOOLERR_SUCCESS;
1202 }
1203
hp9845_tape_delete_file(imgtool::partition & partition,const char * filename)1204 static imgtoolerr_t hp9845_tape_delete_file(imgtool::partition &partition, const char *filename)
1205 {
1206 tape_state_t& state = get_tape_state(partition.image());
1207 tape_image_t& tape_image = get_tape_image(state);
1208
1209 unsigned idx;
1210
1211 if (!tape_image.find_file(filename , true , idx)) {
1212 return IMGTOOLERR_FILENOTFOUND;
1213 }
1214
1215 tape_image.delete_dir_entry(idx);
1216
1217 return IMGTOOLERR_SUCCESS;
1218 }
1219
1220 #define HP9845_WRITEFILE_OPTSPEC "W[0]-65535;T[0]-8"
1221
1222 OPTION_GUIDE_START(hp9845_write_optguide)
1223 OPTION_INT('W' , "wpr" , "Words per record")
1224 OPTION_ENUM_START('T' , "ftype" , "File type")
1225 OPTION_ENUM(0 , "auto" , "Automatic (\"DATA\" or by extension)")
1226 OPTION_ENUM(1 , "U" , "BKUP")
1227 OPTION_ENUM(2 , "D" , "DATA")
1228 OPTION_ENUM(3 , "P" , "PROG")
1229 OPTION_ENUM(4 , "K" , "KEYS")
1230 OPTION_ENUM(5 , "T" , "BDAT")
1231 OPTION_ENUM(6 , "A" , "ALL")
1232 OPTION_ENUM(7 , "B" , "BPRG")
1233 OPTION_ENUM(8 , "O" , "OPRM")
1234 OPTION_ENUM_END
1235 OPTION_GUIDE_END
1236
hp9845_tape_get_info(const imgtool_class * imgclass,uint32_t state,union imgtoolinfo * info)1237 void hp9845_tape_get_info(const imgtool_class *imgclass, uint32_t state, union imgtoolinfo *info)
1238 {
1239 switch (state) {
1240 case IMGTOOLINFO_STR_NAME:
1241 strcpy(info->s = imgtool_temp_str(), "hp9845_tape");
1242 break;
1243
1244 case IMGTOOLINFO_STR_DESCRIPTION:
1245 strcpy(info->s = imgtool_temp_str(), "HP9845 tape image");
1246 break;
1247
1248 case IMGTOOLINFO_STR_FILE:
1249 strcpy(info->s = imgtool_temp_str(), __FILE__);
1250 break;
1251
1252 case IMGTOOLINFO_STR_FILE_EXTENSIONS:
1253 strcpy(info->s = imgtool_temp_str(), "hti");
1254 break;
1255
1256 case IMGTOOLINFO_INT_IMAGE_EXTRA_BYTES:
1257 info->i = sizeof(tape_state_t);
1258 break;
1259
1260 case IMGTOOLINFO_INT_DIRECTORY_EXTRA_BYTES:
1261 info->i = sizeof(dir_state_t);
1262 break;
1263
1264 case IMGTOOLINFO_PTR_OPEN:
1265 info->open = hp9845_tape_open;
1266 break;
1267
1268 case IMGTOOLINFO_PTR_CREATE:
1269 info->create = hp9845_tape_create;
1270 break;
1271
1272 case IMGTOOLINFO_PTR_CLOSE:
1273 info->close = hp9845_tape_close;
1274 break;
1275
1276 case IMGTOOLINFO_PTR_BEGIN_ENUM:
1277 info->begin_enum = hp9845_tape_begin_enum;
1278 break;
1279
1280 case IMGTOOLINFO_PTR_NEXT_ENUM:
1281 info->next_enum = hp9845_tape_next_enum;
1282 break;
1283
1284 case IMGTOOLINFO_PTR_FREE_SPACE:
1285 info->free_space = hp9845_tape_free_space;
1286 break;
1287
1288 case IMGTOOLINFO_PTR_READ_FILE:
1289 info->read_file = hp9845_tape_read_file;
1290 break;
1291
1292 case IMGTOOLINFO_PTR_WRITE_FILE:
1293 info->write_file = hp9845_tape_write_file;
1294 break;
1295
1296 case IMGTOOLINFO_PTR_DELETE_FILE:
1297 info->delete_file = hp9845_tape_delete_file;
1298 break;
1299
1300 case IMGTOOLINFO_PTR_WRITEFILE_OPTGUIDE:
1301 info->writefile_optguide = &hp9845_write_optguide;
1302 break;
1303
1304 case IMGTOOLINFO_STR_WRITEFILE_OPTSPEC:
1305 strcpy(info->s = imgtool_temp_str(), HP9845_WRITEFILE_OPTSPEC);
1306 break;
1307 }
1308 }
1309
1310 /********************************************************************************
1311 * Filter functions
1312 ********************************************************************************/
len_to_eor(imgtool::stream & inp)1313 static unsigned len_to_eor(imgtool::stream &inp)
1314 {
1315 return SECTOR_LEN - (unsigned)(inp.tell() % SECTOR_LEN);
1316 }
1317
get_record_part(imgtool::stream & inp,void * buf,unsigned len)1318 static bool get_record_part(imgtool::stream &inp , void *buf , unsigned len)
1319 {
1320 // Reading must never cross sector boundary
1321 if (len > len_to_eor(inp)) {
1322 return false;
1323 }
1324
1325 return inp.read(buf, len) == len;
1326 }
1327
dump_string(imgtool::stream & inp,imgtool::stream & out,unsigned len,bool add_eoln)1328 static bool dump_string(imgtool::stream &inp, imgtool::stream &out , unsigned len , bool add_eoln)
1329 {
1330 uint8_t tmp[ SECTOR_LEN ];
1331
1332 if (!get_record_part(inp , tmp , len)) {
1333 return false;
1334 }
1335
1336 // Sanitize string
1337 for (unsigned i = 0; i < len; i++) {
1338 if (!isascii(tmp[ i ]) || !isprint(tmp[ i ])) {
1339 tmp[ i ] = ' ';
1340 }
1341 }
1342
1343 out.write(tmp , len);
1344 if (add_eoln) {
1345 out.puts(EOLN);
1346 }
1347
1348 return true;
1349 }
1350
hp9845data_read_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & destf)1351 static imgtoolerr_t hp9845data_read_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &destf)
1352 {
1353 imgtool::stream::ptr inp_data;
1354 imgtoolerr_t res;
1355 uint8_t tmp[ 2 ];
1356
1357 inp_data = imgtool::stream::open_mem(NULL , 0);
1358 if (!inp_data)
1359 return IMGTOOLERR_OUTOFMEMORY;
1360
1361 res = hp9845_tape_read_file(partition , filename , fork , *inp_data);
1362 if (res != IMGTOOLERR_SUCCESS)
1363 return res;
1364
1365 inp_data->seek(0, SEEK_SET);
1366
1367 uint16_t rec_type;
1368 unsigned rec_len;
1369 unsigned tmp_len;
1370 unsigned accum_len = 0;
1371
1372 do {
1373 // Get record type
1374 if (!get_record_part(*inp_data , tmp , 2)) {
1375 return IMGTOOLERR_READERROR;
1376 }
1377 rec_type = (uint16_t)pick_integer_be(tmp , 0 , 2);
1378 switch (rec_type) {
1379 case REC_TYPE_EOR:
1380 // End of record: just skip it
1381 break;
1382
1383 case REC_TYPE_FULLSTR:
1384 // A string in a single piece
1385 case REC_TYPE_1STSTR:
1386 // First piece of a split string
1387 case REC_TYPE_MIDSTR:
1388 // Mid piece(s) of a split string
1389 case REC_TYPE_ENDSTR:
1390 // Closing piece of a split string
1391 if (((rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) && accum_len > 0) ||
1392 ((rec_type == REC_TYPE_MIDSTR || rec_type == REC_TYPE_ENDSTR) && accum_len == 0)) {
1393 fputs("Wrong sequence of string pieces\n" , stderr);
1394 return IMGTOOLERR_CORRUPTFILE;
1395 }
1396
1397 if (!get_record_part(*inp_data , tmp , 2)) {
1398 return IMGTOOLERR_READERROR;
1399 }
1400 tmp_len = (unsigned)pick_integer_be(tmp , 0 , 2);
1401
1402 if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) {
1403 accum_len = tmp_len;
1404 } else if (tmp_len != accum_len) {
1405 fputs("Wrong length of string piece\n" , stderr);
1406 return IMGTOOLERR_CORRUPTFILE;
1407 }
1408
1409 if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR) {
1410 rec_len = accum_len;
1411 } else {
1412 rec_len = std::min(accum_len , len_to_eor(*inp_data));
1413 }
1414 if (!dump_string(*inp_data , destf , rec_len , rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR)) {
1415 return IMGTOOLERR_READERROR;
1416 }
1417 if (rec_len & 1) {
1418 // Keep length of string pieces even
1419 get_record_part(*inp_data , tmp , 1);
1420 }
1421 accum_len -= rec_len;
1422 break;
1423
1424 case REC_TYPE_EOF:
1425 // End of file
1426 break;
1427
1428 default:
1429 fprintf(stderr , "Unknown record type (%04x)\n" , rec_type);
1430 return IMGTOOLERR_CORRUPTFILE;
1431 }
1432 } while (rec_type != REC_TYPE_EOF);
1433
1434 return IMGTOOLERR_SUCCESS;
1435 }
1436
split_string_n_dump(const char * s,imgtool::stream & dest)1437 static bool split_string_n_dump(const char *s , imgtool::stream &dest)
1438 {
1439 unsigned s_len = strlen(s);
1440 uint16_t rec_type = REC_TYPE_1STSTR;
1441 uint8_t tmp[ 4 ];
1442 bool at_least_one = false;
1443
1444 while (1) {
1445 unsigned free_len = len_to_eor(dest);
1446 if (free_len <= 4) {
1447 // Not enough free space at end of current record: fill with EORs
1448 place_integer_be(tmp , 0 , 2 , REC_TYPE_EOR);
1449 while (free_len) {
1450 if (dest.write(tmp , 2) != 2) {
1451 return false;
1452 }
1453 free_len -= 2;
1454 }
1455 } else {
1456 unsigned s_part_len = std::min(free_len - 4 , s_len);
1457 if (s_part_len == s_len) {
1458 // Free space to EOR enough for what's left of string
1459 break;
1460 }
1461 place_integer_be(tmp , 0 , 2 , rec_type);
1462 place_integer_be(tmp , 2 , 2 , s_len);
1463 if (dest.write(tmp , 4) != 4 ||
1464 dest.write(s, s_part_len) != s_part_len) {
1465 return false;
1466 }
1467 rec_type = REC_TYPE_MIDSTR;
1468 s_len -= s_part_len;
1469 s += s_part_len;
1470 at_least_one = true;
1471 }
1472 }
1473
1474 place_integer_be(tmp , 0 , 2 , at_least_one ? REC_TYPE_ENDSTR : REC_TYPE_FULLSTR);
1475 place_integer_be(tmp , 2 , 2 , s_len);
1476 if (dest.write(tmp , 4) != 4 ||
1477 dest.write(s , s_len) != s_len) {
1478 return false;
1479 }
1480 if (s_len & 1) {
1481 tmp[ 0 ] = 0;
1482 if (dest.write(tmp , 1) != 1) {
1483 return false;
1484 }
1485 }
1486 return true;
1487 }
1488
hp9845data_write_file(imgtool::partition & partition,const char * filename,const char * fork,imgtool::stream & sourcef,util::option_resolution * opts)1489 static imgtoolerr_t hp9845data_write_file(imgtool::partition &partition, const char *filename, const char *fork, imgtool::stream &sourcef, util::option_resolution *opts)
1490 {
1491 imgtool::stream::ptr out_data;
1492
1493 out_data = imgtool::stream::open_mem(NULL , 0);
1494 if (!out_data)
1495 return IMGTOOLERR_OUTOFMEMORY;
1496
1497 while (1) {
1498 char line[ 256 ];
1499
1500 // Read input file one line at time
1501 if (sourcef.core_file()->gets(line , sizeof(line)) == nullptr) {
1502 // EOF
1503 break;
1504 }
1505 line[ sizeof(line) - 1 ] = '\0';
1506
1507 // Strip space and non-ASCII characters from the end of the line
1508 size_t line_len = strlen(line);
1509 char *p = &line[ line_len ];
1510 while (p != line) {
1511 char c = *(--p);
1512 if (isascii(c) && !isspace(c)) {
1513 break;
1514 }
1515 *p = '\0';
1516 }
1517
1518 // Ignore empty lines
1519 if (p == line) {
1520 continue;
1521 }
1522
1523 if (!split_string_n_dump(line, *out_data)) {
1524 return IMGTOOLERR_WRITEERROR;
1525 }
1526 }
1527
1528 // Fill free space of last record with EOFs
1529 unsigned free_len = len_to_eor(*out_data);
1530 uint8_t tmp[ 2 ];
1531 place_integer_be(tmp , 0 , 2 , REC_TYPE_EOF);
1532
1533 while (free_len) {
1534 if (out_data->write(tmp , 2 ) != 2) {
1535 return IMGTOOLERR_WRITEERROR;
1536 }
1537 free_len -= 2;
1538 }
1539
1540 out_data->seek(0 , SEEK_SET);
1541
1542 imgtoolerr_t res = hp9845_tape_write_file(partition, filename, fork, *out_data, opts);
1543
1544 return res;
1545 }
1546
filter_hp9845data_getinfo(uint32_t state,union filterinfo * info)1547 void filter_hp9845data_getinfo(uint32_t state, union filterinfo *info)
1548 {
1549 switch (state) {
1550 case FILTINFO_PTR_READFILE:
1551 info->read_file = hp9845data_read_file;
1552 break;
1553
1554 case FILTINFO_PTR_WRITEFILE:
1555 info->write_file = hp9845data_write_file;
1556 break;
1557
1558 case FILTINFO_STR_NAME:
1559 info->s = "9845data";
1560 break;
1561
1562 case FILTINFO_STR_HUMANNAME:
1563 info->s = "HP9845 text-only DATA files";
1564 break;
1565
1566 case FILTINFO_STR_EXTENSION:
1567 info->s = "txt";
1568 break;
1569 }
1570 }
1571