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(&sector[ 0 ] , 3);
496 			// Sector payload
497 			if (in_use) {
498 				memcpy(&sector[ 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(&sector[ 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