From d7ca7f0cf5ee2244dbdb250b241d876a1b466731 Mon Sep 17 00:00:00 2001 From: fulivi Date: Thu, 22 Sep 2016 15:27:50 +0200 Subject: [PATCH 1/2] imgtool, hp9845_tape format: implemented read filter for text-only DATA files (write op. still missing) --- src/tools/imgtool/filter.cpp | 1 + src/tools/imgtool/filter.h | 2 +- src/tools/imgtool/modules/hp9845_tape.cpp | 189 +++++++++++++++++++++- 3 files changed, 190 insertions(+), 2 deletions(-) diff --git a/src/tools/imgtool/filter.cpp b/src/tools/imgtool/filter.cpp index cd90026b6f7..24bfacb5afe 100644 --- a/src/tools/imgtool/filter.cpp +++ b/src/tools/imgtool/filter.cpp @@ -61,6 +61,7 @@ const filter_getinfoproc filters[] = filter_thombas128_getinfo, filter_thomcrypt_getinfo, filter_bml3bas_getinfo, + filter_hp9845data_getinfo, nullptr }; diff --git a/src/tools/imgtool/filter.h b/src/tools/imgtool/filter.h index 814d813d598..fa6e7611a72 100644 --- a/src/tools/imgtool/filter.h +++ b/src/tools/imgtool/filter.h @@ -61,6 +61,6 @@ extern void filter_thombas7_getinfo(UINT32 state, union filterinfo *info); extern void filter_thombas128_getinfo(UINT32 state, union filterinfo *info); extern void filter_thomcrypt_getinfo(UINT32 state, union filterinfo *info); extern void filter_bml3bas_getinfo(UINT32 state, union filterinfo *info); - +extern void filter_hp9845data_getinfo(UINT32 state, union filterinfo *info); #endif /* FILTER_H */ diff --git a/src/tools/imgtool/modules/hp9845_tape.cpp b/src/tools/imgtool/modules/hp9845_tape.cpp index 45ed0a7d332..359e5b40a76 100644 --- a/src/tools/imgtool/modules/hp9845_tape.cpp +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -21,8 +21,30 @@ as "TEST.DATA". * File type is deduced from host file extension when putting files into image. File type can be overridden by the "ftype" option. + This table summarizes the file types. + + ftype Fake Type of file BASIC commands + switch extension for this file type + ======================================================== + U BKUP "Database backup" + No idea + D DATA Generic record-based data file + SAVE/GET/PRINT#/READ# + P PROG Program file (tokenized BASIC & other data) + STORE/LOAD + K KEYS KEY file (definition of soft keys) + STORE KEY/LOAD KEY + T BDAT Binary data file + ? + A ALL Full dump of system state + STORE ALL/LOAD ALL + B BPRG Binary program file + STORE BIN/LOAD BIN + O OPRM Option ROM specific file + ? + * Files are always stored in units of 256-byte physical records. - * An important metadata of files is WPR: Words Per Records. This + * An important metadata of files is WPR: Words Per Record. This is a numeric value that sets the length of each logical record of the file (in units of 16-bit words). It defaults to 128 (i.e. logical and physical records are the same thing). It can be @@ -129,6 +151,17 @@ #define OPRM_FILETYPE 7 #define OPRM_ATTR_STR "OPRM" +// Record type identifiers +#define REC_TYPE_EOR 0x1e // End-of-record +#define REC_TYPE_FULLSTR 0x3c // A whole (un-split) string +#define REC_TYPE_EOF 0x3e // End-of-file +#define REC_TYPE_1STSTR 0x1c // First part of a string +#define REC_TYPE_MIDSTR 0x0c // Middle part(s) of a string +#define REC_TYPE_ENDSTR 0x2c // Last part of a string + +// End-of-lines +#define EOLN (CRLF == 1 ? "\r" : (CRLF == 2 ? "\n" : (CRLF == 3 ? "\r\n" : NULL))) + // Words stored on tape typedef UINT16 tape_word_t; @@ -1293,3 +1326,157 @@ void hp9845_tape_get_info(const imgtool_class *imgclass, UINT32 state, union img break; } } + +/******************************************************************************** + * Filter functions + ********************************************************************************/ +static unsigned len_to_eor(imgtool_stream *inp) +{ + return SECTOR_LEN - (unsigned)(stream_tell(inp) % SECTOR_LEN); +} + +static bool get_record_part(imgtool_stream *inp , void *buf , unsigned len) +{ + // Reading must never cross sector boundary + if (len > len_to_eor(inp)) { + return false; + } + + return stream_read(inp , buf , len) == len; +} + +static bool dump_string(imgtool_stream *inp, imgtool_stream *out , unsigned len , bool add_eoln) +{ + UINT8 tmp[ SECTOR_LEN ]; + + if (!get_record_part(inp , tmp , len)) { + return false; + } + + stream_write(out , tmp , len); + if (add_eoln) { + stream_puts(out , EOLN); + } + + return true; +} + +static imgtoolerr_t hp9845data_read_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *destf) +{ + imgtool_stream *inp_data; + imgtoolerr_t res; + UINT8 tmp[ 2 ]; + + inp_data = stream_open_mem(NULL , 0); + if (inp_data == nullptr) { + return IMGTOOLERR_OUTOFMEMORY; + } + + res = hp9845_tape_read_file(partition , filename , fork , inp_data); + if (res != IMGTOOLERR_SUCCESS) { + stream_close(inp_data); + return res; + } + + stream_seek(inp_data , 0 , SEEK_SET); + + UINT16 rec_type; + unsigned rec_len; + unsigned tmp_len; + unsigned accum_len = 0; + + do { + // Get record type + if (!get_record_part(inp_data , tmp , 2)) { + return IMGTOOLERR_READERROR; + } + rec_type = (UINT16)pick_integer_be(tmp , 0 , 2); + switch (rec_type) { + case REC_TYPE_EOR: + // End of record: just skip it + break; + + case REC_TYPE_FULLSTR: + // A string in a single piece + case REC_TYPE_1STSTR: + // First piece of a split string + case REC_TYPE_MIDSTR: + // Mid piece(s) of a split string + case REC_TYPE_ENDSTR: + // Closing piece of a split string + if (((rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) && accum_len > 0) || + ((rec_type == REC_TYPE_MIDSTR || rec_type == REC_TYPE_ENDSTR) && accum_len == 0)) { + fputs("Wrong sequence of string pieces\n" , stderr); + return IMGTOOLERR_CORRUPTFILE; + } + + if (!get_record_part(inp_data , tmp , 2)) { + return IMGTOOLERR_READERROR; + } + tmp_len = (unsigned)pick_integer_be(tmp , 0 , 2); + + if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_1STSTR) { + accum_len = tmp_len; + } else if (tmp_len != accum_len) { + fputs("Wrong length of string piece\n" , stderr); + return IMGTOOLERR_CORRUPTFILE; + } + + if (rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR) { + rec_len = accum_len; + } else { + rec_len = std::min(accum_len , len_to_eor(inp_data)); + } + if (!dump_string(inp_data , destf , rec_len , rec_type == REC_TYPE_FULLSTR || rec_type == REC_TYPE_ENDSTR)) { + return IMGTOOLERR_READERROR; + } + if (rec_len & 1) { + // Keep length of string pieces even + get_record_part(inp_data , tmp , 1); + } + accum_len -= rec_len; + break; + + case REC_TYPE_EOF: + // End of file + break; + + default: + fprintf(stderr , "Unknown record type (%04x)\n" , rec_type); + return IMGTOOLERR_CORRUPTFILE; + } + } while (rec_type != REC_TYPE_EOF); + + return IMGTOOLERR_SUCCESS; +} + +static imgtoolerr_t hp9845data_write_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *sourcef, util::option_resolution *opts) +{ + // TODO: + return IMGTOOLERR_UNIMPLEMENTED; +} + +void filter_hp9845data_getinfo(UINT32 state, union filterinfo *info) +{ + switch (state) { + case FILTINFO_PTR_READFILE: + info->read_file = hp9845data_read_file; + break; + + case FILTINFO_PTR_WRITEFILE: + info->write_file = hp9845data_write_file; + break; + + case FILTINFO_STR_NAME: + info->s = "9845data"; + break; + + case FILTINFO_STR_HUMANNAME: + info->s = "HP9845 text-only DATA files"; + break; + + case FILTINFO_STR_EXTENSION: + info->s = "txt"; + break; + } +} From dedc07dc754ebc623080af338d8e6295cea3327c Mon Sep 17 00:00:00 2001 From: fulivi Date: Fri, 23 Sep 2016 15:57:56 +0200 Subject: [PATCH 2/2] imgtool, hp9845_tape format: implemented read & write ops in 9845data filter --- src/tools/imgtool/modules/hp9845_tape.cpp | 142 +++++++++++++++++++++- src/tools/imgtool/stream.cpp | 3 +- 2 files changed, 141 insertions(+), 4 deletions(-) diff --git a/src/tools/imgtool/modules/hp9845_tape.cpp b/src/tools/imgtool/modules/hp9845_tape.cpp index 359e5b40a76..a082860bcb5 100644 --- a/src/tools/imgtool/modules/hp9845_tape.cpp +++ b/src/tools/imgtool/modules/hp9845_tape.cpp @@ -55,6 +55,9 @@ is no single block of free records big enough to hold the file even though the total amount of free space would be sufficient. + Notes on commands + ================= + **** dir command **** The format of the "attr" part of file listing is as follows: %c '*' if file has the protection bit set, else ' ' @@ -68,6 +71,7 @@ A file can be extracted from an image with or without an explicit extension. If an extension is given, it must match the one corresponding to file type. + The "9845data" filter can be used on DATA files (see below). **** getall command **** Files are extracted with their "fake" extension. @@ -79,10 +83,30 @@ match any known type, file type is set to "DATA". WPR can be set through the "wpr" option. If it's 0 (the default), WPR is set to 128. + The "9845data" filter can be used on DATA files (see below). **** del command **** File extension is ignored, if present. + "9845data" filter + ================= + + This filter can be applied to DATA files whose content is made + of strings only. BASIC programs that are saved with "SAVE" command + have this format. + This filter translates a DATA file into a standard ASCII text file + and viceversa. + Keep in mind that this translation is NOT lossless because all + non-ASCII & non printable characters are substituted with spaces. + This kind of characters must be removed because they may confuse + the line-by-line reading of file when translating in the opposite + direction. + The 9845 system has the capability to insert formatting characters + directly in the text strings to be displayed on screen. These + characters set things like inverse video or underline. + Turning a DATA file into a text file through this filter removes + these special characters. + *********************************************************************/ #include @@ -1353,6 +1377,13 @@ static bool dump_string(imgtool_stream *inp, imgtool_stream *out , unsigned len return false; } + // Sanitize string + for (unsigned i = 0; i < len; i++) { + if (!isascii(tmp[ i ]) || !isprint(tmp[ i ])) { + tmp[ i ] = ' '; + } + } + stream_write(out , tmp , len); if (add_eoln) { stream_puts(out , EOLN); @@ -1450,10 +1481,117 @@ static imgtoolerr_t hp9845data_read_file(imgtool_partition *partition, const cha return IMGTOOLERR_SUCCESS; } +static bool split_string_n_dump(const char *s , imgtool_stream *dest) +{ + unsigned s_len = strlen(s); + UINT16 rec_type = REC_TYPE_1STSTR; + UINT8 tmp[ 4 ]; + bool at_least_one = false; + + while (1) { + unsigned free_len = len_to_eor(dest); + if (free_len <= 4) { + // Not enough free space at end of current record: fill with EORs + place_integer_be(tmp , 0 , 2 , REC_TYPE_EOR); + while (free_len) { + if (stream_write(dest , tmp , 2) != 2) { + return false; + } + free_len -= 2; + } + } else { + unsigned s_part_len = std::min(free_len - 4 , s_len); + if (s_part_len == s_len) { + // Free space to EOR enough for what's left of string + break; + } + place_integer_be(tmp , 0 , 2 , rec_type); + place_integer_be(tmp , 2 , 2 , s_len); + if (stream_write(dest , tmp , 4) != 4 || + stream_write(dest , s , s_part_len) != s_part_len) { + return false; + } + rec_type = REC_TYPE_MIDSTR; + s_len -= s_part_len; + s += s_part_len; + at_least_one = true; + } + } + + place_integer_be(tmp , 0 , 2 , at_least_one ? REC_TYPE_ENDSTR : REC_TYPE_FULLSTR); + place_integer_be(tmp , 2 , 2 , s_len); + if (stream_write(dest , tmp , 4) != 4 || + stream_write(dest , s , s_len) != s_len) { + return false; + } + if (s_len & 1) { + tmp[ 0 ] = 0; + if (stream_write(dest , tmp , 1) != 1) { + return false; + } + } + return true; +} + static imgtoolerr_t hp9845data_write_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *sourcef, util::option_resolution *opts) { - // TODO: - return IMGTOOLERR_UNIMPLEMENTED; + imgtool_stream *out_data; + + out_data = stream_open_mem(NULL , 0); + if (out_data == nullptr) { + return IMGTOOLERR_OUTOFMEMORY; + } + + while (1) { + char line[ 256 ]; + + // Read input file one line at time + if (stream_core_file(sourcef)->gets(line , sizeof(line)) == nullptr) { + // EOF + break; + } + line[ sizeof(line) - 1 ] = '\0'; + + // Strip space and non-ASCII characters from the end of the line + size_t line_len = strlen(line); + char *p = &line[ line_len ]; + while (p != line) { + char c = *(--p); + if (isascii(c) && !isspace(c)) { + break; + } + *p = '\0'; + } + + // Ignore empty lines + if (p == line) { + continue; + } + + if (!split_string_n_dump(line , out_data)) { + return IMGTOOLERR_WRITEERROR; + } + } + + // Fill free space of last record with EOFs + unsigned free_len = len_to_eor(out_data); + UINT8 tmp[ 2 ]; + place_integer_be(tmp , 0 , 2 , REC_TYPE_EOF); + + while (free_len) { + if (stream_write(out_data , tmp , 2 ) != 2) { + return IMGTOOLERR_WRITEERROR; + } + free_len -= 2; + } + + stream_seek(out_data , 0 , SEEK_SET); + + imgtoolerr_t res = hp9845_tape_write_file(partition , filename , fork , out_data , opts); + + stream_close(out_data); + + return res; } void filter_hp9845data_getinfo(UINT32 state, union filterinfo *info) diff --git a/src/tools/imgtool/stream.cpp b/src/tools/imgtool/stream.cpp index 8aea81031e5..4462164eb01 100644 --- a/src/tools/imgtool/stream.cpp +++ b/src/tools/imgtool/stream.cpp @@ -266,8 +266,7 @@ UINT32 stream_write(imgtool_stream *s, const void *buf, UINT32 sz) if (s->filesize < s->position + sz) { /* try to expand the buffer */ - if (s->buffer) free(s->buffer); - new_buffer = malloc(s->position + sz); + new_buffer = realloc(s->buffer , s->position + sz); if (new_buffer) { s->buffer = (UINT8*)new_buffer;