mirror of
https://github.com/holub/mame
synced 2025-06-29 23:48:56 +03:00
imgtool, hp9845_tape format: write&del done, brief info added at the
start of file.
This commit is contained in:
parent
e39c05c165
commit
e25e1b84b0
@ -747,10 +747,10 @@ static void listoptions(const util::option_guide &opt_guide, const char *opt_spe
|
||||
fprintf(stdout, "Option Allowed values Description\n");
|
||||
fprintf(stdout, "---------------- ------------------------------ -----------\n");
|
||||
|
||||
std::stringstream description_buffer;
|
||||
for (auto iter = resolution.entries_begin(); iter != resolution.entries_end(); iter++)
|
||||
{
|
||||
const util::option_resolution::entry &entry = *iter;
|
||||
std::stringstream description_buffer;
|
||||
|
||||
std::string opt_name = string_format("--%s", entry.identifier());
|
||||
const char *opt_desc = entry.display_name();
|
||||
|
@ -6,6 +6,61 @@
|
||||
|
||||
HP-9845 tape format
|
||||
|
||||
This imgtool module manipulates HTI files. These are image files
|
||||
of the DC-100 tape cartridges that are simulated for the HP9845B
|
||||
driver.
|
||||
HP9845 filesystem for tapes has the following features:
|
||||
* File names are 1 to 6 characters long.
|
||||
* Case is significant in file names.
|
||||
* There is no file "extension", file type is encoded separately
|
||||
in file metadata.
|
||||
* There are 8 file types. File type is encoded in 5 bits.
|
||||
Only 8 out of the 32 possible values are valid.
|
||||
* This module handles the file type as a fake file extension.
|
||||
For example, a file named "TEST" having DATA type is get/put/shown
|
||||
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.
|
||||
* Files are always stored in units of 256-byte physical records.
|
||||
* An important metadata of files is WPR: Words Per Records. 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
|
||||
set by the "wpr" option when putting files into the image.
|
||||
* There is no fragmentation map in the filesystem: each file
|
||||
always occupy a contiguous set of physical records. This fact
|
||||
could prevent the putting of a file into an image when there
|
||||
is no single block of free records big enough to hold the file
|
||||
even though the total amount of free space would be sufficient.
|
||||
|
||||
**** dir command ****
|
||||
The format of the "attr" part of file listing is as follows:
|
||||
%c '*' if file has the protection bit set, else ' '
|
||||
%02x Hexadecimal value of file type (00-1f)
|
||||
%c '?' if file type is not valid, else ' '
|
||||
%4u Number of logical records
|
||||
%4u WPR * 2 (i.e. bytes per logical record)
|
||||
%3u First physical record of file
|
||||
|
||||
**** get command ****
|
||||
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.
|
||||
|
||||
**** getall command ****
|
||||
Files are extracted with their "fake" extension.
|
||||
|
||||
**** put command ****
|
||||
File type can be specified explicitly through the "ftype" option.
|
||||
If this option is "auto" (the default), type is deduced from file
|
||||
extension, if present. When extension is not given or it doesn't
|
||||
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.
|
||||
|
||||
**** del command ****
|
||||
File extension is ignored, if present.
|
||||
|
||||
*********************************************************************/
|
||||
#include <bitset>
|
||||
|
||||
@ -26,7 +81,8 @@
|
||||
#define MAX_DIR_ENTRIES 42 // And the answer is.... the maximum number of entries in the directory!
|
||||
#define DIR_COPIES 2 // Count of directory copies
|
||||
#define CHARS_PER_FNAME 6 // Maximum characters in a filename
|
||||
#define CHARS_PER_FNAME_EXT (CHARS_PER_FNAME + 1 + 4) // Characters in filename + extension
|
||||
#define CHARS_PER_EXT 4 // Characters in file extension. Extension is encoded as file type, it's not actually stored in directory as characters.
|
||||
#define CHARS_PER_FNAME_EXT (CHARS_PER_FNAME + 1 + CHARS_PER_EXT) // Characters in filename + extension
|
||||
#define PAD_WORD 0xffff // Word value for padding
|
||||
#define FIRST_FILE_SECTOR (FIRST_DIR_SECTOR + SECTORS_PER_DIR * DIR_COPIES) // First file sector
|
||||
#define MAGIC 0x5441434f // Magic value at start of image file: "TACO"
|
||||
@ -112,13 +168,20 @@ public:
|
||||
void unset_sector(unsigned s_no);
|
||||
bool get_sector(unsigned s_no , tape_word_t *s_data);
|
||||
|
||||
bool get_dir_entry(unsigned idx , dir_entry_t& entry);
|
||||
bool get_dir_entry(const char* filename , dir_entry_t& entry);
|
||||
bool get_dir_entry(unsigned idx , const dir_entry_t*& entry) const;
|
||||
bool find_file(const char *filename , bool ignore_ext , unsigned& idx) const;
|
||||
|
||||
void delete_dir_entry(unsigned idx);
|
||||
|
||||
bool find_free_block(unsigned blocks , unsigned& first_s) const;
|
||||
|
||||
bool alloc_new_file(unsigned blocks , dir_entry_t*& entry);
|
||||
|
||||
static void tape_word_to_bytes(tape_word_t w , UINT8& bh , UINT8& bl);
|
||||
static void bytes_to_tape_word(UINT8 bh , UINT8 bl , tape_word_t& w);
|
||||
|
||||
static void get_filename_and_ext(const dir_entry_t& ent , char *out , bool& qmark);
|
||||
static void get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark);
|
||||
static void split_filename_and_ext(const char *filename , char *fname , char *ext);
|
||||
|
||||
private:
|
||||
bool dirty;
|
||||
@ -463,7 +526,14 @@ imgtoolerr_t tape_image_t::save_to_file(imgtool_stream *stream)
|
||||
|
||||
unsigned tape_image_t::free_sectors(void) const
|
||||
{
|
||||
return TOT_SECTORS - alloc_map.count();
|
||||
std::bitset<TOT_SECTORS> tmp(alloc_map);
|
||||
|
||||
// Reserve sectors that cannot be allocated to files
|
||||
for (unsigned i = 0; i < FIRST_FILE_SECTOR; i++) {
|
||||
tmp[ i ] = true;
|
||||
}
|
||||
|
||||
return TOT_SECTORS - tmp.count();
|
||||
}
|
||||
|
||||
void tape_image_t::set_sector(unsigned s_no , const tape_word_t *s_data)
|
||||
@ -493,32 +563,135 @@ bool tape_image_t::get_sector(unsigned s_no , tape_word_t *s_data)
|
||||
}
|
||||
}
|
||||
|
||||
bool tape_image_t::get_dir_entry(unsigned idx , dir_entry_t& entry)
|
||||
bool tape_image_t::get_dir_entry(unsigned idx , const dir_entry_t*& entry) const
|
||||
{
|
||||
if (idx >= dir.size()) {
|
||||
return false;
|
||||
} else {
|
||||
entry = dir[ idx ];
|
||||
entry = &dir[ idx ];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool tape_image_t::get_dir_entry(const char* filename , dir_entry_t& entry)
|
||||
bool tape_image_t::find_file(const char *filename , bool ignore_ext , unsigned& idx) const
|
||||
{
|
||||
for (const dir_entry_t& ent : dir) {
|
||||
char fname[ CHARS_PER_FNAME_EXT + 1 ];
|
||||
char ext[ CHARS_PER_EXT + 1 ];
|
||||
|
||||
split_filename_and_ext(filename, fname, ext);
|
||||
|
||||
bool has_ext = !ignore_ext && *ext != '\0';
|
||||
|
||||
if (has_ext) {
|
||||
strcat(fname , ".");
|
||||
strcat(fname , ext);
|
||||
}
|
||||
|
||||
for (auto i = dir.cbegin(); i < dir.cend(); i++) {
|
||||
char full_fname[ CHARS_PER_FNAME_EXT + 1 ];
|
||||
bool qmark;
|
||||
|
||||
get_filename_and_ext(ent, full_fname, qmark);
|
||||
get_filename_and_ext(*i, has_ext, full_fname, qmark);
|
||||
|
||||
if (strcmp(filename , full_fname) == 0) {
|
||||
entry = ent;
|
||||
if (strcmp(fname , full_fname) == 0) {
|
||||
idx = i - dir.cbegin();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void tape_image_t::delete_dir_entry(unsigned idx)
|
||||
{
|
||||
const dir_entry_t& ent = dir[ idx ];
|
||||
|
||||
// Release all sectors of file
|
||||
for (unsigned i = ent.filepos; i < ent.filepos + ent.n_sects; i++) {
|
||||
unset_sector(i);
|
||||
}
|
||||
|
||||
dir.erase(dir.begin() + idx);
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
bool tape_image_t::find_free_block(unsigned blocks , unsigned& first_s) const
|
||||
{
|
||||
if (blocks >= (TOT_SECTORS - FIRST_FILE_SECTOR)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::bitset<TOT_SECTORS> scanner;
|
||||
|
||||
for (unsigned i = FIRST_FILE_SECTOR; i < (FIRST_FILE_SECTOR + blocks); i++) {
|
||||
scanner[ i ] = true;
|
||||
}
|
||||
|
||||
for (unsigned i = FIRST_FILE_SECTOR; i <= (TOT_SECTORS - blocks); i++) {
|
||||
std::bitset<TOT_SECTORS> tmp_map(alloc_map & scanner);
|
||||
if (tmp_map.none()) {
|
||||
first_s = i;
|
||||
return true;
|
||||
}
|
||||
scanner <<= 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool tape_image_t::alloc_new_file(unsigned blocks , dir_entry_t*& entry)
|
||||
{
|
||||
if (dir.size() >= MAX_DIR_ENTRIES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dir_entry_t new_entry;
|
||||
|
||||
memset(&new_entry.filename[ 0 ] , 0 , sizeof(new_entry.filename));
|
||||
new_entry.protection = 0;
|
||||
new_entry.filetype = 0;
|
||||
new_entry.n_recs = 0;
|
||||
new_entry.wpr = 0;
|
||||
|
||||
unsigned first_s;
|
||||
|
||||
if (!find_free_block(blocks, first_s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
new_entry.filepos = (UINT16)first_s;
|
||||
new_entry.n_sects = blocks;
|
||||
|
||||
dir.push_back(new_entry);
|
||||
entry = &dir.back();
|
||||
dirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void tape_image_t::split_filename_and_ext(const char *filename , char *fname , char *ext)
|
||||
{
|
||||
char *fname_fence = fname + CHARS_PER_FNAME;
|
||||
|
||||
while (fname < fname_fence && *filename != '\0' && *filename != '.') {
|
||||
*fname++ = *filename++;
|
||||
}
|
||||
|
||||
*fname = '\0';
|
||||
|
||||
while (*filename != '\0' && *filename != '.') {
|
||||
filename++;
|
||||
}
|
||||
|
||||
if (*filename == '\0') {
|
||||
*ext = '\0';
|
||||
} else {
|
||||
filename++;
|
||||
strncpy(ext , filename , CHARS_PER_EXT);
|
||||
ext[ CHARS_PER_EXT ] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void tape_image_t::wipe_sector(tape_word_t *s)
|
||||
{
|
||||
for (unsigned i = 0; i < WORDS_PER_SECTOR; i++) {
|
||||
@ -660,7 +833,7 @@ static const char *const filetype_attrs[] = {
|
||||
OPRM_ATTR_STR // 7
|
||||
};
|
||||
|
||||
void tape_image_t::get_filename_and_ext(const dir_entry_t& ent , char *out , bool& qmark)
|
||||
void tape_image_t::get_filename_and_ext(const dir_entry_t& ent , bool inc_ext , char *out , bool& qmark)
|
||||
{
|
||||
strncpy(&out[ 0 ] , (const char*)&ent.filename[ 0 ] , CHARS_PER_FNAME);
|
||||
out[ CHARS_PER_FNAME ] = '\0';
|
||||
@ -675,8 +848,10 @@ void tape_image_t::get_filename_and_ext(const dir_entry_t& ent , char *out , boo
|
||||
qmark = (type_low == DATA_FILETYPE && type_hi == 3) ||
|
||||
(type_low != DATA_FILETYPE && type_hi != 2);
|
||||
|
||||
strcat(out , ".");
|
||||
strcat(out , filetype_str);
|
||||
if (inc_ext) {
|
||||
strcat(out , ".");
|
||||
strcat(out , filetype_str);
|
||||
}
|
||||
}
|
||||
|
||||
bool tape_image_t::decode_dir(void)
|
||||
@ -864,7 +1039,7 @@ static imgtoolerr_t hp9845_tape_next_enum (imgtool_directory *enumeration, imgto
|
||||
tape_image_t& tape_image = get_tape_image(state);
|
||||
dir_state_t *ds = (dir_state_t*)imgtool_directory_extrabytes(enumeration);
|
||||
|
||||
dir_entry_t entry;
|
||||
const dir_entry_t *entry = nullptr;
|
||||
|
||||
if (!tape_image.get_dir_entry(ds->dir_idx, entry)) {
|
||||
ent->eof = 1;
|
||||
@ -873,12 +1048,12 @@ static imgtoolerr_t hp9845_tape_next_enum (imgtool_directory *enumeration, imgto
|
||||
|
||||
bool qmark;
|
||||
|
||||
tape_image_t::get_filename_and_ext(entry, ent->filename, qmark);
|
||||
tape_image_t::get_filename_and_ext(*entry, true, ent->filename, qmark);
|
||||
|
||||
// "filename" and "attr" fields try to look like the output of the "CAT" command
|
||||
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);
|
||||
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);
|
||||
|
||||
ent->filesize = entry.n_sects * SECTOR_LEN;
|
||||
ent->filesize = entry->n_sects * SECTOR_LEN;
|
||||
}
|
||||
return IMGTOOLERR_SUCCESS;
|
||||
}
|
||||
@ -898,17 +1073,22 @@ static imgtoolerr_t hp9845_tape_read_file(imgtool_partition *partition, const ch
|
||||
tape_state_t& state = get_tape_state(imgtool_partition_image(partition));
|
||||
tape_image_t& tape_image = get_tape_image(state);
|
||||
|
||||
dir_entry_t ent;
|
||||
unsigned idx;
|
||||
|
||||
if (!tape_image.get_dir_entry(filename, ent)) {
|
||||
if (!tape_image.find_file(filename , false , idx)) {
|
||||
return IMGTOOLERR_FILENOTFOUND;
|
||||
}
|
||||
|
||||
unsigned sect_no = ent.filepos;
|
||||
const dir_entry_t *ent = nullptr;
|
||||
|
||||
tape_image.get_dir_entry(idx, ent);
|
||||
|
||||
unsigned sect_no = ent->filepos;
|
||||
unsigned n_sects = ent->n_sects;
|
||||
tape_word_t buff_w[ WORDS_PER_SECTOR ];
|
||||
UINT8 buff_b[ SECTOR_LEN ];
|
||||
|
||||
while (ent.n_sects--) {
|
||||
while (n_sects--) {
|
||||
if (!tape_image.get_sector(sect_no++, &buff_w[ 0 ])) {
|
||||
return IMGTOOLERR_READERROR;
|
||||
}
|
||||
@ -922,6 +1102,125 @@ static imgtoolerr_t hp9845_tape_read_file(imgtool_partition *partition, const ch
|
||||
return IMGTOOLERR_SUCCESS;
|
||||
}
|
||||
|
||||
static imgtoolerr_t hp9845_tape_write_file(imgtool_partition *partition, const char *filename, const char *fork, imgtool_stream *sourcef, util::option_resolution *opts)
|
||||
{
|
||||
tape_state_t& state = get_tape_state(imgtool_partition_image(partition));
|
||||
tape_image_t& tape_image = get_tape_image(state);
|
||||
|
||||
unsigned idx;
|
||||
|
||||
if (tape_image.find_file(filename , true , idx)) {
|
||||
// When overwriting a file, delete its old version first
|
||||
tape_image.delete_dir_entry(idx);
|
||||
}
|
||||
|
||||
unsigned blocks = (unsigned)((stream_size(sourcef) + SECTOR_LEN - 1) / SECTOR_LEN);
|
||||
|
||||
if (!blocks) {
|
||||
fprintf(stderr , "Null file, not writing..\n");
|
||||
return IMGTOOLERR_SUCCESS;
|
||||
}
|
||||
|
||||
dir_entry_t *ent = nullptr;
|
||||
|
||||
if (!tape_image.alloc_new_file(blocks, ent)) {
|
||||
return IMGTOOLERR_NOSPACE;
|
||||
}
|
||||
|
||||
unsigned s_no = ent->filepos;
|
||||
|
||||
char fname[ CHARS_PER_FNAME + 1 ];
|
||||
char ext[ CHARS_PER_EXT + 1 ];
|
||||
|
||||
tape_image_t::split_filename_and_ext(filename, fname, ext);
|
||||
|
||||
strncpy((char*)&ent->filename[ 0 ] , fname , CHARS_PER_FNAME);
|
||||
|
||||
for (unsigned i = 0; i < blocks; i++) {
|
||||
tape_word_t buff_w[ WORDS_PER_SECTOR ];
|
||||
UINT8 buff_b[ SECTOR_LEN ];
|
||||
|
||||
memset(&buff_b[ 0 ] , 0 , sizeof(buff_b));
|
||||
|
||||
if (stream_read(sourcef , buff_b , SECTOR_LEN) != SECTOR_LEN && i != (blocks - 1)) {
|
||||
return IMGTOOLERR_READERROR;
|
||||
}
|
||||
for (unsigned j = 0; j < WORDS_PER_SECTOR; j++) {
|
||||
tape_image_t::bytes_to_tape_word(buff_b[ 2 * j ], buff_b[ 2 * j + 1 ], buff_w[ j ]);
|
||||
}
|
||||
tape_image.set_sector(s_no, buff_w);
|
||||
s_no++;
|
||||
}
|
||||
|
||||
int wpr = opts->lookup_int('W');
|
||||
if (wpr == 0) {
|
||||
wpr = WORDS_PER_SECTOR;
|
||||
} else if (wpr > (blocks * WORDS_PER_SECTOR)) {
|
||||
fprintf(stderr , "WPR value too large, using %u\n" , WORDS_PER_SECTOR);
|
||||
wpr = WORDS_PER_SECTOR;
|
||||
}
|
||||
ent->wpr = (UINT16)wpr;
|
||||
|
||||
ent->n_recs = (UINT16)((blocks * WORDS_PER_SECTOR) / wpr);
|
||||
|
||||
unsigned type_low;
|
||||
|
||||
if (opts->lookup_int('T') == 0) {
|
||||
// File type defaults to DATA if no extension is given or extension is invalid
|
||||
type_low = DATA_FILETYPE;
|
||||
for (unsigned i = 0; i < 8; i++) {
|
||||
if (strcmp(filetype_attrs[ i ] , ext) == 0) {
|
||||
type_low = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type_low = opts->lookup_int('T') - 1;
|
||||
}
|
||||
|
||||
// See tape_image_t::get_filename_and_ext for the logic behind file type
|
||||
if (type_low == DATA_FILETYPE) {
|
||||
ent->filetype = (UINT8)type_low + (1U << 3);
|
||||
} else {
|
||||
ent->filetype = (UINT8)type_low + (2U << 3);
|
||||
}
|
||||
|
||||
return IMGTOOLERR_SUCCESS;
|
||||
}
|
||||
|
||||
static imgtoolerr_t hp9845_tape_delete_file(imgtool_partition *partition, const char *filename)
|
||||
{
|
||||
tape_state_t& state = get_tape_state(imgtool_partition_image(partition));
|
||||
tape_image_t& tape_image = get_tape_image(state);
|
||||
|
||||
unsigned idx;
|
||||
|
||||
if (!tape_image.find_file(filename , true , idx)) {
|
||||
return IMGTOOLERR_FILENOTFOUND;
|
||||
}
|
||||
|
||||
tape_image.delete_dir_entry(idx);
|
||||
|
||||
return IMGTOOLERR_SUCCESS;
|
||||
}
|
||||
|
||||
#define HP9845_WRITEFILE_OPTSPEC "W[0]-65535;T[0]-8"
|
||||
|
||||
OPTION_GUIDE_START(hp9845_write_optguide)
|
||||
OPTION_INT('W' , "wpr" , "Words per record")
|
||||
OPTION_ENUM_START('T' , "ftype" , "File type")
|
||||
OPTION_ENUM(0 , "auto" , "Automatic (\"DATA\" or by extension)")
|
||||
OPTION_ENUM(1 , "U" , "BKUP")
|
||||
OPTION_ENUM(2 , "D" , "DATA")
|
||||
OPTION_ENUM(3 , "P" , "PROG")
|
||||
OPTION_ENUM(4 , "K" , "KEYS")
|
||||
OPTION_ENUM(5 , "T" , "BDAT")
|
||||
OPTION_ENUM(6 , "A" , "ALL")
|
||||
OPTION_ENUM(7 , "B" , "BPRG")
|
||||
OPTION_ENUM(8 , "O" , "OPRM")
|
||||
OPTION_ENUM_END
|
||||
OPTION_GUIDE_END
|
||||
|
||||
void hp9845_tape_get_info(const imgtool_class *imgclass, UINT32 state, union imgtoolinfo *info)
|
||||
{
|
||||
switch (state) {
|
||||
@ -976,5 +1275,21 @@ void hp9845_tape_get_info(const imgtool_class *imgclass, UINT32 state, union img
|
||||
case IMGTOOLINFO_PTR_READ_FILE:
|
||||
info->read_file = hp9845_tape_read_file;
|
||||
break;
|
||||
|
||||
case IMGTOOLINFO_PTR_WRITE_FILE:
|
||||
info->write_file = hp9845_tape_write_file;
|
||||
break;
|
||||
|
||||
case IMGTOOLINFO_PTR_DELETE_FILE:
|
||||
info->delete_file = hp9845_tape_delete_file;
|
||||
break;
|
||||
|
||||
case IMGTOOLINFO_PTR_WRITEFILE_OPTGUIDE:
|
||||
info->writefile_optguide = &hp9845_write_optguide;
|
||||
break;
|
||||
|
||||
case IMGTOOLINFO_STR_WRITEFILE_OPTSPEC:
|
||||
strcpy(info->s = imgtool_temp_str(), HP9845_WRITEFILE_OPTSPEC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user