diff --git a/h2mm b/h2mm index 8bf4280..ceadec3 100755 --- a/h2mm +++ b/h2mm @@ -304,6 +304,19 @@ Import mods and database from an archive file (coming from h2mm). EOF } +function display_order_help() { + cat << EOF +Usage: h2mm order [OPTIONS] [MOD_NAME|MOD_INDEX] +Change order of a mod by name or index. +Options: + -i Index of the mod to order. + -n New index of the mod. +Usage: + h2mm order -n "Example mod" 1 + h2mm order -i 1 2 +EOF +} + function display_modpack_list_help() { cat << EOF Usage: h2mm modpack-list @@ -519,8 +532,8 @@ function mod_disable() { fi # disable each mod file by adding disabled_ to the start of the filename - files=$(get_files_by_entry_from_db "$entry") - for file in $files; do + current_mod_files=$(get_files_by_entry_from_db "$entry") + for file in $current_mod_files; do [[ ! -f "$MODS_DIR/$file" ]] && { log ERROR "Mod file $file does not exist."; exit 1; } disabled_file="disabled_$file" @@ -538,7 +551,7 @@ function mod_disable() { done # downgrade mods with greater version number - downgrade_mods "$files" + downgrade_mods "$current_mod_files" # update the database sed -i "/^$mod_index,/s/ENABLED/DISABLED/" "$DB_FILE" @@ -575,13 +588,13 @@ function mod_enable() { [[ "$status" == "ENABLED" ]] && { log ERROR "Mod $mod_name is already enabled."; exit 1; } - files=$(get_files_by_entry_from_db "$entry") + current_mod_files=$(get_files_by_entry_from_db "$entry") # upgrade mods with lower version number - upgrade_mods "$files" + upgrade_mods "$current_mod_files" # enable each mod file by removing disabled_ from the start of the filename - for file in $files; do + for file in $current_mod_files; do enabled_file=$(remove_disabled_prefix "$file") # check if the files exists @@ -836,9 +849,9 @@ function mod_uninstall() { get_mod_name_and_index # delete mod files - files=$(get_files_by_entry_from_db "$entry") + current_mod_files=$(get_files_by_entry_from_db "$entry") - for file in $files; do + for file in $current_mod_files; do [[ ! -f "$MODS_DIR/$file" ]] && { log ERROR "Mod file $file does not exist."; exit 1; } log INFO "Removing ${ORANGE}\$MODS_DIR/$file${NC}." @@ -848,7 +861,7 @@ function mod_uninstall() { done # downgrade mods with greater version number, only if the mod is enabled - [[ "$status" == "ENABLED" ]] && downgrade_mods "$files" + [[ "$status" == "ENABLED" ]] && downgrade_mods "$current_mod_files" # remove entry from database sed -i "/^$mod_index,/d" "$DB_FILE" @@ -985,6 +998,173 @@ function mod_import() { log INFO "Mods ${GREEN}successfully${NC} imported." } +function mod_order { + [[ "$1" == "--help" || "$1" == "-h" ]] && { display_order_help; exit 0; } + + local mod_name="" + local mod_index="" + local new_index="" + + # parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + -i) + mod_index="$2"; shift 2 + ;; + -n) + mod_name="$2"; shift 2 + ;; + --help|-h) + display_order_help; exit 0 + ;; + *) + new_index="$1"; shift 1 + ;; + esac + done + + [[ -z "$mod_name" && -z "$mod_index" ]] && { log ERROR "Mod name or index is required to change order."; exit 1; } + + # find mod files + get_mod_name_and_index + + # get the number of mods + mod_count=$(($(cat "$DB_FILE" | wc -l) - 1)) + + # check if the mod is already at the desired index and if new index is valid + [[ "$mod_index" == "$new_index" ]] && { log ERROR "Mod $mod_name is already at index $new_index."; exit 1; } + [[ $new_index -lt 1 ]] && { log ERROR "Index can not be less than 1."; exit 1; } + [[ $new_index -gt $mod_count ]] && { log ERROR "Index can not be more than mod count (${mod_count})."; exit 1; } + + # assert ascending or descending order + ascending_order=true + [[ $mod_index -gt $new_index ]] && ascending_order=false + + # get entries between the indexes + if [[ $ascending_order == true ]]; then + entries=$(awk -v mod_index="$mod_index" -v new_index="$new_index" -F, 'NR > 1 && $1 > mod_index && $1 <= new_index {print $0}' "$DB_FILE") + else + entries=$(awk -v mod_index="$mod_index" -v new_index="$new_index" -F, 'NR > 1 && $1 < mod_index && $1 >= new_index {print $0}' "$DB_FILE") + fi + + # get files for the current mod + IFS= current_mod_files=$(get_files_by_entry_from_db "$entry"); unset IFS + + # step 1 - count how many replaces need to be done in hash table + declare -A replace_count + for file in $current_mod_files; do + extension=$(get_extension "$file") + basename=$(get_basename "$file") + + # if the file has no extension, add the basename to the hash table or increment the count + if [[ -z "$extension" ]]; then + replace_count["$basename"]=$(( ${replace_count["$basename"]:-0} + 1 )) + fi + done + + # step 2.1 - iterate over the basenames, find and store the files with the same basenames as the ones we want to upgrade/downgrade + declare -A files_to_replace + for basename in "${!replace_count[@]}"; do + IFS= files_to_replace["$basename"]=$(echo "$entries" | awk -F, '{print $4}' | grep -o "$basename\.patch_[^ ]*"); unset IFS + done + + # step 2.2 - reverse sort the files_to_replace if we are in descending order + [[ $ascending_order == false ]] && for basename in "${!files_to_replace[@]}"; do + files_to_replace["$basename"]=$(echo "${files_to_replace["$basename"]}" | sort -rV) + done + + # move current mod files to "t_$file" so we can move the new files to the correct index + for file in $current_mod_files; do + mv "$MODS_DIR/$file" "$MODS_DIR/t_$file" + [[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; } + done + + # step 3 - for every basename in files_to_replace, add replace_count to each file's patch number and move + for basename in "${!files_to_replace[@]}"; do + files=$(echo "${files_to_replace["$basename"]}" | tr ' ' '\n') + + for file in $files; do + basename=$(get_basename "$file") + patch_number=$(get_patch_number "$file") + extension=$(get_extension "$file") + + # if ascending order, subtract replace_count to the patch number, otherwise add + operation="-"; [[ $ascending_order == false ]] && operation="+" + new_file="${basename}.patch_$(($patch_number $operation ${replace_count["$basename"]}))${extension}" + + mv "$MODS_DIR/$file" "$MODS_DIR/$new_file" + [[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; } + + log INFO "Reindexing ${ORANGE}$file${NC} to ${GREEN}$new_file${NC}." + + # update db + sed -i "s/\(\b$file\b\)/$new_file/" "$DB_FILE" + done + done + + # step 4 - for every file in current_mod_files, subtract the basename's array size of files_to_replace from the patch number + for file in $current_mod_files; do + basename=$(get_basename "$file") + patch_number=$(get_patch_number "$file") + extension=$(get_extension "$file") + + # get size of only files without an extension + size=$(echo "${files_to_replace["$basename"]}" | grep -E "${basename}.patch_[0-9]+$" | tr ' ' '\n' | wc -l) + + # in case size is 0, move t_file back to file later + new_file=${file} + + if [[ $size -gt 0 ]]; then + # if ascending order, add size from the patch number, otherwise subtract + operation="+"; [[ $ascending_order == false ]] && operation="-" + new_file="${basename}.patch_$(($patch_number $operation $size))${extension}" + + log INFO "Reindexing ${ORANGE}$file${NC} to ${GREEN}$new_file${NC}." + + # dont forget current mod files are prefixed with t_ + mv "$MODS_DIR/t_$file" "$MODS_DIR/t_$new_file" + [[ $? -ne 0 ]] && { log ERROR "Could not move mod file $file."; exit 1; } + + # update file names + entry=$(echo "$entry" | sed "s/\(\b$file\b\)/$new_file/") + fi + + # move back temp file + mv "$MODS_DIR/t_$new_file" "$MODS_DIR/$new_file" + + # update index (will be replaced in db later) + entry=$(echo "$entry" | sed "s/^$mod_index,/$new_index,/") + done + + # step 5.1 - change current mod to t_index so we can move the other indexes + sed -i "s/^$mod_index,/t_$mod_index,/" "$DB_FILE" + + # step 5.2 - reindex the rest of the mods + idxs=$(echo "$entries" | awk -F, '{print $1}' | tr ' ' '\n') + + # step 5.3 - reverse sort the idxs if we are in descending order + [[ $ascending_order == false ]] && idxs=$(echo "$idxs" | sort -r) + + for idx in $idxs; do + # if ascending order, subtract 1 from the index, otherwise add 1 + operation="-"; [[ $ascending_order == false ]] && operation="+" + + sed -i "s/^$idx,/$(($idx $operation 1)),/" "$DB_FILE" + done + + # step 6 - move the entry to the new index + sed -i "/^t_$mod_index,/d" "$DB_FILE" + + # weird edge case where the new index is the last index + if [[ $((new_index + 1)) -gt $(wc -l < "$DB_FILE") ]]; then + echo "$entry" >> "$DB_FILE" + else + sed -i "$((new_index + 1))i $entry" "$DB_FILE" + fi + + log INFO "Mod ${GREEN}successfully${NC} reindexed: $mod_name from $mod_index to $new_index." +} + # --- Modpacks management --- function modpack_list() { @@ -1241,6 +1421,9 @@ function main() { "import"|"im") mod_import "$@" ;; + "order"|"o") + mod_order "$@" + ;; "modpack-list"|"ml") modpack_list "$@" ;;