Skip to content

Instantly share code, notes, and snippets.

@b2cc
Last active April 30, 2021 07:54
Show Gist options
  • Save b2cc/c312bec251aaecbfedd07e4d4bda512e to your computer and use it in GitHub Desktop.
Save b2cc/c312bec251aaecbfedd07e4d4bda512e to your computer and use it in GitHub Desktop.
Find wrong pathnames when running Battletech mods in Linux (check case-sensitivity)
#!/bin/bash
#
# pathfinder.sh
#
# Find wrong pathnames when running Battletech mods
# in Linux (check case-sensitivity)
#
# navigate to your BATTLETECH install folder and execute this script, i.e.
#
# cd /home/$(whoami)/.local/share/Steam/steamapps/common/BATTLETECH
# bash ./pathfinder.sh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
set -o pipefail
debug="disabled"
check=0
checkjson=0
counter=""
failedpath=""
failedpathcounter="0"
fixedmods=""
mod=""
modmap=""
modsymlinkset=""
nopaths=""
path=""
pathmap=""
black=$(tput setaf 0)
red=$(tput setaf 1)
green=$(tput setaf 2)
yellow=$(tput setaf 3)
blue=$(tput setaf 4)
magenta=$(tput setaf 5)
cyan=$(tput setaf 6)
white=$(tput setaf 7)
bold=$(tput bold)
reset=$(tput sgr0)
LOGGER="$(which logger)"
SCRIPTNAME=$(basename $0)
log_generic() {
logdate=$(date "+%h %d %H:%M:%S")
logproc=$(echo $$)
logmessage="$logdate ${HOST} ${scriptname}[${logproc}]: (${USER}) ${LOGLEVEL}: $1"
echo "${logmessage}"
${LOGGER} "$1"
}
log() {
LOGLEVEL="${white}${bold}INFO${reset}"
log_generic "$@"
}
log_debug() {
if [[ ${debug} == enabled ]]; then
LOGLEVEL="${blue}${bold}DEBUG${reset}"
log_generic "$@"
fi
}
log_warn() {
LOGLEVEL="${yellow}${bold}WARN${reset}"
log_generic "$@"
}
die() {
LOGLEVEL="${red}${bold}ERROR${reset}"
log_generic "$@"
exit 1
}
trap "die 'Program interrupted! Exiting...'" SIGHUP SIGINT SIGQUIT SIGABRT
BINARIES=(jq)
for binary in "${BINARIES[@]}"; do
[[ -x $(which ${binary} 2>/dev/null) ]] || die "${binary} binary not found, exiting."
done
check_getopt_version() {
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
die " installed version of getopt does not support required features. install/update util-linux package."
fi
}
print_help() {
echo "
- ${SCRIPTNAME} -
Find wrong pathnames when running Battletech mods
on Linux (check case-sensitivity)
Navigate to your BATTLETECH install folder and execute this script, i.e.
cd /home/\$(whoami)/.local/share/Steam/steamapps/common/BATTLETECH
bash ./pathfinder.sh
Options:
-h | --help show help
-d | --debug enable debug output
-c | --check run path check
-j | --check-json validate JSON files (Warning: script will abort on invalid JSON,
manual intervention will probably be required.)
"
exit 0
}
checkbtfolder() {
log " * Checking if we are in the BATTLETECH install folder..."
if ! [[ -d ./BattleTech_Data ]] || ! [[ -d ./Mods ]]; then
die " ...can't locate either BattleTech_Data (n)or Mods folder, please run this script from the BATTLETECH install directory. exiting."
else
log " ...found BattleTech_Data and Mods folders, continuing."
fi
}
checkmodsymlink() {
log " * Testing if lowercase symlink for Mods folder exists:"
if ! [[ $(readlink ./mods) == Mods ]]; then
log_warn " Symlink mods to folder Mods doesn't exist - setting it up..."
ln -s Mods mods || die " Error while setting up symlink, please check folder permissions! exiting."
log " ...done."
modsymlinkset=1
else
log " mods -> Mods symlink found, continuing."
modsymlinkset=0
fi
}
read_mods() {
find -L ./Mods -mindepth 1 -maxdepth 1 -type d -prune -not -path './Mods/.modtek' | sort
}
read_path(){
jq -r '.Manifest[].Path' "${mod}"/mod.json 2>/dev/null | egrep -v '[Aa]ssets|AssetBundleName'
}
getlistofmods() {
log " * Evaluating mod folders and checking for mod.json files..."
readarray -t modmap < <(read_mods)
}
checkjson() {
log " * Validating mod.json files..."
for mod in "${modmap[@]}"; do
if [[ -r "${mod}"/mod.json ]]; then
log_debug " testing ${mod}/mod.json"
jq -r . "${mod}"/mod.json > /dev/null 2>&1 || die " ${mod}/mod.json is not valid! Please check/fix JSON syntax and run script again. exiting."
fi
done
log " - all JSON files are valid."
}
declare -a failedpathlist
checkpath() {
log_debug " -- start modmap"
if [[ ${debug} == enabled ]]; then
printf '%s\n' "${modmap[@]}"
fi
log_debug " -- stop modmap"
for mod in "${modmap[@]}"; do
log " mod: ${mod}"
counter=0
nopaths=0
if [[ -r "${mod}"/mod.json ]]; then
log_debug " extracting paths from mod.json"
readarray -t pathmap < <(read_path)
if [[ -z "${pathmap}" ]]; then
log " - mod seems to not define any paths, continuing..."
nopaths=1
else
log_debug " - testing paths in mod \"${mod}\"..."
for path in "${pathmap[@]}"; do
log_debug " - testing for existing path \"${path}\" in folder \"${mod}\""
if [[ -d "${mod}/${path}" ]]; then
log_debug " - ${mod}/${path} ...${green}${bold}OK${reset}"
log_debug ""
else
modpath="${mod}/${path}"
realpath=$(find -iname "$(basename "${modpath}")" -ipath "$(dirname "${modpath}")/*" -print -quit)
if [[ ! -z ${realpath} ]]; then
cd "$(dirname "${realpath}")"
if ln -s "$(basename "${realpath}")" "$(basename "${modpath}")"; then
log " - fixed ${modpath} to ${realpath} by ${green}${bold}creating symlink${reset}."
failedpathlist+=(${modpath})
failedpathcounter=$((failedpathcounter+1))
else
log_warn " - an error occurred while symlinking ${modpath} to ${realpath}, please fix manually!"
fi
cd - >/dev/null
counter=$((counter+1))
fi
fi
done
fi
else
log_warn " Couldn't find mod.json - possibly not a mod folder? please make your checks."
echo ""
fi
if [[ ${counter} -eq 0 ]] && [[ ${nopaths} -eq 0 ]]; then
log " - mod ${green}${bold}OK!${reset}"
fi
done
}
printreport() {
log " * Paths and mod.json files checked successfully!"
if [[ ${failedpathcounter} -gt 0 ]]; then
log " * The following paths have been fixed:"
for failedpath in "${failedpathlist[@]}"; do
log " - ${failedpath}"
done
fi
log " * All done, exiting. Have a nice day :)"
echo ""
exit 0
}
OPTS=cdhj
LONGOPTS=check,check-json,debug,help
! PARSED=$(getopt --options=${OPTS} \
--longoptions=${LONGOPTS} \
--name "${0}" \
-- "${@}")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
die "getopt parser reported an error parsing arguments, aborting."
fi
eval set -- "$PARSED"
while [[ ( ${discard_opts_after_doubledash} -eq 1 ) || ( $# -gt 0 ) ]]; do
case "$1" in
-h|--help)
print_help
;;
-c|--check)
checkpath=1
;;
-d|--debug)
debug="enabled"
;;
-j|--check-json)
checkjson=1
;;
--) if [[ ${discard_opts_after_doubledash} -eq 1 ]]; then break; fi
;;
*) extra_args=("${extra_args[@]}" "$1")
;;
esac
shift
done
extra_args=("${extra_args[@]/${dummy_arg}}")
if [[ checkjson -eq 0 ]] && [[ checkpath -eq 0 ]]; then print_help; fi
check_getopt_version
checkbtfolder
getlistofmods
if [[ checkjson -eq 1 ]]; then checkjson; fi
if [[ checkpath -eq 1 ]]; then checkpath; checkmodsymlink; fi
printreport
@berezowski
Copy link

Hi, i've updated your script to automatically create softlinks of case mismatched paths:
https://gist.github.com/berezowski/c7d4fa8b4c65194d01e85824bd105b19/revisions

@b2cc
Copy link
Author

b2cc commented Sep 1, 2019

Nice idea! I integrated it into the script.

@36PopTarts
Copy link

Hi, just so you know, the JSON checking function does not appear to invoke jq correctly. Firstly, it is extremely strict and will fail if there are, for example, trailing commas at the end of collections, which does not reflect the game's JSON compiler standards. Second of all, the "jq -r" invocation lacks a filter which causes it to fail (at least, every time I invoked it that way it would fail, even if the JSON is valid when a filter is provided).

@b2cc
Copy link
Author

b2cc commented Dec 3, 2019

@36PopTarts: I tested the script before I uploaded it, and it worked fine. However I'm not at my workstation at the moment and will check again to make sure. The filter is actually the dot after the option (-r): jq -r . which basically stands for no filter, as I don't want to filter but only validate the JSON files.

It is true that jq is very strict, but it also seems equally true that JSON doesn't allow trailing commas - at least to what I could find out with a quick search (e.g.: SO discussion: https://stackoverflow.com/questions/201782/can-you-use-a-trailing-comma-in-a-json-object ). BATTLETECH seems to be more relaxed when parsing the files, so trailing commas are kind of a false positive I however can't work around in this case. I basically added the option to check JSON for validity after I encountered actually malformed JSON / malfunctioning mods a couple of times and got bored of writing shell loops every time.

If you can supply me with a mod.json file where jq misbehaves for you (except the trailing commas) I'll look into it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment