Created
October 18, 2023 08:44
-
-
Save Europia79/f02de0cc92bb1673f4f9d0fd3176ba5d to your computer and use it in GitHub Desktop.
Save Headers for Sega Mega Drive ; Genesis ; 32x
This file contains 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 | |
# _save_headers.sh by Europia79 | |
########################################## | |
# README # | |
########################################## | |
# (1) This script requires Linux, Mac, or Git-for-Windows: | |
# https://gitforwindows.org/ | |
# https://git-scm.com/download/win | |
# (2) Requires a ROM set: | |
# https://r-roms.github.io/ | |
# (3) Requires Megadrive VGM Player v3.30: | |
# http://www.mjsstuf.x10host.com/pages/vgmPlay/vgmPlay.htm | |
# https://github.com/TheDeadFish/vgmPlay-vgmConv | |
# https://github.com/Europia79/vgmPlay-vgmConv | |
# (4) Requires 7-zip: | |
# https://www.7-zip.org/download.html | |
# (5) Add the 7-zip directory to your PATH environment variable: | |
# https://gist.github.com/nex3/c395b2f8fd4b02068be37c961301caa7 | |
# (6) Alternatively, if you cannot change your PATH environment, then | |
# you can instead, copy 7-zip to your ~/Git/usr/bin/ folder. | |
# (7) Edit CONFIG below for Roms Folder & Headers Directory: | |
########################################## | |
# CONFIG # | |
########################################## | |
ROMS="_roms" | |
HEADERS="_headers" | |
BAD_HEADERS="${HEADERS}/_BAD_HEADERS" | |
temp="temp_abc123xyz789" | |
########################################## | |
# CONSTANTS & GLOBALS # | |
########################################## | |
# add line numbers in debug mode (bash set -x ./_save_headers.sh): | |
PS4='+ \e[0;33m${LINENO}\e[0m::' | |
# 7z??'.="37 7A BC AF 27 1C" | |
CMAGIC_7Z="377ABCAF271C" | |
# PK..="50 4B 03 04" | |
CMAGIC_ZIP="504B0304" | |
global_args=("$@") | |
declare -i arg_count_nes_cli=0 | |
declare -a CLEANUP_FILES=() | |
declare -a ADDED_FILES=() | |
declare -a FAILED_FILES=() | |
declare -a UNSUPPORTED=() | |
# grep -n "^function " _save_headers.sh | sed 's/^/# /g' | sed 's/:function /::/g' | sed 's/() {/()::/g' | |
########################################## | |
# FUNCTION MENU: (Ctl+G) # | |
########################################## | |
# 89::pause():: | |
# 94::close():: | |
# 103::close_now():: | |
# 109::close_delay():: | |
# 114::pipeout():: | |
# 119::die():: | |
# 123::die_now():: | |
# 128::die_delay():: | |
# 133::cleanupValidation():: | |
# 152::cleanup():: | |
# 159::onExit():: | |
# 170::get_bytesize():: | |
# 174::get_magic_num():: | |
# 177::is_archive():: | |
# 180::has_archive_magic():: | |
# 183::has_7z_magic():: | |
# 186::has_zip_magic():: | |
# 189::is_archive_file():: | |
# 192::is_7z():: | |
# 195::is_zip():: | |
# 198::is_gen():: | |
# 201::is_32x():: | |
# 204::is_smd():: | |
# 208::has_gen_ext():: | |
# 221::parseInput():: | |
# 234::parseFullpath():: | |
# 259::getFile():: | |
# 266::getDirectory():: | |
# 273::getBase():: | |
# 280::getExtention():: | |
# 289::isFileType():: | |
# 304::getFileType():: | |
# 317::windows_fix():: | |
# 339::getFileType_ReturnValues_Comment():: | |
# 394::FILETYPE_CONSTANTS():: | |
# 459::require_args():: | |
# 462::onStartupClientsideValidation():: | |
########################################## | |
# CONTROL FUNCTIONS # | |
########################################## | |
# PAUSE | |
function pause() { | |
read -rsn1 -p "Press any key to continue..." | |
echo "" | |
} | |
# CLOSE | |
function close() { | |
if [ $# -eq 0 ] | |
then | |
close_now $? | |
else | |
close_now $1 | |
fi | |
} | |
# CLOSE_NOW | |
function close_now() { | |
local status=0 | |
if [ -n "$1" ] && (( $1 + 0 > 0 )); then status=$1; fi | |
exit $status | |
} | |
# CLOSE_DELAY | |
function close_delay() { | |
pause | |
close $1 | |
} | |
# PIPEOUT | |
function pipeout() { | |
printf "%s" $* | |
close 0 | |
} | |
# DIE | |
function die() { | |
die_now "$1" | |
} | |
# DIE_NOW | |
function die_now() { | |
printf "Aborted: %s\n" "$"; echo "" | |
close 1 | |
} | |
# DIE_DELAY | |
function die_delay() { | |
printf "Aborted: %s\n" "$1"; echo "" | |
pause | |
close 1 | |
} | |
function cleanupValidation() { | |
printf "\e[1;36m" | |
for failedfile in "${FAILED_FILES[@]}" | |
do | |
echo "FAILED: ${failedfile}" | |
done | |
for ufile in "${UNSUPPORTED[@]}" | |
do | |
echo "UNSUPPORTED: ${ufile}" | |
done | |
echo "TOTAL HEADERS: "$(find "./${HEADERS}" -type f | wc -l) | |
echo "ADDED: ${#ADDED_FILES[@]}" | |
echo "BAD: ${#FAILED_FILES[@]}" | |
echo "UNSUPPORTED: ${#UNSUPPORTED[@]}" | |
pwd | |
printf "\e[0m" | |
} | |
# Remove specific files before dying onError/onExit. | |
function cleanup() { | |
rm -rf -- "${temp}" | |
for cfile in "$@" | |
do | |
rm -vf -- "${cfile}" | |
done | |
} | |
function onExit() { | |
printf "\e[1;33m%s\e[1;31m\n" "onExit() cleanup..." >&2 | |
cleanup "${CLEANUP_FILES[@]}" | |
printf "\e[1;33m%s\e[0m\n" "...done." >&2 | |
cleanupValidation | |
pause | |
} >&2 | |
trap onExit EXIT | |
########################################## | |
# FILE METHODS # | |
########################################## | |
function get_bytesize() { | |
wc -c < "$1" | |
} | |
# return_val=$(get_magic_num "${file1}") | |
function get_magic_num() { | |
xxd -p -l 4 "$1" | tr '[:lower:]' '[:upper:]' | |
} | |
function is_archive() { | |
$(is_archive_file "$1") || $(has_archive_magic "$1") | |
} | |
function has_archive_magic() { | |
$(has_zip_magic "$1") || $(has_7z_magic "$1") | |
} | |
function has_7z_magic() { | |
[[ ${CMAGIC_7Z} = $(get_magic_num "$1") ]] | |
} | |
function has_zip_magic() { | |
[[ ${CMAGIC_ZIP} = $(get_magic_num "$1") ]] | |
} | |
function is_archive_file() { | |
$(is_7z "$1") || $(is_zip "$1") | |
} | |
function is_7z() { | |
isFileType "$1" "7z" | |
} | |
function is_zip() { | |
isFileType "$1" "zip" | |
} | |
function is_gen() { | |
isFileType "$1" "gen" | |
} | |
function is_32x() { | |
isFileType "$1" "32x" | |
} | |
function is_smd() { | |
isFileType "$1" "smd" | |
} | |
# 32x 68k bin gen md smd | |
function has_gen_ext() { | |
ext=$(getExtention "$1") | |
case "${ext}" in | |
32x) true; return;; | |
68k) true; return;; | |
bin) true; return;; | |
gen) true; return;; | |
md) true; return;; | |
smd) true; return;; | |
*) false; return;; | |
esac | |
} | |
# Null Check for function arguments: $* $1 $2 etc. | |
function parseInput() { | |
local PARSED_INPUT="" | |
if [ -n "$1" ] | |
then | |
PARSED_INPUT="$1" | |
else | |
die "$(basename $0): function argument cannot be NULL" | |
fi | |
echo "${PARSED_INPUT}" | |
} | |
# $(parseFullpath _RETURN_VAR "${__INPUT_VAL}") | |
# _RETURN_VAR: $1 = A named reference to the array used to store the return values. | |
# __INPUT_VAL: $2 = the input string. | |
function parseFullpath() { | |
local -n returnvar=$1 | |
local input_path=$(parseInput "$2") | |
# Convert backslashes on \Windows\ to foward slashes: /Windows/ | |
local FULLPATH="${input_path//\\//}" | |
# [0] Strip longest match of */ from start | |
local file="${FULLPATH##*/}" | |
# [1] Substring from 0 thru pos of filename | |
local dir="${FULLPATH:0:${#FULLPATH} - ${#file}}" | |
[ -z "${dir}" ] && dir="." | |
# [2] Strip shortest match of . plus at least one non-dot char from end | |
local base="${file%.[^.]*}" | |
# [3] If we have an extension and no base, it's really the base | |
local ext="${file:${#base} + 1}" | |
if [ -z "${base}" ] && [ -n "${ext}" ] | |
then | |
base=".${ext}" | |
ext="" | |
fi | |
returnvar[0]="${file}" | |
returnvar[1]="${dir}" | |
returnvar[2]="${base}" | |
returnvar[3]="${ext}" | |
} | |
# gets the entire Filename including extention. | |
function getFile() { | |
local -a path_array=() | |
local file_input=$(parseInput "$1") | |
parseFullpath path_array "${file_input}" | |
echo "${path_array[0]}" | |
} | |
# gets the Directory, or, "." if none. | |
function getDirectory() { | |
local -a path_array=() | |
local dir_input=$(parseInput "$1") | |
parseFullpath path_array "${dir_input}" | |
echo "${path_array[1]}" | |
} | |
# get only the base Filename without the extention. | |
function getBase() { | |
local -a path_array=() | |
local base_input=$(parseInput "$1") | |
parseFullpath path_array "${base_input}" | |
echo "${path_array[2]}" | |
} | |
# gets the Extention without the period. | |
function getExtention() { | |
local -a path_array=() | |
local ext_input=$(parseInput "$1") | |
parseFullpath path_array "${ext_input}" | |
echo "${path_array[3]}" | |
} | |
# Remember, type 'nes' specifically refers to an NES ROM with a header !!! | |
# $(isFileType "$1" bps) && echo "BPS=true" || echo "BPS=false" | |
# $(isFileType "$2" nes) && echo "NES=true" || echo "NES=false" | |
function isFileType() { | |
local T1=$(getFileType "$1") | |
local T2="${qFILE_TYPE[$2]}" | |
[[ ${T1} = ${T2}* ]] && return | |
if [[ ${T2} = "declare -a"* ]] | |
then | |
eval "${T2}" | |
for element in "${array[@]}" | |
do | |
[[ ${T1} = ${element}* ]] && return | |
done | |
fi | |
false | |
} | |
# Fully qualified FileType with extraneous information: | |
function getFileType() { | |
if [ ! -f "$1" ]; then die "getFileTypeOf() requires a File argument: Not: $1"; fi | |
file -b "$1" | |
} | |
########################################## | |
# WINDOWS BUG FIX # | |
########################################## | |
# Fixes a BUG on Windows: | |
# Where Drag N Drop changes the working directory to | |
# C:\Windows\system32 | |
# /c/Windows/system32 | |
# This functions attempts to change it back based on the input of DragNdrop: | |
# changes=$(windows_fix) | |
function windows_fix() { | |
echo "windows_fix()" | |
local THIS_DIR=$(pwd) | |
for arg in "${global_args[@]}" | |
do | |
if [ "/c/Windows/system32" = "${THIS_DIR}" ] | |
then | |
local temp=$(getDirectory "${arg}") | |
if [ -n "${arg}" ] && [ -n "${temp}" ] && [ -d "${temp}" ] | |
then | |
echo "CHANGING DIRECTORY TO..." | |
echo "${temp}" | |
THIS_DIR="${temp}" | |
cd "${THIS_DIR}" | |
pwd | |
return 1 | |
fi | |
fi | |
done | |
return 0 | |
} | |
# Comment: do-nothing. | |
function getFileType_ReturnValues_Comment() { | |
true | |
} | |
########################################## | |
# getFileType() return values # | |
########################################## | |
#----------------------------------------- | |
# input: Output | |
#-----------COMPRESSED-FILES-------------- | |
# .7z 7-zip archive data, version 0.4 | |
# .zip Zip archive data, at least v2.0 to extract, compression method=deflate | |
#-----------PATCH-FILES------------------- | |
# .aps | |
# .bps BPS patch file | |
# .ebp IPS patch file | |
# .ips IPS patch file | |
# .ppf Playstation Patch File version 3.0, PPF 3.0 patch, Imagetype BIN (any), Blockcheck disabled, Undo data not available, description: FFT: WotL - Valeria 2.2 (PSP USA) | |
# .rup data | |
# .ups UPS patch file | |
# .xdelta VCDIFF binary diff | |
#-----------ROM-FILES--------------------- | |
# .pce data | |
# .nes NES ROM image (iNES): 8x16k PRG, 0x8k CHR [H-mirror] [SRAM] | |
# .fds Famicom Disk System disk image: FMC-ARM, mfr FFFFFFA4 (Rev.00) (2 sides) | |
# .unh data | |
# .unf NES ROM image (UNIF v7 format) | |
# .bs data | |
# .st Sufami Turbo ROM image: "\276\260\327\2", ID 010003, series index 1 [FastROM] | |
# .sfc data | |
# .n64 Nintendo 64 ROM image (32-bit byteswapped) | |
# .v64 Nintendo 64 ROM image (V64) | |
# .z64 Nintendo 64 ROM image: "SUPER MARIO 64 " (NSME, Rev.00) | |
# .iso Nintendo GameCube disc image: "The Legend of Zelda Twilight Princess" (GZ2E01, Rev.00) | |
# .iso Nintendo Wii disc image: "SPORTS PACK for REVOLUTION" (RSPE01, Rev.01) | |
# .wbfs Nintendo Wii disc image (WBFS format): "SPORTS PACK for REVOLUTION" (RSPE01, Rev.01) | |
# .gb Game Boy ROM image: "ZELDA" (Rev.02) [MBC1+RAM+BATT], ROM: 4Mbit, RAM: 64Kbit | |
# .gbc Game Boy ROM image: "PM_CRYSTAL" (Rev.01) [CGB ONLY] [MBC3+TIMER+RAM+BATT], ROM: 16Mbit, RAM: 256Kbit | |
# .gba Game Boy Advance ROM image: "POKEMON RUBY" (AXVE01, Rev.02) | |
# .nds Nintendo DS ROM image: "CASTLEVANIA1" (ACVPA4, Rev.00) (decrypted) | |
# .gg Sega Game Gear ROM image: 2408 (Rev.01) (256 KB) | |
# .sms Sega Master System ROM image: 7076 (Rev.00) (256 KB) | |
# .32x Sega 32X ROM image: "DOOM " (GM MK-84506-00, (C)SEGA 1994.OCT) | |
# .68k Sega Mega Drive / Genesis ROM image: "\377\377\377\377" (\377\377\377\3, \377\377\377\377) | |
# .bin Sega Mega Drive / Genesis ROM image: "SONIC THE " (GM 00001051-02, (C)SEGA 1992.SEP) | |
# .gen Sega Mega Drive / Genesis ROM image: "NHL HOCKEY " (GM T-50236 -00, (C)T-50 1991.MAY) | |
# .md Sega Mega Drive / Genesis ROM image: "EA HOCKEY " (GM T-50236 -50, (C)T-50 1991.JUN) | |
# .smd Sega Mega Drive / Genesis ROM image (SMD format): 64x16k blocks, last in series or standalone | |
# .img Sega Mega CD disc image: "SOULSTAR " (GM T-115035-00, (C)T-1151994.NOV), 2352-byte sectors | |
# .iso Sega Mega CD disc image: "SOULSTAR " (GM T-115035-00, (C)T-1151994.NOV), 2048-byte sectors | |
# .bin Sega Saturn disc image: "AREA 51 " (T-9705H , V1.000) (2352-byte sectors) | |
# .bin Sega Dreamcast disc image: "CRAZY TAXI " (MK-51035 , V1.004) (2352-byte sectors) | |
# .iso UDF filesystem data (version 1.5) 'FINAL_FANTASY_X' | |
########################################## | |
# CONSTANTS2 # | |
########################################## | |
function FILETYPE_CONSTANTS() { | |
true | |
} | |
declare -A qFILE_TYPE | |
declare -A qFILE_TYPE_COM | |
declare -A qFILE_TYPE_DIF | |
declare -A qFILE_TYPE_ROM | |
qFILE_TYPE_COM["7z"]="7-zip archive" | |
qFILE_TYPE_COM[zip]="Zip archive" | |
qFILE_TYPE_DIF[aps]="UNKNOWN: NOT SUPPORTED" | |
qFILE_TYPE_DIF[bps]="BPS patch file" | |
qFILE_TYPE_DIF[ebp]="IPS patch file" | |
qFILE_TYPE_DIF[ips]="IPS patch file" | |
qFILE_TYPE_DIF[ppf]="Playstation Patch File" | |
qFILE_TYPE_DIF[rup]="data" | |
qFILE_TYPE_DIF[ups]="UPS patch file" | |
qFILE_TYPE_DIF[xdelta]="VCDIFF binary diff" | |
qFILE_TYPE_ROM[pce]="data" | |
qFILE_TYPE_ROM[nes]="NES ROM image (iNES)" | |
qFILE_TYPE_ROM[fds]="Famicom Disk System disk image:" | |
qFILE_TYPE_ROM[unh]="data" | |
qFILE_TYPE_ROM[unf]="NES ROM image (UNIF" | |
qFILE_TYPE_ROM[bs]="data" | |
qFILE_TYPE_ROM[st]="Sufami Turbo ROM image:" | |
qFILE_TYPE_ROM[sfc]="data" | |
qFILE_TYPE_ROM[n64]="Nintendo 64 ROM image (32-bit byteswapped)" | |
qFILE_TYPE_ROM[v64]="Nintendo 64 ROM image (V64)" | |
qFILE_TYPE_ROM[z64]="Nintendo 64 ROM image:" | |
qFILE_TYPE_ROM[wbfs]="Nintendo Wii disc image (WBFS format):" | |
qFILE_TYPE_ROM[gb]="Game Boy ROM image:" | |
qFILE_TYPE_ROM[gbc]="Game Boy ROM image:" | |
qFILE_TYPE_ROM[gba]="Game Boy Advance ROM image:" | |
qFILE_TYPE_ROM[nds]="Nintendo DS ROM image:" | |
qFILE_TYPE_ROM[gg]="Sega Game Gear ROM image:" | |
qFILE_TYPE_ROM[sms]="Sega Master System ROM image:" | |
qFILE_TYPE_ROM["32x"]="Sega 32X ROM image:" | |
qFILE_TYPE_ROM["68k"]="Sega Mega Drive / Genesis ROM image:" | |
qFILE_TYPE_ROM[gen]="Sega Mega Drive / Genesis ROM image:" | |
qFILE_TYPE_ROM[md]="Sega Mega Drive / Genesis ROM image:" | |
qFILE_TYPE_ROM[smd]="Sega Mega Drive / Genesis ROM image (SMD format):" | |
qFILE_TYPE_ROM[img]="Sega Mega CD disc image:" | |
declare -a array=("Sega Mega Drive / Genesis ROM image:" "Sega Saturn disc image:" "Sega Dreamcast disc image:") | |
qFILE_TYPE_ROM[bin]=$(declare -p array) | |
declare -a array=("Nintendo GameCube disc image:" "Nintendo Wii disc image:" "Sega Mega CD disc image:" "UDF filesystem data") | |
qFILE_TYPE_ROM[iso]=$(declare -p array) | |
array=() | |
# re-populate array Object: | |
# if [ "declare -a" = "${qFILE_TYPE[$key]::10}" ]; then eval "${qFILE_TYPE[$key]}"; fi | |
# or | |
# if [[ $value = "declare -a"* ]]; then eval "$value"; ...; fi | |
for key in "${!qFILE_TYPE_COM[@]}" | |
do | |
qFILE_TYPE[$key]="${qFILE_TYPE_COM[$key]}" | |
done | |
for key in "${!qFILE_TYPE_DIF[@]}" | |
do | |
qFILE_TYPE[$key]="${qFILE_TYPE_DIF[$key]}" | |
done | |
for key in "${!qFILE_TYPE_ROM[@]}" | |
do | |
qFILE_TYPE[$key]="${qFILE_TYPE_ROM[$key]}" | |
done | |
########################################## | |
# CLIENT-SIDE METHODS # | |
########################################## | |
function require_args() { | |
if [ $arg_count_nes_cli -le $(( $1 - 1 )) ]; then die "Not enough command line arguments (${arg_count_nes_cli}): ${operation}() requires $1"; fi | |
} | |
function onStartupClientsideValidation() { | |
# command -v xxd | |
true | |
} | |
########################################## | |
# START # | |
########################################## | |
onStartupClientsideValidation | |
mkdir -p "${HEADERS}" | |
mkdir -p "${BAD_HEADERS}" | |
cd "${ROMS}" | |
total_files=$(find . -type f | wc -l) | |
declare -i index=0 | |
for file in * | |
do | |
index=$((index + 1)) | |
printf "_progress: %d/%d\r" "${index}" "${total_files}" | |
mkdir -p "${temp}" | |
#---------------------------------------------- | |
if is_archive "${file}" | |
then | |
if is_zip "${file}" | |
then | |
unzip "${file}" -d "${temp}" | |
cd "${temp}" | |
elif is_7z "${file}" | |
then | |
7z e "${file}" -o"${temp}" >/dev/null | |
cd "${temp}" | |
fi | |
else | |
cp -f -- "${file}" "${temp}/${file}" | |
cd "${temp}" | |
fi | |
#---------------------------------------------- | |
for tfile in * | |
do | |
if is_smd "${tfile}" | |
then | |
# NOT SUPPORTED | |
FAILED_FILES+=("${tfile}") | |
xxd -ps -l 0x200 "${tfile}" | xxd -r -ps > "../../${BAD_HEADERS}/${tfile}" | |
elif is_gen "${tfile}" || is_32x "${tfile}" | |
then | |
xxd -ps -l 0x200 "${tfile}" | xxd -r -ps > "../../${HEADERS}/${tfile}" | |
ADDED_FILES+=("${tfile}") | |
elif has_gen_ext "${tfile}" | |
then | |
FAILED_FILES+=("${tfile}") | |
xxd -ps -l 0x200 "${tfile}" | xxd -r -ps > "../../${BAD_HEADERS}/${tfile}" | |
else | |
# NOT SUPPORTED | |
UNSUPPORTED+=("${tfile}") | |
fi | |
done | |
cd .. | |
rm -rf -- "${temp}" | |
done | |
cd .. | |
pause | |
close |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment