Last active
April 21, 2026 17:19
-
-
Save i30817/ed0ba4e7ee3108e3d850ba74e9e998e6 to your computer and use it in GitHub Desktop.
chd converter of redump dumps (dats from https://github.com/MetalSlug/MAMERedump). Hacks will not be recognized, if you place them in a subdir you can use --skip '*/Hacks/*' to skip a sub-directory if a tool has otherwise a method to get hack names (either by a dedicated dat or another trick like rxdelta files from my other tool rhdndat-rn).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # chd-convert.sh | |
| #use a absolute path if you want a newer self compiled chdman | |
| CHDMAN_PATH="chdman" | |
| SEARCH_DIR="." | |
| SKIP_DIRS=() | |
| DRY_RUN=false | |
| LOG_FILE="conversion.log" | |
| # Counters | |
| fail_count=0 | |
| success_count=0 | |
| total_saved_bytes=0 | |
| processed_count=0 | |
| usage() { | |
| echo "Usage: chd-convert.sh [options] [search_directory]" | |
| echo "" | |
| echo "Options:" | |
| echo " -h, --help Show help message" | |
| echo " -s, --skip DIR Dir glob to skip (can be used multiple times)" | |
| echo " -d, --dry-run Show what would be done without making changes" | |
| echo "" | |
| echo "Compression Codecs used:" | |
| echo " CD: cdzs,cdzl,cdfl (Zstandard, Deflate, FLAC)" | |
| echo " DVD: zstd,zlib,huff (Zstandard, Deflate, Huffman)" | |
| exit 0 | |
| } | |
| for cmd in "$CHDMAN_PATH" setfattr sha1sum awk sed find; do | |
| if ! command -v "$cmd" &> /dev/null; then | |
| echo "ERROR: Required command '$cmd' is not found." | |
| exit 1 | |
| fi | |
| done | |
| # Parse Command Line Arguments | |
| while [[ "$#" -gt 0 ]]; do | |
| case $1 in | |
| -h|--help) usage ;; | |
| -d|--dry-run) DRY_RUN=true ;; | |
| -s|--skip) SKIP_DIRS+=("$2"); shift ;; | |
| -*) usage ;; | |
| *) SEARCH_DIR="$1" ;; | |
| esac | |
| shift | |
| done | |
| # Initialize Log | |
| > "$LOG_FILE" | |
| shopt -s nullglob nocaseglob | |
| status() { | |
| local prefix="" | |
| $DRY_RUN && prefix="[DRY-RUN] " | |
| echo -e "${prefix}$1" | tee -a "$LOG_FILE" | |
| } | |
| human_readable() { | |
| local bytes=$1 | |
| if [ "$bytes" -lt 1024 ]; then echo "${bytes}B" | |
| elif [ "$bytes" -lt 1048576 ]; then echo "$((bytes/1024))KB" | |
| elif [ "$bytes" -lt 1073741824 ]; then echo "$((bytes/1048576))MB" | |
| else echo "$((bytes/1073741824))GB" | |
| fi | |
| } | |
| convert_game() { | |
| local source_file="$1" | |
| local chdman_mode="$2" | |
| local codec="$3" | |
| local parent_dir="${source_file%/*}" | |
| local output_chd="${source_file%.*}.chd" | |
| # Internal check: ensure source exists before proceeding | |
| if [ ! -f "$source_file" ]; then | |
| status " ERROR: Source file \"$source_file\" no longer exists. Skipping." | |
| ((fail_count++)) | |
| return 1 | |
| fi | |
| status "Processing: $source_file" | |
| ((processed_count++)) | |
| if [ -f "$output_chd" ]; then | |
| status " Warning: Skipping, \"$output_chd\" already exists." | |
| return | |
| fi | |
| local cmd_args=("-c" "$codec") | |
| local total_source_size=$(stat -c%s "$source_file") | |
| local tracks=() | |
| # Unified track validation loop | |
| if [[ "$chdman_mode" == "createcd" ]]; then | |
| while read -r track_name; do | |
| local track_path=$([[ "$track_name" == /* ]] && echo "$track_name" || echo "$parent_dir/$track_name") | |
| if [ ! -f "$track_path" ]; then | |
| status " ERROR: Required track file missing: \"$track_path\". Aborting." | |
| ((fail_count++)) | |
| return 1 | |
| fi | |
| tracks+=("$track_path") | |
| total_source_size=$((total_source_size + $(stat -c%s "$track_path"))) | |
| done < <(sed -n 's/^[[:space:]]*FILE[[:space:]]\+"//p' "$source_file" | sed 's/"[[:space:]]\+[A-Z]\+[[:space:]]*$//') | |
| fi | |
| # PSP specific hunksize check | |
| if [[ "$chdman_mode" == "createdvd" ]] && head -c 500000 "$source_file" | grep -aqE "PSP[ _]GAME"; then | |
| status " Note: PSP signature detected. Applying hunksize 2048." | |
| cmd_args+=("-hs" "2048") | |
| fi | |
| if $DRY_RUN; then | |
| status " Would run: $CHDMAN_PATH $chdman_mode -i \"$source_file\" -o \"$output_chd\" ${cmd_args[*]}" | |
| ((success_count++)) | |
| else | |
| if "$CHDMAN_PATH" "$chdman_mode" -i "$source_file" -o "$output_chd" "${cmd_args[@]}"; then | |
| local chd_size=$(stat -c%s "$output_chd") | |
| local saved_bytes=$((total_source_size - chd_size)) | |
| local saved_percent=$(( 100 - (chd_size * 100 / total_source_size) )) | |
| total_saved_bytes=$((total_saved_bytes + saved_bytes)) | |
| # Cleanup | |
| for track in "${tracks[@]}"; do rm -- "$track"; done | |
| rm -- "$source_file" | |
| status " Success: Created $output_chd ($saved_percent% smaller, saved $(human_readable $saved_bytes))" | |
| ((success_count++)) | |
| else | |
| status " ERROR: chdman failed for $source_file" | |
| ((fail_count++)) | |
| fi | |
| fi | |
| } | |
| # Construct find prune arguments | |
| FIND_SKIP_ARGS=() | |
| for skip in "${SKIP_DIRS[@]}"; do | |
| FIND_SKIP_ARGS+=(-path "$skip" -prune -o) | |
| done | |
| # Main Execution Loop | |
| while IFS= read -r -d '' unique_dir; do | |
| for f in "$unique_dir"/*.cue; do | |
| convert_game "$f" "createcd" "cdzs,cdzl,cdfl" | |
| done | |
| for f in "$unique_dir"/*.iso; do | |
| convert_game "$f" "createdvd" "zstd,zlib,huff" | |
| done | |
| done < <(find "$SEARCH_DIR" "${FIND_SKIP_ARGS[@]}" -type f \( -iname "*.cue" -o -iname "*.iso" \) -printf "%h\0" | sort -zu) | |
| # Final Summary | |
| status "-----------------------------------" | |
| status "Finished!" | |
| status "Successful: $success_count | Failures: $fail_count" | |
| if ! $DRY_RUN && [ "$processed_count" -gt 0 ]; then | |
| status "Total space saved: $(human_readable $total_saved_bytes)" | |
| fi | |
| [ "$processed_count" -eq 0 ] && rm "$LOG_FILE" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment