diff options
author | 2024-02-10 06:10:19 +1100 | |
---|---|---|
committer | 2024-02-10 06:21:05 +1100 | |
commit | 5731492874513cfcd204aae6735cf70db4d6b6e0 (patch) | |
tree | a6c3bcb208780a759a2681eabea9a4c0cde51172 | |
parent | d88e12714307ff5a9dc02d0e849ed84d3c4b4f93 (diff) |
tools/chdman.cpp: Fixed numerous issues, including:
Support input start/size options for createdvd.
Fixed not reporting an error on unrecognised command line options.
Fixed --fix/-f option for verify command not working.
Report an error when conflicting options are supplied (e.g. hard disk
template and C/H/S geometry, or input start offset in both bytes and
hunks). Previously the results would be unpredictable.
Detect more invalid combinations of options, and detect when output unit
size or hunk size doesn't match parent.
Changed order of processing options for createhd so using a template
cannot not inadvertently result in an invalid combination of sector size
and hunk size.
Don't require an explicit unit size for createraw if an output parent
CHD file is supplied.
Fixed an object leak in createcd.
-rw-r--r-- | docs/source/tools/chdman.rst | 109 | ||||
-rw-r--r-- | src/lib/util/chd.cpp | 32 | ||||
-rw-r--r-- | src/lib/util/chd.h | 8 | ||||
-rw-r--r-- | src/tools/chdman.cpp | 1032 |
4 files changed, 609 insertions, 572 deletions
diff --git a/docs/source/tools/chdman.rst b/docs/source/tools/chdman.rst index f23a21cddf5..04612642764 100644 --- a/docs/source/tools/chdman.rst +++ b/docs/source/tools/chdman.rst @@ -27,6 +27,25 @@ used by multiple commands: most commands that operate on CHD format input files. This option must be used if the input file is a *delta CHD*, storing only the hunks that differ from its parent CHD, +--inputstartbyte <offset> / -isb <offset> + Specify the offset to the data in the input file in bytes. This is useful + for creating CHD format files from input files that contain a header before + the start of the data, or for extracting partial content from a CHD format + file. May not be specified in combination with the + ``--inputstarthunk``/``-ish`` option. +--inputstarthunk <offset> / -ish <offset> + Specify the offset to the data in the input file in hunks. May not be + specified in combination with the ``--inputstartbyte``/``-isb`` option. +--inputbytes <length> / -ib <length> + Specify the amount of input data to use in bytes, starting from the offset + to the data in the input file. This is useful for creating CHD format files + from input files that contain additional content after the data, or for + extracting partial content from a CHD format file. May not be specified in + combination with the ``--inputhunks``/``-ih`` option. +--inputhunks <length> / -ih <length> + Specify the amount of input data to use in hunks, starting from the offset + to the data in the input file. May not be specified in combination with the + ``--inputbytes``/``-ib`` option. --output <file> / -o <file> Specify the output file name. This option is required for commands that produce output files. The output file formats supported depend on the @@ -86,13 +105,19 @@ verify ~~~~~~ Verify the integrity of a CHD format file. The input file must be a read-only -CHD format file (the integrity of writable CHD files cannot be verified). +CHD format file (the integrity of writable CHD files cannot be verified). Note +that this command modifies its input file if the ``--fix``/``-f`` option is +specified. Common options supported: * ``--input <file>`` / ``-i <file>`` (required) * ``--inputparent <chdfile>`` / ``-ip <chdfile>`` +Additional options: + +* ``--fix`` / ``-f`` + createraw ~~~~~~~~~ @@ -101,20 +126,34 @@ Create a CHD format file from a raw media image. Common options supported: * ``--input <file>`` / ``-i <file>`` (required) +* ``--inputstartbyte <offset>`` / ``-isb <offset>`` +* ``--inputstarthunk <offset>`` / ``-ish <offset>`` +* ``--inputbytes <length>`` / ``-ib <length>`` +* ``--inputhunks <length>`` / ``-ih <length>`` * ``--output <file>`` / ``-o <file>`` (required) * ``--outputparent <chdfile>`` / ``-op <chdfile>`` * ``--compression none|<type>[,<type>]...`` / ``-c none|<type>[,<type>]...`` -* ``--hunksize <bytes>`` / ``-hs <bytes>`` (required) +* ``--hunksize <bytes>`` / ``-hs <bytes>`` * ``--force`` / ``-f`` * ``--numprocessors <count>`` / ``-np <count>`` Additional options: -* ``--unitsize <bytes>`` / ``-us <bytes>`` (required) -* ``--inputstartbyte <offset>`` / ``-isb <offset>`` -* ``--inputstarthunk <offset>`` / ``-ish <offset>`` -* ``--inputbytes <length>`` / ``-ib <length>`` -* ``--inputhunks <length>`` / ``-ih <length>`` +--unitsize <bytes> / -us <bytes> (required) + The unit size for the output CHD file in bytes. This is the smallest unit + of data that can be addressed within the CHD file. It should match the + sector size or page size of the source media. The hunk size must be a whole + multiple of the unit size. The unit size must be specified if no parent CHD + file for the output is supplied. If a parent CHD file for the output is + supplied, the unit size must match the unit size of the parent CHD file. + +If the ``--hunksize`` or ``-hs`` option is not supplied, the default will be: + +* The hunk size of the parent CHD file if a parent CHD file for the output is + supplied. +* The smallest whole multiple of the unit size not larger than 4 KiB if the unit + size is not larger than 4 KiB (4096 bytes). +* The unit size if it is larger than 4 KiB (4096 bytes). If the ``--compression`` or ``-c`` option is not supplied, it defaults to ``lzma,zlib,huff,flac``. @@ -126,7 +165,11 @@ Create a CHD format hard disk image file. Common options supported: -* ``--input <file>`` / ``-i <file>`` (required) +* ``--input <file>`` / ``-i <file>`` +* ``--inputstartbyte <offset>`` / ``-isb <offset>`` +* ``--inputstarthunk <offset>`` / ``-ish <offset>`` +* ``--inputbytes <length>`` / ``-ib <length>`` +* ``--inputhunks <length>`` / ``-ih <length>`` * ``--output <file>`` / ``-o <file>`` (required) * ``--outputparent <chdfile>`` / ``-op <chdfile>`` * ``--compression none|<type>[,<type>]...`` / ``-c none|<type>[,<type>]...`` @@ -140,13 +183,23 @@ Additional options: * ``--size <bytes>`` / ``-s <bytes>`` * ``--chs <cylinders>,<heads>,<sectors>`` / ``-chs <cylinders>,<heads>,<sectors>`` * ``--template <template>`` / ``-tp <template>`` -* ``--inputstartbyte <offset>`` / ``-isb <offset>`` -* ``--inputstarthunk <offset>`` / ``-ish <offset>`` -* ``--inputbytes <length>`` / ``-ib <length>`` -* ``--inputhunks <length>`` / ``-ih <length>`` + +Creates a blank (zero-filled) hard disk image if no input file is supplied. The +input start/length (``--inputstartbyte``/``-isb``, +``--inputstarthunk``/``-ish``, ``--inputbytes``/``-ib`` and +``--inputhunks``/``-ih`` options) cannot be used if no input file is supplied. + +If the ``--hunksize`` or ``-hs`` option is not supplied, the default will be: + +* The hunk size of the parent CHD file if a parent CHD file for the output is + supplied. +* The smallest whole multiple of the sector size not larger than 4 KiB if the + sector size is not larger than 4 KiB (4096 bytes). +* The sector size if it is larger than 4 KiB (4096 bytes). If the ``--compression`` or ``-c`` option is not supplied, it defaults to -``lzma,zlib,huff,flac``. +``lzma,zlib,huff,flac`` if an input file is supplied, or ``none`` if no input +file is supplied. createcd ~~~~~~~~ @@ -163,6 +216,10 @@ Common options supported: * ``--force`` / ``-f`` * ``--numprocessors <count>`` / ``-np <count>`` +If the ``--hunksize`` or ``-hs`` option is not supplied, the default will be +the hunk size of the parent CHD if a parent CHD file for the output is supplied, +or eight sectors per hunk (18,816 bytes) otherwise. + If the ``--compression`` or ``-c`` option is not supplied, it defaults to ``cdlz,cdzl,cdfl``. @@ -174,6 +231,10 @@ Create a CHD format DVD-ROM image file. Common options supported: * ``--input <file>`` / ``-i <file>`` (required) +* ``--inputstartbyte <offset>`` / ``-isb <offset>`` +* ``--inputstarthunk <offset>`` / ``-ish <offset>`` +* ``--inputbytes <length>`` / ``-ib <length>`` +* ``--inputhunks <length>`` / ``-ih <length>`` * ``--output <file>`` / ``-o <file>`` (required) * ``--outputparent <chdfile>`` / ``-op <chdfile>`` * ``--compression none|<type>[,<type>]...`` / ``-c none|<type>[,<type>]...`` @@ -181,6 +242,10 @@ Common options supported: * ``--force`` / ``-f`` * ``--numprocessors <count>`` / ``-np <count>`` +If the ``--hunksize`` or ``-hs`` option is not supplied, the default will be +the hunk size of the parent CHD if a parent CHD file for the output is supplied, +or two sectors per hunk (4096 bytes) otherwise. + If the ``--compression`` or ``-c`` option is not supplied, it defaults to ``lzma,zlib,huff,flac``. @@ -216,15 +281,12 @@ Common options supported: * ``--input <file>`` / ``-i <file>`` (required) * ``--inputparent <chdfile>`` / ``-ip <chdfile>`` -* ``--output <file>`` / ``-o <file>`` (required) -* ``--force`` / ``-f`` - -Additional options: - * ``--inputstartbyte <offset>`` / ``-isb <offset>`` * ``--inputstarthunk <offset>`` / ``-ish <offset>`` * ``--inputbytes <length>`` / ``-ib <length>`` * ``--inputhunks <length>`` / ``-ih <length>`` +* ``--output <file>`` / ``-o <file>`` (required) +* ``--force`` / ``-f`` extracthd ~~~~~~~~~ @@ -235,15 +297,12 @@ Common options supported: * ``--input <file>`` / ``-i <file>`` (required) * ``--inputparent <chdfile>`` / ``-ip <chdfile>`` -* ``--output <file>`` / ``-o <file>`` (required) -* ``--force`` / ``-f`` - -Additional options: - * ``--inputstartbyte <offset>`` / ``-isb <offset>`` * ``--inputstarthunk <offset>`` / ``-ish <offset>`` * ``--inputbytes <length>`` / ``-ib <length>`` * ``--inputhunks <length>`` / ``-ih <length>`` +* ``--output <file>`` / ``-o <file>`` (required) +* ``--force`` / ``-f`` extractcd ~~~~~~~~~ @@ -270,6 +329,10 @@ Common options supported: * ``--input <file>`` / ``-i <file>`` (required) * ``--inputparent <chdfile>`` / ``-ip <chdfile>`` +* ``--inputstartbyte <offset>`` / ``-isb <offset>`` +* ``--inputstarthunk <offset>`` / ``-ish <offset>`` +* ``--inputbytes <length>`` / ``-ib <length>`` +* ``--inputhunks <length>`` / ``-ih <length>`` * ``--output <file>`` / ``-o <file>`` (required) * ``--force`` / ``-f`` diff --git a/src/lib/util/chd.cpp b/src/lib/util/chd.cpp index 194884bfd9a..32624ac5bf9 100644 --- a/src/lib/util/chd.cpp +++ b/src/lib/util/chd.cpp @@ -585,7 +585,7 @@ void chd_file::set_parent_sha1(util::sha1_t parent) } /** - * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]) + * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]) * * @brief ------------------------------------------------- * create - create a new file with no parent using an existing opened file handle @@ -605,7 +605,7 @@ std::error_condition chd_file::create( uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, - chd_codec_type compression[4]) + const chd_codec_type (&compression)[4]) { // make sure we don't already have a file open if (m_file) @@ -626,7 +626,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent) + * @fn std::error_condition chd_file::create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent) * * @brief ------------------------------------------------- * create - create a new file with a parent using an existing opened file handle @@ -645,7 +645,7 @@ std::error_condition chd_file::create( util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, - chd_codec_type compression[4], + const chd_codec_type (&compression)[4], chd_file &parent) { // make sure we don't already have a file open @@ -667,7 +667,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]) + * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]) * * @brief ------------------------------------------------- * create - create a new file with no parent using a filename @@ -687,7 +687,7 @@ std::error_condition chd_file::create( uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, - chd_codec_type compression[4]) + const chd_codec_type (&compression)[4]) { // make sure we don't already have a file open if (m_file) @@ -712,7 +712,7 @@ std::error_condition chd_file::create( } /** - * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent) + * @fn std::error_condition chd_file::create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent) * * @brief ------------------------------------------------- * create - create a new file with a parent using a filename @@ -731,7 +731,7 @@ std::error_condition chd_file::create( std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, - chd_codec_type compression[4], + const chd_codec_type (&compression)[4], chd_file &parent) { // make sure we don't already have a file open @@ -1969,9 +1969,9 @@ std::error_condition chd_file::compress_v5_map() { uint8_t curcomp = m_rawmap[hunknum * 12 + 0]; - // promote self block references to more compact forms if (curcomp == COMPRESSION_SELF) { + // promote self block references to more compact forms uint32_t refhunk = get_u48be(&m_rawmap[hunknum * 12 + 4]); if (refhunk == last_self) curcomp = COMPRESSION_SELF_0; @@ -1981,10 +1981,9 @@ std::error_condition chd_file::compress_v5_map() max_self = std::max(max_self, refhunk); last_self = refhunk; } - - // promote parent block references to more compact forms else if (curcomp == COMPRESSION_PARENT) { + // promote parent block references to more compact forms uint32_t refunit = get_u48be(&m_rawmap[hunknum * 12 + 4]); if (refunit == mulu_32x32(hunknum, m_hunkbytes) / m_unitbytes) curcomp = COMPRESSION_PARENT_SELF; @@ -2921,9 +2920,9 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do osd_work_item_release(item.m_osd); item.m_osd = nullptr; - // for parent walking, just add to the hashmap if (m_walking_parent) { + // for parent walking, just add to the hashmap uint32_t uph = hunk_bytes() / unit_bytes(); uint32_t units = uph; if (item.m_hunknum == hunk_count() - 1 || !compressed()) @@ -2932,10 +2931,9 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do if (m_parent_map.find(item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1) == hashmap::NOT_FOUND) m_parent_map.add(item.m_hunknum * uph + unit, item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1); } - - // if we're uncompressed, use regular writes else if (!compressed()) { + // if we're uncompressed, use regular writes std::error_condition err = write_hunk(item.m_hunknum, item.m_data); if (err) return err; @@ -2947,10 +2945,10 @@ std::error_condition chd_file_compressor::compress_continue(double &progress, do if (codec == CHD_CODEC_NONE) m_total_out += m_hunkbytes; } - - // for compressing, process the result else do { + // for compressing, process the result + // first see if the hunk is in the parent or self maps uint64_t selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); if (selfhunk != hashmap::NOT_FOUND) @@ -3079,7 +3077,7 @@ void chd_file_compressor::async_walk_parent(work_item &item) void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid) { - auto *item = reinterpret_cast<work_item *>(param); + auto *const item = reinterpret_cast<work_item *>(param); item->m_compressor->async_compress_hunk(*item, threadid); return nullptr; } diff --git a/src/lib/util/chd.h b/src/lib/util/chd.h index 3e52cab4f9b..d5a813944e0 100644 --- a/src/lib/util/chd.h +++ b/src/lib/util/chd.h @@ -323,10 +323,10 @@ public: void set_parent_sha1(util::sha1_t parent); // file create - std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]); - std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, chd_codec_type compression[4]); - std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent); - std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, chd_codec_type compression[4], chd_file &parent); + std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]); + std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, uint32_t unitbytes, const chd_codec_type (&compression)[4]); + std::error_condition create(std::string_view filename, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent); + std::error_condition create(util::random_read_write::ptr &&file, uint64_t logicalbytes, uint32_t hunkbytes, const chd_codec_type (&compression)[4], chd_file &parent); // file open std::error_condition open(std::string_view filename, bool writeable = false, chd_file *parent = nullptr, const open_parent_func &open_parent = nullptr); diff --git a/src/tools/chdman.cpp b/src/tools/chdman.cpp index c613a968bad..c410f371a7e 100644 --- a/src/tools/chdman.cpp +++ b/src/tools/chdman.cpp @@ -23,7 +23,6 @@ #include <cassert> #include <cctype> -#include <cstdarg> #include <cstdio> #include <cstdlib> #include <cstring> @@ -31,8 +30,13 @@ #include <iostream> #include <limits> #include <memory> +#include <optional> #include <new> +#include <string_view> +#include <tuple> #include <unordered_map> +#include <utility> +#include <vector> using util::string_format; @@ -50,16 +54,20 @@ using util::string_format; #define I64FMT "ll" #endif +// hunk size limits +constexpr uint32_t HUNK_SIZE_MIN = 16; +constexpr uint32_t HUNK_SIZE_MAX = 1024 * 1024; + // default hard disk sector size -const uint32_t IDE_SECTOR_SIZE = 512; +constexpr uint32_t IDE_SECTOR_SIZE = 512; // temporary input buffer size -const uint32_t TEMP_BUFFER_SIZE = 32 * 1024 * 1024; +constexpr uint32_t TEMP_BUFFER_SIZE = 32 * 1024 * 1024; // modes -const int MODE_NORMAL = 0; -const int MODE_CUEBIN = 1; -const int MODE_GDI = 2; +constexpr int MODE_NORMAL = 0; +constexpr int MODE_CUEBIN = 1; +constexpr int MODE_GDI = 2; // command modifier #define REQUIRED "~" @@ -121,7 +129,9 @@ const int MODE_GDI = 2; typedef std::unordered_map<std::string, std::string *> parameters_map; -template <typename Format, typename... Params> [[noreturn]] static void report_error(int error, Format &&fmt, Params &&...args); +template <typename Format, typename... Params> +[[noreturn]] static void report_error(int error, Format &&fmt, Params &&...args); + static void do_info(parameters_map ¶ms); static void do_verify(parameters_map ¶ms); static void do_create_raw(parameters_map ¶ms); @@ -132,7 +142,6 @@ static void do_create_ld(parameters_map ¶ms); static void do_copy(parameters_map ¶ms); static void do_extract_raw(parameters_map ¶ms); static void do_extract_cd(parameters_map ¶ms); -static void do_extract_dvd(parameters_map ¶ms); static void do_extract_ld(parameters_map ¶ms); static void do_add_metadata(parameters_map ¶ms); static void do_del_metadata(parameters_map ¶ms); @@ -207,8 +216,7 @@ struct metadata_index_info class fatal_error : public std::exception { public: - fatal_error(int error) - : m_error(error) { } + fatal_error(int error) : m_error(error) { } int error() const { return m_error; } @@ -296,10 +304,12 @@ class chd_chdfile_compressor : public chd_file_compressor public: // construction/destruction chd_chdfile_compressor(chd_file &file, uint64_t offset = 0, uint64_t maxoffset = ~0) - : m_toc(nullptr), - m_file(file), - m_offset(offset), - m_maxoffset(std::min(maxoffset, file.logical_bytes())) { } + : m_toc(nullptr) + , m_file(file) + , m_offset(offset) + , m_maxoffset(std::min(maxoffset, file.logical_bytes())) + { + } // read interface virtual uint32_t read_data(void *dest, uint64_t offset, uint32_t length) @@ -367,9 +377,11 @@ class chd_cd_compressor : public chd_file_compressor public: // construction/destruction chd_cd_compressor(cdrom_file::toc &toc, cdrom_file::track_input_info &info) - : m_file(), - m_toc(toc), - m_info(info) { } + : m_file() + , m_toc(toc) + , m_info(info) + { + } ~chd_cd_compressor() { @@ -493,13 +505,15 @@ class chd_avi_compressor : public chd_file_compressor public: // construction/destruction chd_avi_compressor(avi_file &file, avi_info &info, uint32_t first_frame, uint32_t num_frames) - : m_file(file), - m_info(info), - m_bitmap(info.width, info.height * (info.interlaced ? 2 : 1)), - m_start_frame(first_frame), - m_frame_count(num_frames), - m_ldframedata(num_frames * VBI_PACKED_BYTES), - m_rawdata(info.bytes_per_frame) { } + : m_file(file) + , m_info(info) + , m_bitmap(info.width, info.height * (info.interlaced ? 2 : 1)) + , m_start_frame(first_frame) + , m_frame_count(num_frames) + , m_ldframedata(num_frames * VBI_PACKED_BYTES) + , m_rawdata(info.bytes_per_frame) + { + } // getters const std::vector<uint8_t> &ldframedata() const { return m_ldframedata; } @@ -515,6 +529,7 @@ public: int32_t start_frame = offset / m_info.bytes_per_frame; int32_t end_frame = (offset + length - 1) / m_info.bytes_per_frame; for (int32_t framenum = start_frame; framenum <= end_frame; framenum++) + { if (framenum < m_frame_count) { // determine effective frame number and first/last samples @@ -571,6 +586,7 @@ public: dest += bytes_to_copy; length_remaining -= bytes_to_copy; } + } return length; } @@ -580,11 +596,11 @@ private: avi_file & m_file; avi_info & m_info; bitmap_yuy16 m_bitmap; - uint32_t m_start_frame; - uint32_t m_frame_count; + uint32_t m_start_frame; + uint32_t m_frame_count; std::vector<int16_t> m_audio[8]; - std::vector<uint8_t> m_ldframedata; - std::vector<uint8_t> m_rawdata; + std::vector<uint8_t> m_ldframedata; + std::vector<uint8_t> m_rawdata; }; @@ -598,6 +614,7 @@ static clock_t lastprogress = 0; // default compressors +static const chd_codec_type s_no_compression[4] = { CHD_CODEC_NONE, CHD_CODEC_NONE, CHD_CODEC_NONE, CHD_CODEC_NONE }; static const chd_codec_type s_default_raw_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN, CHD_CODEC_FLAC }; static const chd_codec_type s_default_hd_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN, CHD_CODEC_FLAC }; static const chd_codec_type s_default_cd_compression[4] = { CHD_CODEC_CD_LZMA, CHD_CODEC_CD_ZLIB, CHD_CODEC_CD_FLAC }; @@ -623,7 +640,7 @@ static const option_description s_options[] = { OPTION_UNIT_SIZE, "us", true, " <bytes>: size of each unit, in bytes" }, { OPTION_COMPRESSION, "c", true, " <none|type1[,type2[,...]]>: which compression codecs to use (up to 4)" }, { OPTION_IDENT, "id", true, " <filename>: name of ident file to provide CHS information" }, - { OPTION_CHS, "chs", true, " <cylinders,heads,sectors>: specifies CHS values directly" }, + { OPTION_CHS, "chs", true, " <cylinders,heads,sectors>: specifies CHS geometry directly" }, { OPTION_SECTOR_SIZE, "ss", true, " <bytes>: size of each hard disk sector" }, { OPTION_TAG, "t", true, " <tag>: 4-character tag for metadata" }, { OPTION_INDEX, "ix", true, " <index>: indexed instance of this metadata tag" }, @@ -651,7 +668,8 @@ static const command_description s_commands[] = { COMMAND_VERIFY, do_verify, ": verifies a CHD's integrity", { REQUIRED OPTION_INPUT, - OPTION_INPUT_PARENT + OPTION_INPUT_PARENT, + OPTION_FIX } }, @@ -665,8 +683,8 @@ static const command_description s_commands[] = OPTION_INPUT_START_HUNK, OPTION_INPUT_LENGTH_BYTES, OPTION_INPUT_LENGTH_HUNKS, - REQUIRED OPTION_HUNK_SIZE, - REQUIRED OPTION_UNIT_SIZE, + OPTION_HUNK_SIZE, + OPTION_UNIT_SIZE, OPTION_COMPRESSION, OPTION_NUMPROCESSORS } @@ -710,6 +728,10 @@ static const command_description s_commands[] = OPTION_OUTPUT_PARENT, OPTION_OUTPUT_FORCE, REQUIRED OPTION_INPUT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS, OPTION_HUNK_SIZE, OPTION_COMPRESSION, OPTION_NUMPROCESSORS @@ -766,12 +788,16 @@ static const command_description s_commands[] = } }, - { COMMAND_EXTRACT_DVD, do_extract_dvd, ": extract DVD file from a CHD input file", + { COMMAND_EXTRACT_DVD, do_extract_raw, ": extract DVD file from a CHD input file", { REQUIRED OPTION_OUTPUT, OPTION_OUTPUT_FORCE, REQUIRED OPTION_INPUT, OPTION_INPUT_PARENT, + OPTION_INPUT_START_BYTE, + OPTION_INPUT_START_HUNK, + OPTION_INPUT_LENGTH_BYTES, + OPTION_INPUT_LENGTH_HUNKS } }, @@ -867,7 +893,8 @@ static const hd_template s_hd_templates[] = // report_error - report an error //------------------------------------------------- -template <typename Format, typename... Params> static void report_error(int error, Format &&fmt, Params &&...args) +template <typename Format, typename... Params> +static void report_error(int error, Format &&fmt, Params &&...args) { // output to stderr util::stream_format(std::cerr, std::forward<Format>(fmt), std::forward<Params>(args)...); @@ -883,7 +910,8 @@ template <typename Format, typename... Params> static void report_error(int erro // progress - generic progress callback //------------------------------------------------- -template <typename Format, typename... Params> static void progress(bool forceit, Format &&fmt, Params &&...args) +template <typename Format, typename... Params> +static void progress(bool forceit, Format &&fmt, Params &&...args) { // skip if it hasn't been long enough clock_t curtime = clock(); @@ -904,17 +932,17 @@ template <typename Format, typename... Params> static void progress(bool forceit static int print_help(const std::string &argv0, const char *error = nullptr) { // print the error message first - if (error != nullptr) - fprintf(stderr, "Error: %s\n\n", error); + if (error) + util::stream_format(std::cerr, "Error: %s\n\n", error); // print a summary of each command - printf("Usage:\n"); + util::stream_format(std::cout, "Usage:\n"); for (auto & desc : s_commands) { - printf(" %s %s%s\n", argv0.c_str(), desc.name, desc.description); + util::stream_format(std::cout, " %s %s%s\n", argv0, desc.name, desc.description); } - printf("\nFor help with any command, run:\n"); - printf(" %s %s <command>\n", argv0.c_str(), COMMAND_HELP); + util::stream_format(std::cout, "\nFor help with any command, run:\n"); + util::stream_format(std::cout, " %s %s <command>\n", argv0, COMMAND_HELP); return 1; } @@ -928,11 +956,11 @@ static int print_help(const std::string &argv0, const command_description &desc, { // print the error message first if (error != nullptr) - fprintf(stderr, "Error: %s\n\n", error); + util::stream_format(std::cerr, "Error: %s\n\n", error); // print usage for this command - printf("Usage:\n"); - printf(" %s %s [options], where valid options are:\n", argv0.c_str(), desc.name); + util::stream_format(std::cout, "Usage:\n"); + util::stream_format(std::cout, " %s %s [options], where valid options are:\n", argv0, desc.name); for (int valid = 0; valid < std::size(desc.valid_options); valid++) { // determine whether we are required @@ -948,10 +976,10 @@ static int print_help(const std::string &argv0, const command_description &desc, if (strcmp(option, s_option.name) == 0) { const option_description &odesc = s_option; - printf(" --%s", odesc.name); + util::stream_format(std::cout, " --%s", odesc.name); if (odesc.shortname != nullptr) - printf(", -%s", odesc.shortname); - printf("%s%s\n", odesc.description, required ? " (required)" : ""); + util::stream_format(std::cout, ", -%s", odesc.shortname); + util::stream_format(std::cout, "%s%s\n", odesc.description, required ? " (required)" : ""); } } return 1; @@ -1035,10 +1063,17 @@ uint64_t parse_number(const char *string) // compute a best guess CHS value set //------------------------------------------------- -static void guess_chs(std::string *filename, uint64_t filesize, int sectorsize, uint32_t &cylinders, uint32_t &heads, uint32_t §ors, uint32_t &bps) +static void guess_chs( + const std::string *filename, + uint64_t filesize, + int sectorsize, + uint32_t &cylinders, + uint32_t &heads, + uint32_t §ors, + uint32_t &bps) { // if this is a direct physical drive read, handle it specially - if (filename != nullptr && osd_get_physical_drive_geometry(filename->c_str(), &cylinders, &heads, §ors, &bps)) + if (filename && osd_get_physical_drive_geometry(filename->c_str(), &cylinders, &heads, §ors, &bps)) return; // if we have no length to work with, we can't guess @@ -1047,11 +1082,14 @@ static void guess_chs(std::string *filename, uint64_t filesize, int sectorsize, // now find a valid value for (uint32_t totalsectors = filesize / sectorsize; ; totalsectors++) + { for (uint32_t cursectors = 63; cursectors > 1; cursectors--) + { if (totalsectors % cursectors == 0) { uint32_t totalheads = totalsectors / cursectors; for (uint32_t curheads = 16; curheads > 1; curheads--) + { if (totalheads % curheads == 0) { cylinders = totalheads / curheads; @@ -1059,7 +1097,10 @@ static void guess_chs(std::string *filename, uint64_t filesize, int sectorsize, sectors = cursectors; return; } + } } + } + } } @@ -1095,38 +1136,80 @@ static void parse_input_chd_parameters(const parameters_map ¶ms, chd_file &i // parameters in a standard way //------------------------------------------------- -static void parse_input_start_end(const parameters_map ¶ms, uint64_t logical_size, uint32_t hunkbytes, uint32_t framebytes, uint64_t &input_start, uint64_t &input_end) +static std::pair<uint64_t, uint64_t> parse_input_start_end( + const parameters_map ¶ms, + uint64_t logical_size, + uint32_t hunkbytes, + uint32_t framebytes) { - // process start/end if we were provided an input CHD - input_start = 0; - input_end = logical_size; - // process input start - auto input_start_byte_str = params.find(OPTION_INPUT_START_BYTE); - auto input_start_hunk_str = params.find(OPTION_INPUT_START_HUNK); - auto input_start_frame_str = params.find(OPTION_INPUT_START_FRAME); + const auto input_start_byte_str = params.find(OPTION_INPUT_START_BYTE); + const auto input_start_hunk_str = params.find(OPTION_INPUT_START_HUNK); + const auto input_start_frame_str = params.find(OPTION_INPUT_START_FRAME); + uint64_t input_start = 0; if (input_start_byte_str != params.end()) + { + if (input_start_hunk_str != params.end()) + report_error(1, "Start offset cannot be specified in both bytes and hunks"); + if (input_start_frame_str != params.end()) + report_error(1, "Start offset cannot be specified in both bytes and frames"); input_start = parse_number(input_start_byte_str->second->c_str()); - if (input_start_hunk_str != params.end()) + } + else if (input_start_hunk_str != params.end()) + { + if (input_start_frame_str != params.end()) + report_error(1, "Start offset cannot be specified in both hunks and frames"); input_start = parse_number(input_start_hunk_str->second->c_str()) * hunkbytes; - if (input_start_frame_str != params.end()) + } + else if (input_start_frame_str != params.end()) + { input_start = parse_number(input_start_frame_str->second->c_str()) * framebytes; - if (input_start >= input_end) - report_error(1, "Input start offset greater than input file size"); + } // process input length - auto input_length_bytes_str = params.find(OPTION_INPUT_LENGTH_BYTES); + const auto input_length_bytes_str = params.find(OPTION_INPUT_LENGTH_BYTES); auto input_length_hunks_str = params.find(OPTION_INPUT_LENGTH_HUNKS); auto input_length_frames_str = params.find(OPTION_INPUT_LENGTH_FRAMES); - uint64_t input_length = input_end; + std::optional<uint64_t> input_length; if (input_length_bytes_str != params.end()) + { + if (input_length_hunks_str != params.end()) + report_error(1, "Length cannot be specified in both bytes and hunks"); + if (input_length_frames_str != params.end()) + report_error(1, "Length cannot be specified in both bytes and frames"); input_length = parse_number(input_length_bytes_str->second->c_str()); - if (input_length_hunks_str != params.end()) + } + else if (input_length_hunks_str != params.end()) + { + if (input_length_frames_str != params.end()) + report_error(1, "Length cannot be specified in both hunks and frames"); input_length = parse_number(input_length_hunks_str->second->c_str()) * hunkbytes; - if (input_length_frames_str != params.end()) + } + else if (input_length_frames_str != params.end()) + { input_length = parse_number(input_length_frames_str->second->c_str()) * framebytes; - if (input_start + input_length < input_end) - input_end = input_start + input_length; + } + + // check that offsets are within input + if (input_start >= logical_size) + report_error(1, "Input start offset is beyond end of input"); + if (input_length && ((input_start + *input_length) > logical_size)) + report_error(1, "Input length is larger than available input from start offset"); + return std::make_pair(input_start, input_length ? (input_start + *input_length) : logical_size); +} + +static std::tuple<uint64_t, uint64_t, uint64_t> parse_input_start_end( + const parameters_map ¶ms, + util::random_read &input_file, + uint32_t hunkbytes, + uint32_t framebytes) +{ + uint64_t input_size = 0; + const std::error_condition err = input_file.length(input_size); + if (err) + report_error(1, "Error getting size of input file: %s", err.message()); + const auto [input_start, input_end] = parse_input_start_end(params, input_size, hunkbytes, framebytes); + return std::make_tuple(input_size, input_start, input_end); } @@ -1136,7 +1219,7 @@ static void parse_input_start_end(const parameters_map ¶ms, uint64_t logical // unless --force is specified //------------------------------------------------- -static void check_existing_output_file(const parameters_map ¶ms, const char *filename) +static void check_existing_output_file(const parameters_map ¶ms, std::string_view filename) { if (params.find(OPTION_OUTPUT_FORCE) == params.end()) { @@ -1156,10 +1239,10 @@ static void check_existing_output_file(const parameters_map ¶ms, const char // standard set of output CHD parameters //------------------------------------------------- -static std::string *parse_output_chd_parameters(const parameters_map ¶ms, chd_file &output_parent_chd) +static const std::string *parse_output_chd_parameters(const parameters_map ¶ms, chd_file &output_parent_chd) { // process output parent file - auto output_chd_parent_str = params.find(OPTION_OUTPUT_PARENT); + const auto output_chd_parent_str = params.find(OPTION_OUTPUT_PARENT); if (output_chd_parent_str != params.end()) { std::error_condition err = output_parent_chd.open(*output_chd_parent_str->second); @@ -1168,10 +1251,11 @@ static std::string *parse_output_chd_parameters(const parameters_map ¶ms, ch } // process output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - check_existing_output_file(params, output_chd_str->second->c_str()); - return (output_chd_str != params.end()) ? output_chd_str->second : nullptr; + const auto output_chd_str = params.find(OPTION_OUTPUT); + if (output_chd_str == params.end()) + return nullptr; + check_existing_output_file(params, *output_chd_str->second); + return output_chd_str->second; } @@ -1180,17 +1264,32 @@ static std::string *parse_output_chd_parameters(const parameters_map ¶ms, ch // parameter in a standard way //------------------------------------------------- -static void parse_hunk_size(const parameters_map ¶ms, uint32_t required_granularity, uint32_t &hunk_size) +static uint32_t parse_hunk_size( + const parameters_map ¶ms, + const chd_file &output_parent, + uint32_t required_granularity, + uint32_t default_size) { - auto hunk_size_str = params.find(OPTION_HUNK_SIZE); + const auto hunk_size_str = params.find(OPTION_HUNK_SIZE); + uint32_t hunk_size = default_size; if (hunk_size_str != params.end()) { hunk_size = parse_number(hunk_size_str->second->c_str()); - if (hunk_size < 16 || hunk_size > 1024 * 1024) - report_error(1, "Invalid hunk size"); - if (hunk_size % required_granularity != 0) - report_error(1, "Hunk size is not an even multiple of %d", required_granularity); + if (output_parent.opened() && (output_parent.hunk_bytes() != hunk_size)) + report_error(1, "Specified hunk size %u bytes does not match output parent CHD hunk size %u bytes", hunk_size, output_parent.hunk_bytes()); + if (hunk_size < HUNK_SIZE_MIN) + report_error(1, "Invalid hunk size (minimum %u)", HUNK_SIZE_MIN); + if (hunk_size > HUNK_SIZE_MAX) + report_error(1, "Invalid hunk size (maximum %u)", HUNK_SIZE_MAX); + } + else if (output_parent.opened()) + { + hunk_size = output_parent.hunk_bytes(); } + + if (hunk_size % required_granularity) + report_error(1, "Hunk size %u bytes is not a whole multiple of %u", hunk_size, required_granularity); + return hunk_size; } @@ -1199,15 +1298,18 @@ static void parse_hunk_size(const parameters_map ¶ms, uint32_t required_gran // compression parameter string //------------------------------------------------- -static void parse_compression(const parameters_map ¶ms, chd_codec_type compression[4]) +static void parse_compression(const parameters_map ¶ms, const chd_codec_type (&defaults)[4], const chd_file &output_parent, chd_codec_type compression[4]) { + // TODO: should we default to the same compression as the output parent? + std::copy(std::begin(defaults), std::end(defaults), compression); + // see if anything was specified - auto compression_str = params.find(OPTION_COMPRESSION); + const auto compression_str = params.find(OPTION_COMPRESSION); if (compression_str == params.end()) return; // special case: 'none' - if (compression_str->second->compare("none")==0) + if (*compression_str->second == "none") { compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; return; @@ -1286,6 +1388,49 @@ static std::string compression_string(chd_codec_type compression[4]) //------------------------------------------------- +// open_input_file - open input file if specified +//------------------------------------------------- + +static std::pair<util::core_file::ptr, const std::string *> open_input_file(const parameters_map ¶ms) +{ + const auto path = params.find(OPTION_INPUT); + if (path == params.end()) + return std::make_pair(nullptr, nullptr); + + util::core_file::ptr file; + const std::error_condition err = util::core_file::open(*path->second, OPEN_FLAG_READ, file); + if (err) + report_error(1, "Unable to open input file (%s): %s", *path->second, err.message()); + + return std::make_pair(std::move(file), path->second); +} + + +//------------------------------------------------- +// create_output_chd - open output CHD with or +// without parent CHD file +//------------------------------------------------- + +static void create_output_chd( + chd_file_compressor &compressor, + std::string_view path, + uint64_t logical_size, + uint32_t hunk_size, + uint32_t unit_size, + const chd_codec_type (&compression)[4], + chd_file &parent) +{ + std::error_condition err; + if (parent.opened()) + err = compressor.create(path, logical_size, hunk_size, compression, parent); + else + err = compressor.create(path, logical_size, hunk_size, unit_size, compression); + if (err) + report_error(1, "Error creating CHD file (%s): %s", path, err.message()); +} + + +//------------------------------------------------- // compress_common - standard compression loop //------------------------------------------------- @@ -1476,8 +1621,8 @@ static void do_info(parameters_map ¶ms) parse_input_chd_parameters(params, input_chd, input_parent_chd); // print filename and version - printf("Input file: %s\n", params.find(OPTION_INPUT)->second->c_str()); - printf("File Version: %d\n", input_chd.version()); + util::stream_format(std::cout, "Input file: %s\n", *params.find(OPTION_INPUT)->second); + util::stream_format(std::cout, "File Version: %d\n", input_chd.version()); if (input_chd.version() < 3) report_error(1, "Unsupported version (%d); use an older chdman to upgrade to version 3 or later", input_chd.version()); @@ -1485,27 +1630,27 @@ static void do_info(parameters_map ¶ms) chd_codec_type compression[4] = { input_chd.compression(0), input_chd.compression(1), input_chd.compression(2), input_chd.compression(3) }; uint64_t filesize = 0; input_chd.file().length(filesize); - printf("Logical size: %s bytes\n", big_int_string(input_chd.logical_bytes()).c_str()); - printf("Hunk Size: %s bytes\n", big_int_string(input_chd.hunk_bytes()).c_str()); - printf("Total Hunks: %s\n", big_int_string(input_chd.hunk_count()).c_str()); - printf("Unit Size: %s bytes\n", big_int_string(input_chd.unit_bytes()).c_str()); - printf("Total Units: %s\n", big_int_string(input_chd.unit_count()).c_str()); - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("CHD size: %s bytes\n", big_int_string(filesize).c_str()); + util::stream_format(std::cout, "Logical size: %s bytes\n", big_int_string(input_chd.logical_bytes())); + util::stream_format(std::cout, "Hunk Size: %s bytes\n", big_int_string(input_chd.hunk_bytes())); + util::stream_format(std::cout, "Total Hunks: %s\n", big_int_string(input_chd.hunk_count())); + util::stream_format(std::cout, "Unit Size: %s bytes\n", big_int_string(input_chd.unit_bytes())); + util::stream_format(std::cout, "Total Units: %s\n", big_int_string(input_chd.unit_count())); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "CHD size: %s bytes\n", big_int_string(filesize)); if (compression[0] != CHD_CODEC_NONE) - printf("Ratio: %.1f%%\n", 100.0 * double(filesize) / double(input_chd.logical_bytes())); + util::stream_format(std::cout, "Ratio: %.1f%%\n", 100.0 * double(filesize) / double(input_chd.logical_bytes())); - // add SHA1 output + // add SHA-1 output util::sha1_t overall = input_chd.sha1(); if (overall != util::sha1_t::null) { - printf("SHA1: %s\n", overall.as_string().c_str()); + util::stream_format(std::cout, "SHA1: %s\n", overall.as_string()); if (input_chd.version() >= 4) - printf("Data SHA1: %s\n", input_chd.raw_sha1().as_string().c_str()); + util::stream_format(std::cout, "Data SHA1: %s\n", input_chd.raw_sha1().as_string()); } util::sha1_t parent = input_chd.parent_sha1(); if (parent != util::sha1_t::null) - printf("Parent SHA1: %s\n", parent.as_string().c_str()); + util::stream_format(std::cout, "Parent SHA1: %s\n", parent.as_string()); // print out metadata std::vector<uint8_t> buffer; @@ -1538,18 +1683,18 @@ static void do_info(parameters_map ¶ms) // print either a string representation or a hex representation of the tag if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff)) - printf("Metadata: Tag='%c%c%c%c' Index=%d Length=%d bytes\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metaindex, int(buffer.size())); + util::stream_format(std::cout, "Metadata: Tag='%c%c%c%c' Index=%d Length=%d bytes\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metaindex, buffer.size()); else - printf("Metadata: Tag=%08x Index=%d Length=%d bytes\n", metatag, metaindex, int(buffer.size())); - printf(" "); + util::stream_format(std::cout, "Metadata: Tag=%08x Index=%d Length=%d bytes\n", metatag, metaindex, buffer.size()); + util::stream_format(std::cout, " "); uint32_t count = buffer.size(); // limit output to 60 characters of metadata if not verbose if (!verbose) count = std::min(60U, count); for (int chnum = 0; chnum < count; chnum++) - printf("%c", isprint(uint8_t(buffer[chnum])) ? buffer[chnum] : '.'); - printf("\n"); + util::stream_format(std::cout, "%c", isprint(uint8_t(buffer[chnum])) ? buffer[chnum] : '.'); + util::stream_format(std::cout, "\n"); } // print compression stats if verbose @@ -1581,9 +1726,9 @@ static void do_info(parameters_map ¶ms) } // output the stats - printf("\n"); - printf(" Hunks Percent Name\n"); - printf("---------- ------- ------------------------------------\n"); + util::stream_format(std::cout, "\n"); + util::stream_format(std::cout, " Hunks Percent Name\n"); + util::stream_format(std::cout, "---------- ------- ------------------------------------\n"); for (int comptype = 0; comptype < std::size(compression_types); comptype++) if (compression_types[comptype] != 0) { @@ -1603,8 +1748,8 @@ static void do_info(parameters_map ¶ms) } // output the stats - printf("%10s %5.1f%% %-40s\n", - big_int_string(compression_types[comptype]).c_str(), + util::stream_format(std::cout, "%10s %5.1f%% %-40s\n", + big_int_string(compression_types[comptype]), 100.0 * double(compression_types[comptype]) / double(input_chd.hunk_count()), name); } @@ -1613,7 +1758,7 @@ static void do_info(parameters_map ¶ms) //------------------------------------------------- -// do_verify - validate the SHA1 on a CHD +// do_verify - validate the SHA-1 on a CHD //------------------------------------------------- static void do_verify(parameters_map ¶ms) @@ -1623,7 +1768,7 @@ static void do_verify(parameters_map ¶ms) chd_file input_chd; parse_input_chd_parameters(params, input_chd, input_parent_chd); - // only makes sense for compressed CHDs with valid SHA1's + // only makes sense for compressed CHDs with valid SHA-1's if (!input_chd.compressed()) report_error(0, "No verification to be done; CHD is uncompressed"); util::sha1_t raw_sha1 = (input_chd.version() <= 3) ? input_chd.sha1() : input_chd.raw_sha1(); @@ -1654,36 +1799,36 @@ static void do_verify(parameters_map ¶ms) // finish up if (raw_sha1 != computed_sha1) { - fprintf(stderr, "Error: Raw SHA1 in header = %s\n", raw_sha1.as_string().c_str()); - fprintf(stderr, " actual SHA1 = %s\n", computed_sha1.as_string().c_str()); + util::stream_format(std::cerr, "Error: Raw SHA1 in header = %s\n", raw_sha1.as_string()); + util::stream_format(std::cerr, " actual SHA1 = %s\n", computed_sha1.as_string()); // fix it if requested; this also fixes the overall one so we don't need to do any more if (params.find(OPTION_FIX) != params.end()) { input_chd.set_raw_sha1(computed_sha1); - printf("SHA-1 updated to correct value in input CHD\n"); + util::stream_format(std::cout, "SHA-1 updated to correct value in input CHD\n"); } } else { - printf("Raw SHA1 verification successful!\n"); + util::stream_format(std::cout, "Raw SHA1 verification successful!\n"); // now include the metadata for >= v4 if (input_chd.version() >= 4) { util::sha1_t computed_overall_sha1 = input_chd.compute_overall_sha1(computed_sha1); if (input_chd.sha1() == computed_overall_sha1) - printf("Overall SHA1 verification successful!\n"); + util::stream_format(std::cout, "Overall SHA1 verification successful!\n"); else { - fprintf(stderr, "Error: Overall SHA1 in header = %s\n", input_chd.sha1().as_string().c_str()); - fprintf(stderr, " actual SHA1 = %s\n", computed_overall_sha1.as_string().c_str()); + util::stream_format(std::cerr, "Error: Overall SHA1 in header = %s\n", input_chd.sha1().as_string()); + util::stream_format(std::cerr, " actual SHA1 = %s\n", computed_overall_sha1.as_string()); // fix it if requested if (params.find(OPTION_FIX) != params.end()) { input_chd.set_raw_sha1(computed_sha1); - printf("SHA-1 updated to correct value in input CHD\n"); + util::stream_format(std::cout, "SHA-1 updated to correct value in input CHD\n"); } } } @@ -1699,22 +1844,11 @@ static void do_verify(parameters_map ¶ms) static void do_create_raw(parameters_map ¶ms) { // process input file - util::core_file::ptr input_file; - auto input_file_str = params.find(OPTION_INPUT); - if (input_file_str != params.end()) - { - std::error_condition const filerr = util::core_file::open(*input_file_str->second, OPEN_FLAG_READ, input_file); - if (filerr) - report_error(1, "Unable to open file (%s): %s", *input_file_str->second, filerr.message()); - } + auto [input_file, input_file_str] = open_input_file(params); // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); - - // process hunk size - uint32_t hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : 0; - parse_hunk_size(params, 1, hunk_size); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); // process unit size uint32_t unit_size = output_parent.opened() ? output_parent.unit_bytes() : 0; @@ -1722,51 +1856,49 @@ static void do_create_raw(parameters_map ¶ms) if (unit_size_str != params.end()) { unit_size = parse_number(unit_size_str->second->c_str()); - if (hunk_size % unit_size != 0) - report_error(1, "Unit size is not an even divisor of the hunk size"); + if (output_parent.opened() && (output_parent.unit_bytes() != unit_size)) + report_error(1, "Specified unit size %u bytes does not match output parent CHD unit size %u bytes", unit_size, output_parent.unit_bytes()); } + else if (!output_parent.opened()) + { + report_error(1, "Unit size must be specified if no output parent CHD is supplied"); + } + + // process hunk size + const uint32_t hunk_size = parse_hunk_size(params, output_parent, unit_size, std::max((4096 / unit_size) * unit_size, unit_size)); // process input start/end (needs to know hunk_size) - uint64_t input_start; - uint64_t input_end; - uint64_t input_size = 0; - input_file->length(input_size); // FIXME: check error return - parse_input_start_end(params, input_size, hunk_size, hunk_size, input_start, input_end); + const auto [input_size, input_start, input_end] = parse_input_start_end(params, *input_file, hunk_size, hunk_size); + if ((input_end - input_start) % unit_size) + report_error(1, "Data size %s is not divisible by unit size %d", big_int_string(input_end - input_start), unit_size); // process compression chd_codec_type compression[4]; - memcpy(compression, s_default_raw_compression, sizeof(compression)); - parse_compression(params, compression); + parse_compression(params, s_default_raw_compression, output_parent, compression); // process numprocessors parse_numprocessors(params); // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); - printf("Input file: %s\n", input_file_str->second->c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); + util::stream_format(std::cout, "Input file: %s\n", *input_file_str); if (input_start != 0 || input_end != input_size) { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(input_end - input_start)); } - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Hunk size: %s\n", big_int_string(hunk_size).c_str()); - printf("Logical size: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "Hunk size: %s\n", big_int_string(hunk_size)); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(input_end - input_start)); // catch errors so we can close & delete the output file try { // create the new CHD - std::unique_ptr<chd_file_compressor> chd(new chd_rawfile_compressor(*input_file, input_start, input_end)); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), input_end - input_start, hunk_size, compression, output_parent); - else - err = chd->create(output_chd_str->c_str(), input_end - input_start, hunk_size, unit_size, compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", output_chd_str, err.message()); + auto chd = std::make_unique<chd_rawfile_compressor>(*input_file, input_start, input_end); + create_output_chd(*chd, *output_chd_str, input_end - input_start, hunk_size, unit_size, compression, output_parent); // if we have a parent, copy forward all the metadata if (output_parent.opened()) @@ -1778,9 +1910,7 @@ static void do_create_raw(parameters_map ¶ms) catch (...) { // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -1794,79 +1924,92 @@ static void do_create_raw(parameters_map ¶ms) static void do_create_hd(parameters_map ¶ms) { // process input file - util::core_file::ptr input_file; - auto input_file_str = params.find(OPTION_INPUT); - if (input_file_str != params.end()) - { - std::error_condition const filerr = util::core_file::open(*input_file_str->second, OPEN_FLAG_READ, input_file); - if (filerr) - report_error(1, "Unable to open file (%s): %s", *input_file_str->second, filerr.message()); - } + auto [input_file, input_file_str] = open_input_file(params); // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); - // process sectorsize + // process template + uint32_t cylinders = 0; + uint32_t heads = 0; + uint32_t sectors = 0; uint32_t sector_size = output_parent.opened() ? output_parent.unit_bytes() : IDE_SECTOR_SIZE; + const auto template_str = params.find(OPTION_TEMPLATE); + if (template_str != params.end()) + { + if (output_parent.opened()) + report_error(1, "Template cannot be used when a parent CHD is supplied"); + const uint32_t id = parse_number(template_str->second->c_str()); + if (id >= std::size(s_hd_templates)) + report_error(1, "Template '%s' is invalid\n", *template_str->second); + + cylinders = s_hd_templates[id].cylinders; + heads = s_hd_templates[id].heads; + sectors = s_hd_templates[id].sectors; + sector_size = s_hd_templates[id].sector_size; + } + + // process CHS + const auto chs_str = params.find(OPTION_CHS); + if (chs_str != params.end()) + { + if (template_str != params.end()) + report_error(1, "CHS geometry cannot be specified separately when a template is specified"); + if (output_parent.opened()) + report_error(1, "CHS geometry cannot be specified when a parent CHD is supplied"); + if (sscanf(chs_str->second->c_str(), "%u,%u,%u", &cylinders, &heads, §ors) != 3) + report_error(1, "Invalid CHS string; must be of the form <cylinders>,<heads>,<sectors>"); + } + + // process sector size auto sectorsize_str = params.find(OPTION_SECTOR_SIZE); if (sectorsize_str != params.end()) { - if (output_parent.opened()) - report_error(1, "Sector size does not apply when creating a diff from the parent"); + if (template_str != params.end()) + report_error(1, "Sector size cannot be specified separately when a template is specified"); sector_size = parse_number(sectorsize_str->second->c_str()); } + if (output_parent.opened() && (output_parent.unit_bytes() != sector_size)) + report_error(1, "Sector size %u bytes does not match output parent CHD sector size %u bytes", sector_size, output_parent.unit_bytes()); // process hunk size (needs to know sector_size) - uint32_t hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : std::max((4096 / sector_size) * sector_size, sector_size); - parse_hunk_size(params, sector_size, hunk_size); + const uint32_t hunk_size = parse_hunk_size(params, output_parent, sector_size, std::max((4096 / sector_size) * sector_size, sector_size)); // process input start/end (needs to know hunk_size) uint64_t filesize = 0; + uint64_t input_size = 0; uint64_t input_start = 0; uint64_t input_end = 0; if (input_file) { - uint64_t input_size = 0; - input_file->length(input_size); // FIXME: check error return - parse_input_start_end(params, input_size, hunk_size, hunk_size, input_start, input_end); + std::tie(input_size, input_start, input_end) = parse_input_start_end(params, *input_file, hunk_size, hunk_size); filesize = input_end - input_start; + if (params.find(OPTION_SIZE) != params.end()) + report_error(1, "Size cannot be specified when an input file is supplied"); } else { - auto size_str = params.find(OPTION_SIZE); + const auto size_str = params.find(OPTION_SIZE); if (size_str != params.end()) { - if (sscanf(size_str->second->c_str(), "%" I64FMT"d", &filesize) != 1) - report_error(1, "Invalid size string"); + if (sscanf(size_str->second->c_str(), "%" I64FMT"u", &filesize) != 1) + report_error(1, "Invalid size specified"); } + if ((params.find(OPTION_INPUT_START_BYTE) != params.end()) || (params.find(OPTION_INPUT_START_HUNK) != params.end()) || + (params.find(OPTION_INPUT_LENGTH_BYTES) != params.end()) || (params.find(OPTION_INPUT_LENGTH_HUNKS) != params.end())) + report_error(1, "Input start/length cannot be specified when no input file is supplied"); } // process compression chd_codec_type compression[4]; - memcpy(compression, s_default_hd_compression, sizeof(compression)); - if (!input_file) - compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; - parse_compression(params, compression); - if (!input_file && compression[0] != CHD_CODEC_NONE) - report_error(1, "Blank hard disks must be uncompressed"); + parse_compression(params, input_file ? s_default_hd_compression : s_no_compression, output_parent, compression); + if (!input_file && (compression[0] != CHD_CODEC_NONE)) + report_error(1, "Blank hard disk images must be uncompressed"); // process numprocessors parse_numprocessors(params); - // process chs - uint32_t cylinders = 0; - uint32_t heads = 0; - uint32_t sectors = 0; - auto chs_str = params.find(OPTION_CHS); - if (chs_str != params.end()) - { - if (output_parent.opened()) - report_error(1, "CHS does not apply when creating a diff from the parent"); - if (sscanf(chs_str->second->c_str(), "%d,%d,%d", &cylinders, &heads, §ors) != 3) - report_error(1, "Invalid CHS string; must be of the form <cylinders>,<heads>,<sectors>"); - } - // process ident std::vector<uint8_t> identdata; if (output_parent.opened()) @@ -1887,27 +2030,10 @@ static void do_create_hd(parameters_map ¶ms) sectors = get_u16le(&identdata[12]); // ignore CHS for > 8GB drives - if (cylinders * heads * sectors >= 16514064) + if (cylinders * heads * sectors >= 16'514'064) cylinders = 0; } - // process template - auto template_str = params.find(OPTION_TEMPLATE); - if (template_str != params.end()) - { - uint32_t id = parse_number(template_str->second->c_str()); - - if (id >= std::size(s_hd_templates)) - report_error(1, "Template '%d' is invalid\n", id); - - cylinders = s_hd_templates[id].cylinders; - heads = s_hd_templates[id].heads; - sectors = s_hd_templates[id].sectors; - sector_size = s_hd_templates[id].sector_size; - - printf("Template: %s %s\n", s_hd_templates[id].manufacturer, s_hd_templates[id].model); - } - // extract geometry from the parent if we have one if (output_parent.opened() && cylinders == 0) { @@ -1919,58 +2045,55 @@ static void do_create_hd(parameters_map ¶ms) } // validate the size - if (filesize % sector_size != 0) - report_error(1, "Data size is not divisible by sector size %d", sector_size); + if (filesize % sector_size) + report_error(1, "Data size %s is not divisible by sector size %u", big_int_string(input_end - input_start), sector_size); // if no CHS values, try to guess them if (cylinders == 0) { if (!input_file && filesize == 0) - report_error(1, "Blank hard drives must specify either a length or a set of CHS values"); - guess_chs((input_file_str != params.end()) ? input_file_str->second : nullptr, filesize, sector_size, cylinders, heads, sectors, sector_size); + report_error(1, "Length or CHS geometry must be specified when creating a blank hard disk image"); + guess_chs(input_file_str, filesize, sector_size, cylinders, heads, sectors, sector_size); } uint32_t totalsectors = cylinders * heads * sectors; // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); if (input_file) { - uint64_t input_size = 0; - input_file->length(input_size); // FIXME: check error return - printf("Input file: %s\n", input_file_str->second->c_str()); + util::stream_format(std::cout, "Input file: %s\n", *input_file_str); if (input_start != 0 || input_end != input_size) { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(filesize).c_str()); + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(filesize)); } } - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Cylinders: %d\n", cylinders); - printf("Heads: %d\n", heads); - printf("Sectors: %d\n", sectors); - printf("Bytes/sector: %d\n", sector_size); - printf("Sectors/hunk: %d\n", hunk_size / sector_size); - printf("Logical size: %s\n", big_int_string(uint64_t(totalsectors) * uint64_t(sector_size)).c_str()); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + if (template_str != params.end()) + util::stream_format(std::cout, "Template: %s %s\n", s_hd_templates[id].manufacturer, s_hd_templates[id].model); + util::stream_format(std::cout, "Cylinders: %u\n", cylinders); + util::stream_format(std::cout, "Heads: %u\n", heads); + util::stream_format(std::cout, "Sectors: %u\n", sectors); + util::stream_format(std::cout, "Bytes/sector: %u\n", sector_size); + util::stream_format(std::cout, "Sectors/hunk: %u\n", hunk_size / sector_size); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(uint64_t(totalsectors) * uint64_t(sector_size))); // catch errors so we can close & delete the output file try { // create the new hard drive std::unique_ptr<chd_file_compressor> chd; - if (input_file) chd.reset(new chd_rawfile_compressor(*input_file, input_start, input_end)); - else chd.reset(new chd_zero_compressor(input_start, input_end)); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * uint64_t(sector_size), hunk_size, compression, output_parent); + if (input_file) + chd.reset(new chd_rawfile_compressor(*input_file, input_start, input_end)); else - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * uint64_t(sector_size), hunk_size, sector_size, compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", output_chd_str, err.message()); + chd.reset(new chd_zero_compressor(input_start, input_end)); + create_output_chd(*chd, *output_chd_str, uint64_t(totalsectors) * sector_size, hunk_size, sector_size, compression, output_parent); // add the standard hard disk metadata - std::string metadata = string_format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sector_size); + std::string metadata = string_format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sector_size); + std::error_condition err; err = chd->write_metadata(HARD_DISK_METADATA_TAG, 0, metadata); if (err) report_error(1, "Error adding hard disk metadata: %s", err.message()); @@ -1990,9 +2113,7 @@ static void do_create_hd(parameters_map ¶ms) catch (...) { // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -2018,21 +2139,21 @@ static void do_create_cd(parameters_map ¶ms) // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); // process hunk size - uint32_t hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : cdrom_file::FRAMES_PER_HUNK * cdrom_file::FRAME_SIZE; - parse_hunk_size(params, cdrom_file::FRAME_SIZE, hunk_size); + const uint32_t hunk_size = parse_hunk_size(params, output_parent, cdrom_file::FRAME_SIZE, cdrom_file::FRAMES_PER_HUNK * cdrom_file::FRAME_SIZE); + if (output_parent.opened() && (output_parent.unit_bytes() != cdrom_file::FRAME_SIZE)) + report_error(1, "Output parent CHD sector size %u bytes does not match CD-ROM frame size %u bytes", output_parent.unit_bytes(), cdrom_file::FRAME_SIZE); // process compression chd_codec_type compression[4]; - memcpy(compression, s_default_cd_compression, sizeof(compression)); - parse_compression(params, compression); + parse_compression(params, s_default_cd_compression, output_parent, compression); // process numprocessors parse_numprocessors(params); - // pad each track to a 4-frame boundary. cdrom.c will deal with this on the read side + // pad each track to a 4-frame boundary. cdrom.cpp will deal with this on the read side uint32_t origtotalsectors = 0; uint32_t totalsectors = 0; for (int tracknum = 0; tracknum < toc.numtrks; tracknum++) @@ -2045,45 +2166,34 @@ static void do_create_cd(parameters_map ¶ms) } // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); - printf("Input file: %s\n", input_file_str->second->c_str()); - printf("Input tracks: %d\n", toc.numtrks); - printf("Input length: %s\n", msf_string_from_frames(origtotalsectors).c_str()); - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Logical size: %s\n", big_int_string(uint64_t(totalsectors) * cdrom_file::FRAME_SIZE).c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); + util::stream_format(std::cout, "Input file: %s\n", *input_file_str->second); + util::stream_format(std::cout, "Input tracks: %d\n", toc.numtrks); + util::stream_format(std::cout, "Input length: %s\n", msf_string_from_frames(origtotalsectors)); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(uint64_t(totalsectors) * cdrom_file::FRAME_SIZE)); // catch errors so we can close & delete the output file - chd_cd_compressor *chd = nullptr; try { // create the new CD - chd = new chd_cd_compressor(toc, track_info); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * uint64_t(cdrom_file::FRAME_SIZE), hunk_size, compression, output_parent); - else - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * uint64_t(cdrom_file::FRAME_SIZE), hunk_size, cdrom_file::FRAME_SIZE, compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", *output_chd_str, err.message()); + auto chd = std::make_unique<chd_cd_compressor>(toc, track_info); + create_output_chd(*chd, *output_chd_str, uint64_t(totalsectors) * cdrom_file::FRAME_SIZE, hunk_size, cdrom_file::FRAME_SIZE, compression, output_parent); // add the standard CD metadata; we do this even if we have a parent because it might be different - err = cdrom_file::write_metadata(chd, toc); + const std::error_condition err = cdrom_file::write_metadata(chd.get(), toc); if (err) report_error(1, "Error adding CD metadata: %s", err.message()); // compress it generically compress_common(*chd); - delete chd; } catch (...) { - delete chd; // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -2097,69 +2207,53 @@ static void do_create_cd(parameters_map ¶ms) static void do_create_dvd(parameters_map ¶ms) { // process input file - util::core_file::ptr input_file; - auto input_file_str = params.find(OPTION_INPUT); - if (input_file_str != params.end()) - { - std::error_condition const filerr = util::core_file::open(*input_file_str->second, OPEN_FLAG_READ, input_file); - if (filerr) - report_error(1, "Unable to open file (%s): %s", *input_file_str->second, filerr.message()); - } + auto [input_file, input_file_str] = open_input_file(params); // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); // process hunk size - uint32_t hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : (2 * 2048); - parse_hunk_size(params, 2048, hunk_size); + const uint32_t hunk_size = parse_hunk_size(params, output_parent, 2048, 2 * 2048); + if (output_parent.opened() && (output_parent.unit_bytes() != 2048)) + report_error(1, "Output parent CHD sector size %u bytes does not match DVD-ROM sector size 2048 bytes", output_parent.unit_bytes()); // process input start/end - uint64_t filesize = 0; - input_file->length(filesize); // FIXME: check error return + const auto [input_size, input_start, input_end] = parse_input_start_end(params, *input_file, hunk_size, hunk_size); + if ((input_end - input_start) % 2048) + report_error(1, "Data size %s is not divisible by sector size 2048", big_int_string(input_end - input_start)); // process compression chd_codec_type compression[4]; - memcpy(compression, s_default_hd_compression, sizeof(compression)); // No reason to be different than HD for compression - if (!input_file) - compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; - parse_compression(params, compression); + parse_compression(params, s_default_hd_compression, output_parent, compression); // No reason to be different than HD for compression // process numprocessors parse_numprocessors(params); - // validate the size - if (filesize % 2048 != 0) - report_error(1, "Data size is not divisible by 2048"); - - uint32_t totalsectors = filesize / 2048; - // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); - printf("Input file: %s\n", input_file_str->second->c_str()); - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Logical size: %s\n", big_int_string(uint64_t(totalsectors) * 2048).c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); + util::stream_format(std::cout, "Input file: %s\n", *input_file_str); + if (input_start != 0 || input_end != input_size) + { + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(input_end - input_start)); + } + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(input_end - input_start)); // catch errors so we can close & delete the output file try { - // create the new dvd - std::unique_ptr<chd_file_compressor> chd; - chd.reset(new chd_rawfile_compressor(*input_file, 0, filesize)); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * 2048, hunk_size, compression, output_parent); - else - err = chd->create(output_chd_str->c_str(), uint64_t(totalsectors) * 2048, hunk_size, 2048, compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", output_chd_str, err.message()); + // create the new DVD + auto chd = std::make_unique<chd_rawfile_compressor>(*input_file, input_start, input_end); + create_output_chd(*chd, *output_chd_str, input_end - input_start, hunk_size, 2048, compression, output_parent); - // add the standard dvd type tag - err = chd->write_metadata(DVD_METADATA_TAG, 0, ""); + // add the standard DVD type tag + const std::error_condition err = chd->write_metadata(DVD_METADATA_TAG, 0, ""); if (err) - report_error(1, "Error adding dvd metadata: %s", err.message()); + report_error(1, "Error adding DVD metadata: %s", err.message()); // compress it generically compress_common(*chd); @@ -2167,9 +2261,7 @@ static void do_create_dvd(parameters_map ¶ms) catch (...) { // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -2195,12 +2287,10 @@ static void do_create_ld(parameters_map ¶ms) // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); // process input start/end - uint64_t input_start; - uint64_t input_end; - parse_input_start_end(params, aviinfo.video_numsamples, 0, 1, input_start, input_end); + auto [input_start, input_end] = parse_input_start_end(params, aviinfo.video_numsamples, 0, 1); // determine parameters of the incoming video stream avi_info info; @@ -2225,13 +2315,11 @@ static void do_create_ld(parameters_map ¶ms) info.bytes_per_frame = avhuff_encoder::raw_data_size(info.width, info.height, info.channels, info.max_samples_per_frame); // process hunk size - uint32_t hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : info.bytes_per_frame; - parse_hunk_size(params, info.bytes_per_frame, hunk_size); + const uint32_t hunk_size = parse_hunk_size(params, output_parent, info.bytes_per_frame, info.bytes_per_frame); // process compression chd_codec_type compression[4]; - memcpy(compression, s_default_ld_compression, sizeof(compression)); - parse_compression(params, compression); + parse_compression(params, s_default_ld_compression, output_parent, compression); // disable support for uncompressed ones until the extraction code can handle it if (compression[0] == CHD_CODEC_NONE) report_error(1, "Uncompressed is not supported"); @@ -2240,39 +2328,33 @@ static void do_create_ld(parameters_map ¶ms) parse_numprocessors(params); // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); - printf("Input file: %s\n", input_file_str->second->c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); + util::stream_format(std::cout, "Input file: %s\n", *input_file_str->second); if (input_start != 0 && input_end != aviinfo.video_numsamples) - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s (%02d:%02d:%02d)\n", big_int_string(input_end - input_start).c_str(), + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s (%02d:%02d:%02d)\n", big_int_string(input_end - input_start), uint32_t((uint64_t(input_end - input_start) * 1000000 / info.fps_times_1million / 60 / 60)), uint32_t(((uint64_t(input_end - input_start) * 1000000 / info.fps_times_1million / 60) % 60)), uint32_t(((uint64_t(input_end - input_start) * 1000000 / info.fps_times_1million) % 60))); - printf("Frame rate: %d.%06d\n", info.fps_times_1million / 1000000, info.fps_times_1million % 1000000); - printf("Frame size: %d x %d %s\n", info.width, info.height * (info.interlaced ? 2 : 1), info.interlaced ? "interlaced" : "non-interlaced"); - printf("Audio: %d channels at %d Hz\n", info.channels, info.rate); - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Hunk size: %s\n", big_int_string(hunk_size).c_str()); - printf("Logical size: %s\n", big_int_string(uint64_t(input_end - input_start) * hunk_size).c_str()); + util::stream_format(std::cout, "Frame rate: %d.%06d\n", info.fps_times_1million / 1000000, info.fps_times_1million % 1000000); + util::stream_format(std::cout, "Frame size: %d x %d %s\n", info.width, info.height * (info.interlaced ? 2 : 1), info.interlaced ? "interlaced" : "non-interlaced"); + util::stream_format(std::cout, "Audio: %d channels at %d Hz\n", info.channels, info.rate); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "Hunk size: %s\n", big_int_string(hunk_size)); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(uint64_t(input_end - input_start) * hunk_size)); // catch errors so we can close & delete the output file - chd_avi_compressor *chd = nullptr; try { // create the new CHD - chd = new chd_avi_compressor(*input_file, info, input_start, input_end); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), uint64_t(input_end - input_start) * hunk_size, hunk_size, compression, output_parent); - else - err = chd->create(output_chd_str->c_str(), uint64_t(input_end - input_start) * hunk_size, hunk_size, info.bytes_per_frame, compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", *output_chd_str, err.message()); + auto chd = std::make_unique<chd_avi_compressor>(*input_file, info, input_start, input_end); + create_output_chd(*chd, *output_chd_str, uint64_t(input_end - input_start) * hunk_size, hunk_size, info.bytes_per_frame, compression, output_parent); // write the core A/V metadata std::string metadata = string_format(AV_METADATA_FORMAT, info.fps_times_1million / 1000000, info.fps_times_1million % 1000000, info.width, info.height, info.interlaced, info.channels, info.rate); + std::error_condition err; err = chd->write_metadata(AV_METADATA_TAG, 0, metadata); if (err) report_error(1, "Error adding AV metadata: %s\n", err.message()); @@ -2287,15 +2369,11 @@ static void do_create_ld(parameters_map ¶ms) if (err) report_error(1, "Error adding AVLD metadata: %s\n", err.message()); } - delete chd; } catch (...) { - delete chd; // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -2314,71 +2392,56 @@ static void do_copy(parameters_map ¶ms) parse_input_chd_parameters(params, input_chd, input_parent_chd); // parse out input start/end - uint64_t input_start; - uint64_t input_end; - parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); + const auto [input_start, input_end] = parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes()); + // TODO: should we check that the input range is aligned to the unit size? // process output CHD chd_file output_parent; - std::string *output_chd_str = parse_output_chd_parameters(params, output_parent); + const auto output_chd_str = parse_output_chd_parameters(params, output_parent); // process hunk size - uint32_t hunk_size = input_chd.hunk_bytes(); - parse_hunk_size(params, 1, hunk_size); - if (hunk_size % input_chd.hunk_bytes() != 0 && input_chd.hunk_bytes() % hunk_size != 0) - report_error(1, "Hunk size is not an even multiple or divisor of input hunk size"); + const uint32_t hunk_size = parse_hunk_size(params, output_parent, input_chd.unit_bytes(), input_chd.hunk_bytes()); + if ((hunk_size % input_chd.hunk_bytes()) && (input_chd.hunk_bytes() % hunk_size)) // TODO: is this check still necessary, or is the unit size check enough? + report_error(1, "Hunk size is not a whole multiple or factor of input hunk size"); // process compression; we default to our current preferences using metadata to pick the type chd_codec_type compression[4]; - { - std::vector<uint8_t> metadata; - if (!input_chd.read_metadata(HARD_DISK_METADATA_TAG, 0, metadata)) - memcpy(compression, s_default_hd_compression, sizeof(compression)); - else if (!input_chd.read_metadata(AV_METADATA_TAG, 0, metadata)) - memcpy(compression, s_default_ld_compression, sizeof(compression)); - else if (!input_chd.read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) || - !input_chd.read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) || - !input_chd.read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) || - !input_chd.read_metadata(GDROM_OLD_METADATA_TAG, 0, metadata) || - !input_chd.read_metadata(GDROM_TRACK_METADATA_TAG, 0, metadata)) - memcpy(compression, s_default_cd_compression, sizeof(compression)); - else - memcpy(compression, s_default_raw_compression, sizeof(compression)); - } - parse_compression(params, compression); + if (input_chd.is_hd() || input_chd.is_dvd()) + parse_compression(params, s_default_hd_compression, output_parent, compression); + else if (input_chd.is_av()) + parse_compression(params, s_default_ld_compression, output_parent, compression); + else if (input_chd.is_cd() || input_chd.is_gd()) + parse_compression(params, s_default_cd_compression, output_parent, compression); + else + parse_compression(params, s_default_raw_compression, output_parent, compression); // process numprocessors parse_numprocessors(params); // print some info - printf("Output CHD: %s\n", output_chd_str->c_str()); + util::stream_format(std::cout, "Output CHD: %s\n", *output_chd_str); if (output_parent.opened()) - printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->second->c_str()); - printf("Input CHD: %s\n", params.find(OPTION_INPUT)->second->c_str()); + util::stream_format(std::cout, "Parent CHD: %s\n", *params.find(OPTION_OUTPUT_PARENT)->second); + util::stream_format(std::cout, "Input CHD: %s\n", *params.find(OPTION_INPUT)->second); if (input_start != 0 || input_end != input_chd.logical_bytes()) { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(input_end - input_start)); } - printf("Compression: %s\n", compression_string(compression).c_str()); - printf("Hunk size: %s\n", big_int_string(hunk_size).c_str()); - printf("Logical size: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Compression: %s\n", compression_string(compression)); + util::stream_format(std::cout, "Hunk size: %s\n", big_int_string(hunk_size)); + util::stream_format(std::cout, "Logical size: %s\n", big_int_string(input_end - input_start)); // catch errors so we can close & delete the output file - chd_chdfile_compressor *chd = nullptr; try { // create the new CHD - chd = new chd_chdfile_compressor(input_chd, input_start, input_end); - std::error_condition err; - if (output_parent.opened()) - err = chd->create(output_chd_str->c_str(), input_end - input_start, hunk_size, compression, output_parent); - else - err = chd->create(output_chd_str->c_str(), input_end - input_start, hunk_size, input_chd.unit_bytes(), compression); - if (err) - report_error(1, "Error creating CHD file (%s): %s", *output_chd_str, err.message()); + std::unique_ptr<cdrom_file> cdrom; // want this to be unwound after chd + auto chd = std::make_unique<chd_chdfile_compressor>(input_chd, input_start, input_end); + create_output_chd(*chd, *output_chd_str, input_end - input_start, hunk_size, input_chd.unit_bytes(), compression, output_parent); // clone all the metadata, upgrading where appropriate + std::error_condition err; std::vector<uint8_t> metadata; chd_metadata_tag metatag; uint8_t metaflags; @@ -2409,9 +2472,9 @@ static void do_copy(parameters_map ¶ms) // if we need to re-do the CD metadata, do it now if (redo_cd) { - cdrom_file *cdrom = new cdrom_file(&input_chd); + cdrom = std::make_unique<cdrom_file>(&input_chd); const cdrom_file::toc &toc = cdrom->get_toc(); - err = cdrom_file::write_metadata(chd, toc); + err = cdrom_file::write_metadata(chd.get(), toc); if (err) report_error(1, "Error writing upgraded CD metadata: %s", err.message()); if (cdda_swap) @@ -2420,15 +2483,11 @@ static void do_copy(parameters_map ¶ms) // compress it generically compress_common(*chd); - delete chd; } catch (...) { - delete chd; // delete the output file - auto output_chd_str = params.find(OPTION_OUTPUT); - if (output_chd_str != params.end()) - osd_file::remove(*output_chd_str->second); + osd_file::remove(*output_chd_str); throw; } } @@ -2447,22 +2506,20 @@ static void do_extract_raw(parameters_map ¶ms) parse_input_chd_parameters(params, input_chd, input_parent_chd); // parse out input start/end - uint64_t input_start; - uint64_t input_end; - parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); + const auto [input_start, input_end] = parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes()); // verify output file doesn't exist auto output_file_str = params.find(OPTION_OUTPUT); if (output_file_str != params.end()) - check_existing_output_file(params, output_file_str->second->c_str()); + check_existing_output_file(params, *output_file_str->second); // print some info - printf("Output File: %s\n", output_file_str->second->c_str()); - printf("Input CHD: %s\n", params.find(OPTION_INPUT)->second->c_str()); + util::stream_format(std::cout, "Output File: %s\n", *output_file_str->second); + util::stream_format(std::cout, "Input CHD: %s\n", *params.find(OPTION_INPUT)->second); if (input_start != 0 || input_end != input_chd.logical_bytes()) { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(input_end - input_start)); } // catch errors so we can close & delete the output file @@ -2498,7 +2555,7 @@ static void do_extract_raw(parameters_map ¶ms) // finish up output_file.reset(); - printf("Extraction complete \n"); + util::stream_format(std::cout, "Extraction complete \n"); } catch (...) { @@ -2532,7 +2589,7 @@ static void do_extract_cd(parameters_map ¶ms) // verify output file doesn't exist auto output_file_str = params.find(OPTION_OUTPUT); if (output_file_str != params.end()) - check_existing_output_file(params, output_file_str->second->c_str()); + check_existing_output_file(params, *output_file_str->second); // verify output BIN file doesn't exist auto output_bin_file_fnd = params.find(OPTION_OUTPUT_BIN); @@ -2548,12 +2605,12 @@ static void do_extract_cd(parameters_map ¶ms) else output_bin_file_str = output_bin_file_fnd->second; - check_existing_output_file(params, output_bin_file_str->c_str()); + check_existing_output_file(params, *output_bin_file_str); // print some info - printf("Output TOC: %s\n", output_file_str->second->c_str()); - printf("Output Data: %s\n", output_bin_file_str->c_str()); - printf("Input CHD: %s\n", params.find(OPTION_INPUT)->second->c_str()); + util::stream_format(std::cout, "Output TOC: %s\n", *output_file_str->second); + util::stream_format(std::cout, "Output Data: %s\n", *output_bin_file_str); + util::stream_format(std::cout, "Input CHD: %s\n", *params.find(OPTION_INPUT)->second); // catch errors so we can close & delete the output file util::core_file::ptr output_bin_file; @@ -2672,8 +2729,8 @@ static void do_extract_cd(parameters_map ¶ms) uint32_t output_frame_size = trackinfo.datasize + ((trackinfo.subtype != cdrom_file::CD_SUB_NONE) ? trackinfo.subsize : 0); if (trackinfo.subtype != cdrom_file::CD_SUB_NONE && ((mode == MODE_CUEBIN) || (mode == MODE_GDI))) { - printf("Warning: Track %d has subcode data. bin/cue and gdi formats cannot contain subcode data and it will be omitted.\n", tracknum+1); - printf(" : This may affect usage of the output image. Use bin/toc output to keep all data.\n"); + util::stream_format(std::cout, "Warning: Track %d has subcode data. bin/cue and gdi formats cannot contain subcode data and it will be omitted.\n", tracknum+1); + util::stream_format(std::cout, " : This may affect usage of the output image. Use bin/toc output to keep all data.\n"); output_frame_size = trackinfo.datasize; } @@ -2728,7 +2785,7 @@ static void do_extract_cd(parameters_map ¶ms) // finish up output_bin_file.reset(); output_toc_file.reset(); - printf("Extraction complete \n"); + util::stream_format(std::cout, "Extraction complete \n"); } catch (...) { @@ -2742,85 +2799,6 @@ static void do_extract_cd(parameters_map ¶ms) } -//------------------------------------------------- -// do_extract_dvd - extract a DVD image from a -// CHD image, currently identical to extractraw -//------------------------------------------------- - -static void do_extract_dvd(parameters_map ¶ms) -{ - // parse out input files - chd_file input_parent_chd; - chd_file input_chd; - parse_input_chd_parameters(params, input_chd, input_parent_chd); - - // parse out input start/end - uint64_t input_start; - uint64_t input_end; - parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); - - // verify output file doesn't exist - auto output_file_str = params.find(OPTION_OUTPUT); - if (output_file_str != params.end()) - check_existing_output_file(params, output_file_str->second->c_str()); - - // print some info - printf("Output File: %s\n", output_file_str->second->c_str()); - printf("Input CHD: %s\n", params.find(OPTION_INPUT)->second->c_str()); - if (input_start != 0 || input_end != input_chd.logical_bytes()) - { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(input_end - input_start).c_str()); - } - - // catch errors so we can close & delete the output file - util::core_file::ptr output_file; - try - { - // process output file - std::error_condition const filerr = util::core_file::open(*output_file_str->second, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, output_file); - if (filerr) - report_error(1, "Unable to open file (%s): %s", *output_file_str->second, filerr.message()); - - // copy all data - std::vector<uint8_t> buffer((TEMP_BUFFER_SIZE / input_chd.hunk_bytes()) * input_chd.hunk_bytes()); - for (uint64_t offset = input_start; offset < input_end; ) - { - progress(false, "Extracting, %.1f%% complete... \r", 100.0 * double(offset - input_start) / double(input_end - input_start)); - - // determine how much to read - uint32_t bytes_to_read = (std::min<uint64_t>)(buffer.size(), input_end - offset); - std::error_condition err = input_chd.read_bytes(offset, &buffer[0], bytes_to_read); - if (err) - report_error(1, "Error reading CHD file (%s): %s", *params.find(OPTION_INPUT)->second, err.message()); - - // write to the output - size_t count; - std::error_condition const writerr = output_file->write(&buffer[0], bytes_to_read, count); - if (writerr || (count != bytes_to_read)) - report_error(1, "Error writing to file; check disk space (%s)", *output_file_str->second); - - // advance - offset += bytes_to_read; - } - - // finish up - output_file.reset(); - printf("Extraction complete \n"); - } - catch (...) - { - // delete the output file - if (output_file != nullptr) - { - output_file.reset(); - osd_file::remove(*output_file_str->second); - } - throw; - } -} - - //------------------------------------------------- @@ -2866,9 +2844,7 @@ static void do_extract_ld(parameters_map ¶ms) report_error(1, "Frame size does not match hunk size for this CHD"); // parse out input start/end - uint64_t input_start; - uint64_t input_end; - parse_input_start_end(params, input_chd.hunk_count() / interlace_factor, 0, 1, input_start, input_end); + auto [input_start, input_end] = parse_input_start_end(params, input_chd.hunk_count() / interlace_factor, 0, 1); input_start *= interlace_factor; input_end *= interlace_factor; @@ -2890,15 +2866,15 @@ static void do_extract_ld(parameters_map ¶ms) // verify output file doesn't exist auto output_file_str = params.find(OPTION_OUTPUT); if (output_file_str != params.end()) - check_existing_output_file(params, output_file_str->second->c_str()); + check_existing_output_file(params, *output_file_str->second); // print some info - printf("Output File: %s\n", output_file_str->second->c_str()); - printf("Input CHD: %s\n", params.find(OPTION_INPUT)->second->c_str()); + util::stream_format(std::cout, "Output File: %s\n", *output_file_str->second); + util::stream_format(std::cout, "Input CHD: %s\n", *params.find(OPTION_INPUT)->second); if (input_start != 0 || input_end != input_chd.hunk_count()) { - printf("Input start: %s\n", big_int_string(input_start).c_str()); - printf("Input length: %s\n", big_int_string(input_end - input_start).c_str()); + util::stream_format(std::cout, "Input start: %s\n", big_int_string(input_start)); + util::stream_format(std::cout, "Input length: %s\n", big_int_string(input_end - input_start)); } // catch errors so we can close & delete the output file @@ -2962,7 +2938,7 @@ static void do_extract_ld(parameters_map ¶ms) // close and return output_file.reset(); - printf("Extraction complete \n"); + util::stream_format(std::cout, "Extraction complete \n"); } catch (...) { @@ -3033,13 +3009,13 @@ static void do_add_metadata(parameters_map ¶ms) flags &= ~CHD_MDFLAGS_CHECKSUM; // print some info - printf("Input file: %s\n", params.find(OPTION_INPUT)->second->c_str()); - printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); - printf("Index: %d\n", index); + util::stream_format(std::cout, "Input file: %s\n", *params.find(OPTION_INPUT)->second); + util::stream_format(std::cout, "Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); + util::stream_format(std::cout, "Index: %d\n", index); if (text_str != params.end()) - printf("Text: %s\n", text.c_str()); + util::stream_format(std::cout, "Text: %s\n", text); else - printf("Data: %s (%d bytes)\n", file_str->second->c_str(), int(file.size())); + util::stream_format(std::cout, "Data: %s (%d bytes)\n", *file_str->second, file.size()); // write the metadata std::error_condition err; @@ -3050,7 +3026,7 @@ static void do_add_metadata(parameters_map ¶ms) if (err) report_error(1, "Error adding metadata: %s", err.message()); else - printf("Metadata added\n"); + util::stream_format(std::cout, "Metadata added\n"); } @@ -3081,16 +3057,16 @@ static void do_del_metadata(parameters_map ¶ms) index = atoi(index_str->second->c_str()); // print some info - printf("Input file: %s\n", params.find(OPTION_INPUT)->second->c_str()); - printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); - printf("Index: %d\n", index); + util::stream_format(std::cout, "Input file: %s\n", *params.find(OPTION_INPUT)->second); + util::stream_format(std::cout, "Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); + util::stream_format(std::cout, "Index: %d\n", index); // write the metadata std::error_condition err = input_chd.delete_metadata(tag, index); if (err) report_error(1, "Error removing metadata: %s", err.message()); else - printf("Metadata removed\n"); + util::stream_format(std::cout, "Metadata removed\n"); } @@ -3108,7 +3084,7 @@ static void do_dump_metadata(parameters_map ¶ms) // verify output file doesn't exist auto output_file_str = params.find(OPTION_OUTPUT); if (output_file_str != params.end()) - check_existing_output_file(params, output_file_str->second->c_str()); + check_existing_output_file(params, *output_file_str->second); // process tag chd_metadata_tag tag = CHD_MAKE_TAG('?','?','?','?'); @@ -3154,7 +3130,7 @@ static void do_dump_metadata(parameters_map ¶ms) output_file.reset(); // provide some feedback - printf("File (%s) written, %s bytes\n", output_file_str->second->c_str(), big_int_string(buffer.size()).c_str()); + util::stream_format(std::cout, "File (%s) written, %s bytes\n", *output_file_str->second, big_int_string(buffer.size())); } else { @@ -3180,13 +3156,13 @@ static void do_dump_metadata(parameters_map ¶ms) static void do_list_templates(parameters_map ¶ms) { - printf("\n"); - printf("ID Manufacturer Model Cylinders Heads Sectors Sector Size Total Size\n"); - printf("------------------------------------------------------------------------------------\n"); + util::stream_format(std::cout, "\n"); + util::stream_format(std::cout, "ID Manufacturer Model Cylinders Heads Sectors Sector Size Total Size\n"); + util::stream_format(std::cout, "------------------------------------------------------------------------------------\n"); for (int id = 0; id < std::size(s_hd_templates); id++) { - printf("%2d %-13s %-15s %9d %5d %7d %11d %7d MB\n", + util::stream_format(std::cout, "%2d %-13s %-15s %9d %5d %7d %11d %7d MB\n", id, s_hd_templates[id].manufacturer, s_hd_templates[id].model, @@ -3210,7 +3186,7 @@ int CLIB_DECL main(int argc, char *argv[]) // print the header extern const char build_version[]; - printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); + util::stream_format(std::cout, "chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); // handle help specially if (args.size() < 2) @@ -3227,6 +3203,7 @@ int CLIB_DECL main(int argc, char *argv[]) // iterate over commands to find our match for (auto & s_command : s_commands) + { if (command == s_command.name) { const command_description &desc = s_command; @@ -3246,12 +3223,10 @@ int CLIB_DECL main(int argc, char *argv[]) // iterate over valid options int valid; - for (valid = 0; valid < std::size(desc.valid_options); valid++) + for (valid = 0; (valid < std::size(desc.valid_options)) && desc.valid_options[valid]; valid++) { // reduce to the option name const char *validname = desc.valid_options[valid]; - if (validname == nullptr) - break; if (*validname == REQUIRED[0]) validname++; @@ -3284,8 +3259,8 @@ int CLIB_DECL main(int argc, char *argv[]) } // if not valid, error - if (valid == std::size(desc.valid_options)) - return print_help(args[0], desc, "Option not valid for this command"); + if ((valid == std::size(desc.valid_options)) || !desc.valid_options[valid]) + return print_help(args[0], desc, string_format("Option '%s' not valid for this command", arg).c_str()); } // make sure we got all our required parameters @@ -3306,20 +3281,21 @@ int CLIB_DECL main(int argc, char *argv[]) } catch (std::error_condition const &err) { - fprintf(stderr, "CHD error occurred (main): %s\n", err.message().c_str()); + util::stream_format(std::cerr, "CHD error occurred (main): %s\n", err.message()); return 1; } catch (fatal_error &err) { - fprintf(stderr, "Fatal error occurred: %d\n", err.error()); + util::stream_format(std::cerr, "Fatal error occurred: %d\n", err.error()); return err.error(); } catch (std::exception& ex) { - fprintf(stderr, "Unhandled exception: %s\n", ex.what()); + util::stream_format(std::cerr, "Unhandled exception: %s\n", ex.what()); return 1; } } + } // print generic help if nothing found return print_help(args[0]); |