From f2d5660cd25f7bd353a9eeac9c49a8e01e101d32 Mon Sep 17 00:00:00 2001 From: 987123879113 <63495610+987123879113@users.noreply.github.com> Date: Tue, 26 Mar 2024 21:52:43 +0900 Subject: [PATCH] Add split output bin support --- src/tools/chdman.cpp | 202 ++++++++++++++++++++++++------------------- 1 file changed, 112 insertions(+), 90 deletions(-) diff --git a/src/tools/chdman.cpp b/src/tools/chdman.cpp index 4ef41387117a0..f85da7dfd351c 100644 --- a/src/tools/chdman.cpp +++ b/src/tools/chdman.cpp @@ -30,8 +30,9 @@ #include #include #include -#include #include +#include +#include #include #include #include @@ -96,6 +97,7 @@ constexpr int MODE_GDI = 2; #define OPTION_INPUT "input" #define OPTION_OUTPUT "output" #define OPTION_OUTPUT_BIN "outputbin" +#define OPTION_OUTPUT_SPLIT "outputsplit" #define OPTION_OUTPUT_FORCE "force" #define OPTION_INPUT_START_BYTE "inputstartbyte" #define OPTION_INPUT_START_HUNK "inputstarthunk" @@ -628,6 +630,7 @@ static const option_description s_options[] = { OPTION_INPUT_PARENT, "ip", true, " : parent file name for input CHD" }, { OPTION_OUTPUT, "o", true, " : output file name" }, { OPTION_OUTPUT_BIN, "ob", true, " : output file name for binary data" }, + { OPTION_OUTPUT_SPLIT, "os", false, ": output one binary file per track" }, { OPTION_OUTPUT_FORCE, "f", false, ": force overwriting an existing file" }, { OPTION_OUTPUT_PARENT, "op", true, " : parent file name for output CHD" }, { OPTION_INPUT_START_BYTE, "isb", true, " : starting byte offset within the input" }, @@ -782,6 +785,7 @@ static const command_description s_commands[] = { REQUIRED OPTION_OUTPUT, OPTION_OUTPUT_BIN, + OPTION_OUTPUT_SPLIT, OPTION_OUTPUT_FORCE, REQUIRED OPTION_INPUT, OPTION_INPUT_PARENT, @@ -1462,62 +1466,19 @@ static void compress_common(chd_file_compressor &chd) // to a CUE file //------------------------------------------------- -void output_track_metadata(int mode, util::core_file &file, int tracknum, const cdrom_file::track_info &info, const std::string &filename, uint32_t frameoffs, uint64_t discoffs) +void output_track_metadata(int mode, util::core_file &file, int tracknum, const cdrom_file::track_info &info, const std::string &filename, uint32_t frameoffs, uint64_t discoffs, bool is_new_file) { if (mode == MODE_GDI) { - int mode = 0, size = 2048; - - switch (info.trktype) - { - case cdrom_file::CD_TRACK_MODE1: - mode = 4; - size = 2048; - break; - - case cdrom_file::CD_TRACK_MODE1_RAW: - mode = 4; - size = 2352; - break; - - case cdrom_file::CD_TRACK_MODE2: - mode = 4; - size = 2336; - break; - - case cdrom_file::CD_TRACK_MODE2_FORM1: - mode = 4; - size = 2048; - break; - - case cdrom_file::CD_TRACK_MODE2_FORM2: - mode = 4; - size = 2324; - break; - - case cdrom_file::CD_TRACK_MODE2_FORM_MIX: - mode = 4; - size = 2336; - break; - - case cdrom_file::CD_TRACK_MODE2_RAW: - mode = 4; - size = 2352; - break; - - case cdrom_file::CD_TRACK_AUDIO: - mode = 0; - size = 2352; - break; - } + const int tracktype = info.trktype == cdrom_file::CD_TRACK_AUDIO ? 0 : 4; const bool needquote = filename.find(' ') != std::string::npos; const char *const quotestr = needquote ? "\"" : ""; - file.printf("%d %d %d %d %s%s%s %d\n", tracknum+1, frameoffs, mode, size, quotestr, filename, quotestr, discoffs); + file.printf("%d %d %d %d %s%s%s %d\n", tracknum+1, frameoffs, tracktype, info.datasize, quotestr, filename, quotestr, discoffs); } else if (mode == MODE_CUEBIN) { // first track specifies the file - if (tracknum == 0) + if (is_new_file) file.printf("FILE \"%s\" BINARY\n", filename); // determine submode @@ -1593,10 +1554,10 @@ void output_track_metadata(int mode, util::core_file &file, int tracknum, const file.printf("ZERO %s %s\n", modesubmode, msf_string_from_frames(info.pregap)); // all tracks but the first one have a file offset - if (tracknum > 0) - file.printf("DATAFILE \"%s\" #%d %s // length in bytes: %d\n", filename, uint32_t(discoffs), msf_string_from_frames(info.frames), info.frames * (info.datasize + info.subsize)); - else + if (is_new_file) file.printf("DATAFILE \"%s\" %s // length in bytes: %d\n", filename, msf_string_from_frames(info.frames), info.frames * (info.datasize + info.subsize)); + else + file.printf("DATAFILE \"%s\" #%d %s // length in bytes: %d\n", filename, uint32_t(discoffs), msf_string_from_frames(info.frames), info.frames * (info.datasize + info.subsize)); // tracks with pregaps get a START marker too if (info.pregap > 0) @@ -2569,7 +2530,6 @@ static void do_extract_raw(parameters_map ¶ms) } } - //------------------------------------------------- // do_extract_cd - extract a CD file from a // CHD image @@ -2591,43 +2551,61 @@ static void do_extract_cd(parameters_map ¶ms) if (output_file_str != params.end()) check_existing_output_file(params, *output_file_str->second); - // verify output BIN file doesn't exist + // determine output type based on the specified file extension + int mode = MODE_NORMAL; + if (output_file_str->second->find(".cue") != -1) + mode = MODE_CUEBIN; + else if (output_file_str->second->find(".gdi") != -1) + mode = MODE_GDI; + + // determine the output bin filename based on provided input parameters auto output_bin_file_fnd = params.find(OPTION_OUTPUT_BIN); std::string default_name(*output_file_str->second); + + // split path and extension int chop = default_name.find_last_of('.'); if (chop != -1) default_name.erase(chop, default_name.size()); - std::string basename = default_name; - default_name.append(".bin"); + + // GDIs will always output as split bin + bool is_splitbin = mode == MODE_GDI || params.find(OPTION_OUTPUT_SPLIT) != params.end(); + if (is_splitbin) + { + if (mode == MODE_GDI) + default_name = default_name + "%02t"; + else + default_name = default_name + " (Track %t)"; + } + + std::string output_bin_file_ext = ".bin"; std::string *output_bin_file_str; if (output_bin_file_fnd == params.end()) + { output_bin_file_str = &default_name; + } else + { output_bin_file_str = output_bin_file_fnd->second; - check_existing_output_file(params, *output_bin_file_str); + chop = (*output_bin_file_str).find_last_of('.'); + if (chop != -1) + { + output_bin_file_ext = (*output_bin_file_str).substr(chop, (*output_bin_file_str).size() - chop); + (*output_bin_file_str).erase(chop, (*output_bin_file_str).size()); + } + } // print some info 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, "Output Data: %s\n", *output_bin_file_str + output_bin_file_ext); 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; util::core_file::ptr output_toc_file; + std::string trackbin_name; try { - int mode = MODE_NORMAL; - - if (output_file_str->second->find(".cue") != -1) - { - mode = MODE_CUEBIN; - } - else if (output_file_str->second->find(".gdi") != -1) - { - mode = MODE_GDI; - } - if (cdrom->is_gdrom() && (mode == MODE_CUEBIN)) { util::stream_format(std::cout, "Warning: extracting GD-ROM CHDs as bin/cue is not fully supported and will result in an unusable CD-ROM cue file.\n"); @@ -2638,14 +2616,6 @@ static void do_extract_cd(parameters_map ¶ms) if (filerr) report_error(1, "Unable to open file (%s): %s", *output_file_str->second, filerr.message()); - // process output BIN file - if (mode != MODE_GDI) - { - filerr = util::core_file::open(*output_bin_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, output_bin_file); - if (filerr) - report_error(1, "Unable to open file (%s): %s", *output_bin_file_str, filerr.message()); - } - // determine total frames uint64_t total_bytes = 0; for (int tracknum = 0; tracknum < toc.numtrks; tracknum++) @@ -2697,37 +2667,88 @@ static void do_extract_cd(parameters_map ¶ms) uint64_t outputoffs = 0; uint32_t discoffs = 0; std::vector buffer; + bool is_new_file = false; + + // process output BIN file for (int tracknum = 0; tracknum < toc.numtrks; tracknum++) { - std::string trackbin_name = basename; + std::string new_trackbin_name = *output_bin_file_str; if (mode == MODE_GDI) { - trackbin_name += util::string_format("%02d", tracknum+1); if (toc.tracks[tracknum].trktype == cdrom_file::CD_TRACK_AUDIO) - trackbin_name += ".raw"; + new_trackbin_name += ".raw"; else - trackbin_name += ".bin"; + new_trackbin_name += output_bin_file_ext; + } + else + { + new_trackbin_name += output_bin_file_ext; + } + if (is_splitbin) + { + outputoffs = 0; + } + + if (new_trackbin_name != trackbin_name) + { + is_new_file = true; output_bin_file.reset(); + // variable replacement in output filename + const std::regex variables_regex("%([+-]?\\d+)?([a-zA-Z])"); + std::string::const_iterator new_trackbin_name_itr = new_trackbin_name.begin(); + std::string::const_iterator new_trackbin_name_end = new_trackbin_name.end(); + std::string filename_formatted = new_trackbin_name; + std::smatch variable_matches; + + while (std::regex_search(new_trackbin_name_itr, new_trackbin_name_end, variable_matches, variables_regex)) + { + const std::string full_match = variable_matches[0].str(); + const std::string format_part = variable_matches[1].str(); + const std::string format_type = variable_matches[2].str(); + std::string replacement; + + if (format_type == "t") // track + { + replacement = util::string_format("%" + format_part + "d", tracknum+1); + + if (!is_splitbin) + { + util::stream_format(std::cout, "Warning: --%s not specified but track format string detected, enabling automatically\n", OPTION_OUTPUT_SPLIT); + is_splitbin = true; + } + } + else + util::stream_format(std::cout, "Warning: found unknown format value '%s'\n", format_type); + + if (!replacement.empty()) + { + size_t index = std::string::npos; + while ((index = filename_formatted.find(full_match)) != std::string::npos) + filename_formatted.replace(index, full_match.size(), replacement); + } + + new_trackbin_name_itr = variable_matches.suffix().first; // move past match for next loop + } + + trackbin_name = filename_formatted; + + // verify output BIN file doesn't exist + check_existing_output_file(params, trackbin_name); + filerr = util::core_file::open(trackbin_name, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, output_bin_file); if (filerr) report_error(1, "Unable to open file (%s): %s", trackbin_name, filerr.message()); - outputoffs = 0; + util::stream_format(std::cout, "Current output data file: %s\n", trackbin_name); } // output the metadata about the track to the TOC file const cdrom_file::track_info &trackinfo = toc.tracks[tracknum]; - if (mode == MODE_GDI) - { - output_track_metadata(mode, *output_toc_file, tracknum, trackinfo, std::string(core_filename_extract_base(trackbin_name)), discoffs, outputoffs); - } - else - { - output_track_metadata(mode, *output_toc_file, tracknum, trackinfo, std::string(core_filename_extract_base(*output_bin_file_str)), discoffs, outputoffs); - } + output_track_metadata(mode, *output_toc_file, tracknum, trackinfo, std::string(core_filename_extract_base(trackbin_name)), discoffs, outputoffs, is_new_file); + is_new_file = false; // If this is bin/cue output and the CHD contains subdata, warn the user and don't include // the subdata size in the buffer calculation. @@ -2818,7 +2839,8 @@ static void do_extract_cd(parameters_map ¶ms) // delete the output files output_bin_file.reset(); output_toc_file.reset(); - osd_file::remove(*output_bin_file_str); + if (!trackbin_name.empty()) + osd_file::remove(trackbin_name); osd_file::remove(*output_file_str->second); throw; }