Jelajahi Sumber

Merged feature/ultimatebyte-911 into develop

Daniel Gibbs 9 tahun lalu
induk
melakukan
2e5cfb164f

+ 9 - 1
lgsm/functions/check.sh

@@ -18,6 +18,14 @@ if [ "${function_selfname}" != "command_install.sh" ]&&[ "${function_selfname}"
 	check_system_dir.sh
 fi
 
+local allowed_commands_array=( command_start.sh )
+for allowed_command in "${allowed_commands_array[@]}"
+do
+	if [ "${allowed_command}" == "${function_selfname}" ]; then
+		check_executable.sh
+	fi
+done
+
 local allowed_commands_array=( command_debug.sh command_start.sh command_install.sh )
 for allowed_command in "${allowed_commands_array[@]}"
 do
@@ -26,7 +34,7 @@ do
 	fi
 done
 
-local allowed_commands_array=( command_backup.sh command_console.sh command_debug.sh command_details.sh command_unreal2_maps.sh command_ut99_maps.sh command_monitor.sh command_start.sh command_stop.sh command_update.sh command_validate.sh command_update_functions.sh command_email_test.sh )
+local allowed_commands_array=( command_backup.sh command_console.sh command_debug.sh command_details.sh command_unreal2_maps.sh command_ut99_maps.sh command_monitor.sh command_start.sh command_stop.sh command_update.sh command_validate.sh command_update_functions.sh command_email_test.sh command_mods_install.sh command_mods_update.sh command_mods_remove.sh )
 for allowed_command in "${allowed_commands_array[@]}"
 do
 	if [ "${allowed_command}" == "${function_selfname}" ]; then

+ 2 - 2
lgsm/functions/check_deps.sh

@@ -78,7 +78,7 @@ fn_found_missing_deps(){
 		fn_print_dots "Checking dependencies"
 		sleep 0.5
 		fn_print_error_nl "Checking dependencies: missing: ${red}${array_deps_missing[@]}${default}"
-		fn_script_log_error "Checking dependencies: missing: ${red}${array_deps_missing[@]}${default}"
+		fn_script_log_error "Checking dependencies: missing: ${array_deps_missing[@]}"
 		sleep 1
 		sudo -v > /dev/null 2>&1
 		if [ $? -eq 0 ]; then
@@ -204,7 +204,7 @@ if [ -n "$(command -v dpkg-query)" ]; then
 		array_deps_required+=( zlib1g:i386 libldap-2.4-2:i386 )
 	# Serious Sam 3: BFE
 	elif [ "${gamename}" ==  "Serious Sam 3: BFE" ]; then
-		array_deps_required+=( libxrandr2:i386 libglu1-mesa:i386 libxtst6:i386 libusb-1.0-0-dev:i386 libxxf86vm1:i386 libopenal1:i386 libssl1.0.0:i386 libgtk2.0-0:i386 libdbus-glib-1-2:i386 libnm-glib-dev:i386 )		
+		array_deps_required+=( libxrandr2:i386 libglu1-mesa:i386 libxtst6:i386 libusb-1.0-0-dev:i386 libxxf86vm1:i386 libopenal1:i386 libssl1.0.0:i386 libgtk2.0-0:i386 libdbus-glib-1-2:i386 libnm-glib-dev:i386 )
 	# Unreal Engine
 	elif [ "${executable}" ==  "./ucc-bin" ]; then
 		#UT2K4

+ 18 - 0
lgsm/functions/check_executable.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+# LGSM check_system_dir.sh function
+# Author: Daniel Gibbs
+# Website: https://gameservermanagers.com
+# Description: Checks if systemdir is accessible.
+
+local commandname="CHECK"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+# Check if executable exists
+if [ ! -f "${executabledir}/${execname}" ]; then
+	fn_script_log_warn "Expected executable not found: ${executabledir}/${execname}"
+	if [ -d "${scriptlogdir}" ]; then
+		fn_print_fail_nl "Executable ${execname} was not found"
+	fi
+	exitcode="1"
+	core_exit.sh
+fi

+ 2 - 2
lgsm/functions/command_backup.sh

@@ -16,12 +16,12 @@ fn_backup_trap(){
 	echo ""
 	echo -ne "backup ${backupname}.tar.gz..."
 	fn_print_canceled_eol_nl
-	fn_script_log_info "backup ${backupname}.tar.gz: CANCELED"
+	fn_script_log_info "Backup ${backupname}.tar.gz: CANCELED"
 	sleep 1
 	rm -f "${backupdir}/${backupname}.tar.gz" | tee -a "${scriptlog}"
 	echo -ne "backup ${backupname}.tar.gz..."
 	fn_print_removed_eol_nl
-	fn_script_log_info "backup ${backupname}.tar.gz: REMOVED"
+	fn_script_log_info "Backup ${backupname}.tar.gz: REMOVED"
 	# Remove lock file
 	rm -f "${tmpdir}/.backup.lock"
 	core_exit.sh

+ 0 - 1
lgsm/functions/command_dev_debug.sh

@@ -8,7 +8,6 @@ local commandname="DEV-DEBUG"
 local commandaction="Dev-Debug"
 local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
 
-
 if [ -f "${rootdir}/.dev-debug" ]; then
 	rm "${rootdir}/.dev-debug"
 	fn_print_ok_nl "Disabled dev-debug"

+ 2 - 2
lgsm/functions/command_dev_detect_ldd.sh

@@ -22,8 +22,8 @@ elif [ -f "${filesdir}" ]; then
 fi
 echo ""
 
-files=$(find ${filesdir} | wc -l)
-find ${filesdir} -type f -print0 |
+files=$(find "${filesdir}" | wc -l)
+find "${filesdir}" -type f -print0 |
 while IFS= read -r -d $'\0' line; do
 	#ldd -v $line 2>/dev/null|grep "=>" >>"${tmpdir}/detect_ldd.tmp"
 	if [ -n "$(ldd $line 2>/dev/null |grep -v "not a dynamic executable")" ]; then

+ 117 - 0
lgsm/functions/command_mods_install.sh

@@ -0,0 +1,117 @@
+#!/bin/bash
+# LGSM command_mods_install.sh function
+# Author: Daniel Gibbs
+# Contributor: UltimateByte
+# Website: https://gameservermanagers.com
+# Description: List and installs available mods along with mods_list.sh and mods_core.sh.
+
+local commandname="MODS"
+local commandaction="addons/mods"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+check.sh
+mods_core.sh
+
+fn_print_header
+
+# Displays a list of installed mods
+fn_mods_installed_list
+if [ ${installedmodscount} -gt 0 ]; then
+	echo "Installed addons/mods"
+	echo "================================="
+	# Go through all available commands, get details and display them to the user
+	for ((llindex=0; llindex < ${#installedmodslist[@]}; llindex++)); do
+		# Current mod is the "llindex" value of the array we're going through
+		currentmod="${installedmodslist[llindex]}"
+		fn_mod_get_info
+		# Display mod info to the user
+		echo -e " * ${green}${modcommand}${default}${default}"
+	done
+	echo ""
+fi
+
+echo "Available addons/mods"
+echo "================================="
+# Display available mods from mods_list.sh
+# Set and reset vars
+compatiblemodslistindex=0
+# As long as we're within index values
+while [ "${compatiblemodslistindex}" -lt "${#compatiblemodslist[@]}" ]; do
+	# Set values for convenience
+	displayedmodname="${compatiblemodslist[compatiblemodslistindex]}"
+	displayedmodcommand="${compatiblemodslist[compatiblemodslistindex+1]}"
+	displayedmodsite="${compatiblemodslist[compatiblemodslistindex+2]}"
+	displayedmoddescription="${compatiblemodslist[compatiblemodslistindex+3]}"
+	# Output mods to the user
+	echo -e "${displayedmodname} - ${displayedmoddescription} - ${displayedmodsite}"
+	echo -e " * ${cyan}${displayedmodcommand}${default}"
+	# Increment index from the amount of values we just displayed
+	let "compatiblemodslistindex+=4"
+	((totalmodsavailable++))
+done
+
+# If no mods are available for a specific game
+if [ -z "${compatiblemodslist}" ]; then
+	fn_print_fail "No mods are currently available for ${gamename}."
+	fn_script_log_info "No mods are currently available for ${gamename}."
+	core_exit.sh
+fi
+fn_script_log_info "${totalmodsavailable} addons/mods are available for install"
+
+## User selects a mod
+echo ""
+while [[ ! " ${availablemodscommands[@]} " =~ " ${usermodselect} " ]]; do
+	echo -en "Enter an ${cyan}addon/mod${default} to ${green}install${default} (or exit to abort): "
+	read -r usermodselect
+	# Exit if user says exit or abort
+	if [ "${usermodselect}" == "exit" ]||[ "${usermodselect}" == "abort" ]; then
+			core_exit.sh
+	# Supplementary output upon invalid user input
+	elif [[ ! " ${availablemodscommands[@]} " =~ " ${usermodselect} " ]]; then
+		fn_print_error2_nl "${usermodselect} is not a valid addon/mod."
+	fi
+done
+# Get mod info
+currentmod="${usermodselect}"
+fn_mod_get_info
+
+echo ""
+echo "Installing ${modprettyname}"
+echo "================================="
+fn_script_log_info "${modprettyname} selected for install"
+
+# Check if the mod is already installed and warn the user
+if [ -f "${modsinstalledlistfullpath}" ]; then
+	if [ -n "$(sed -n "/^${modcommand}$/p" "${modsinstalledlistfullpath}")" ]; then
+		fn_print_warning_nl "${modprettyname} is already installed"
+		fn_script_log_warn "${modprettyname} is already installed"
+		sleep 1
+		echo " * Any configs may be overwritten."
+		while true; do
+			read -e -i "y" -p "Continue? [Y/n]" yn
+			case $yn in
+			[Yy]* ) break;;
+			[Nn]* ) echo Exiting; core_exit.sh;;
+			* ) echo "Please answer yes or no.";;
+			esac
+		done
+		fn_script_log_info "User selected to continue"
+	fi
+fi
+
+## Installation
+
+fn_create_mods_dir
+fn_mods_clear_tmp_dir
+fn_mods_create_tmp_dir
+fn_mod_install_files
+fn_mod_lowercase
+fn_mod_create_filelist
+fn_mod_copy_destination
+fn_mod_add_list
+fn_mod_tidy_files_list
+fn_mods_clear_tmp_dir
+echo "${modprettyname} installed"
+fn_script_log_pass "${modprettyname} installed."
+
+core_exit.sh

+ 137 - 0
lgsm/functions/command_mods_remove.sh

@@ -0,0 +1,137 @@
+#!/bin/bash
+# LGSM command_mods_uninstall.sh function
+# Author: Daniel Gibbs
+# Contributor: UltimateByte
+# Website: https://gameservermanagers.com
+# Description: Uninstall mods along with mods_list.sh and mods_core.sh.
+
+local commandname="MODS"
+local commandaction="addons/mods"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+check.sh
+mods_core.sh
+fn_mods_check_installed
+
+fn_print_header
+echo "Remove addons/mods"
+echo "================================="
+
+## Displays list of installed mods
+# Generates list to display to user
+fn_mods_installed_list
+for ((mlindex=0; mlindex < ${#installedmodslist[@]}; mlindex++)); do
+	# Current mod is the "mlindex" value of the array we are going through
+	currentmod="${installedmodslist[mlindex]}"
+	# Get mod info
+	fn_mod_get_info
+	# Display mod info to the user
+	echo -e "${red}${modcommand}${default} - ${modprettyname} - ${moddescription}"
+done
+
+echo ""
+# Keep prompting as long as the user input doesn't correspond to an available mod
+while [[ ! " ${installedmodslist[@]} " =~ " ${usermodselect} " ]]; do
+	echo -en "Enter an ${cyan}addon/mod${default} to ${green}install${default} (or exit to abort): "
+	read -r usermodselect
+	# Exit if user says exit or abort
+	if [ "${usermodselect}" == "exit" ]||[ "${usermodselect}" == "abort" ]; then
+			core_exit.sh
+	# Supplementary output upon invalid user input
+	elif [[ ! " ${availablemodscommands[@]} " =~ " ${usermodselect} " ]]; then
+		fn_print_error2_nl "${usermodselect} is not a valid addon/mod."
+	fi
+done
+
+fn_print_warning_nl "You are about to remove ${cyan}${usermodselect}${default}."
+echo " * Any custom files/configuration will be removed."
+while true; do
+	read -e -i "y" -p "Continue? [Y/n]" yn
+	case $yn in
+	[Yy]* ) break;;
+	[Nn]* ) echo Exiting; exit;;
+	* ) echo "Please answer yes or no.";;
+esac
+done
+
+currentmod="${usermodselect}"
+fn_mod_get_info
+fn_check_mod_files_list
+
+# Uninstall the mod
+fn_script_log_info "Removing ${modsfilelistsize} files from ${modprettyname}"
+echo -e "removing ${modprettyname}"
+echo -e "* ${modsfilelistsize} files to be removed"
+echo -e "* location: ${modinstalldir}"
+sleep 1
+# Go through every file and remove it
+modfileline="1"
+tput sc
+while [ "${modfileline}" -le "${modsfilelistsize}" ]; do
+	# Current line defines current file to remove
+	currentfileremove="$(sed "${modfileline}q;d" "${modsdir}/${modcommand}-files.txt")"
+	# If file or directory exists, then remove it
+
+	if [ -f "${modinstalldir}/${currentfileremove}" ]||[ -d "${modinstalldir}/${currentfileremove}" ]; then
+		rm -rf "${modinstalldir}/${currentfileremove}"
+		((exitcode=$?))
+		if [ ${exitcode} -ne 0 ]; then
+			fn_script_log_fatal "Removing ${modinstalldir}/${currentfileremove}"
+			break
+		else
+			fn_script_log_pass "Removing ${modinstalldir}/${currentfileremove}"
+		fi
+	fi
+	tput rc; tput el
+	printf "removing ${modprettyname} ${modfileline} / ${modsfilelistsize} : ${currentfileremove}..."
+	((modfileline++))
+done
+if [ ${exitcode} -ne 0 ]; then
+	fn_print_fail_eol_nl
+	core_exit.sh
+else
+	fn_print_ok_eol_nl
+fi
+sleep 0.5
+# Remove file list
+echo -en "removing ${modcommand}-files.txt..."
+sleep 0.5
+rm -rf "${modsdir}/${modcommand}-files.txt"
+local exitcode=$?
+if [ ${exitcode} -ne 0 ]; then
+	fn_script_log_fatal "Removing ${modsdir}/${modcommand}-files.txt"
+	fn_print_fail_eol_nl
+	core_exit.sh
+else
+	fn_script_log_pass "Removing ${modsdir}/${modcommand}-files.txt"
+	fn_print_ok_eol_nl
+fi
+
+# Remove mods from installed mods list
+echo -en "removing ${modcommand} from ${modsinstalledlist}..."
+sleep 0.5
+
+sed -i "/^${modcommand}$/d" "${modsinstalledlistfullpath}"
+local exitcode=$?
+if [ ${exitcode} -ne 0 ]; then
+	fn_script_loga_fatal "Removing ${modcommand} from ${modsinstalledlist}"
+	fn_print_fail_eol_nl
+	core_exit.sh
+else
+	fn_script_loga_pass "Removing ${modcommand} from ${modsinstalledlist}"
+	fn_print_ok_eol_nl
+fi
+
+# Oxide fix
+# Oxide replaces server files, so a validate is required after uninstall
+if [ "${engine}" == "unity3d" ]&&[[ "${modprettyname}" == *"Oxide"* ]]; then
+	fn_print_information_nl "Validating to restore original ${gamename} files replaced by Oxide"
+	fn_script_log "Validating to restore original ${gamename} files replaced by Oxide"
+	exitbypass="1"
+	command_validate.sh
+	unset exitbypass
+fi
+echo "${modprettyname} removed"
+fn_script_log "${modprettyname} removed"
+
+core_exit.sh

+ 110 - 0
lgsm/functions/command_mods_update.sh

@@ -0,0 +1,110 @@
+#!/bin/bash
+# LGSM command_mods_update.sh function
+# Author: Daniel Gibbs
+# Contributor: UltimateByte
+# Website: https://gameservermanagers.com
+# Description: Updates installed mods along with mods_list.sh and mods_core.sh.
+
+local commandname="MODS"
+local commandaction="Mods Update"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+check.sh
+mods_core.sh
+
+# Prevents specific files being overwritten upon update (set by ${modkeepfiles})
+# For that matter, remove cfg files after extraction before copying them to destination
+fn_remove_cfg_files(){
+	if [ "${modkeepfiles}" !=  "OVERWRITE" ]&&[ "${modkeepfiles}" != "NOUPDATE" ]; then
+		echo -e "the following files/directories will be preserved:"
+		sleep 0.5
+		# Count how many files there are to remove
+		filestopreserve="$(echo "${modkeepfiles}" | awk -F ';' '{ print NF }')"
+		# Test all subvalues of "modkeepfiles" using the ";" separator
+		for ((preservefilesindex=1; preservefilesindex < ${filestopreserve}; preservefilesindex++)); do
+			# Put the current file we are looking for into a variable
+			filetopreserve="$(echo "${modkeepfiles}" | awk -F ';' -v x=${preservefilesindex} '{ print $x }' )"
+			echo -e "	* serverfiles/${filetopreserve}"
+			# If it matches an existing file that have been extracted delete the file
+			if [ -f "${extractdir}/${filetopreserve}" ]||[ -d "${extractdir}/${filetopreserve}" ]; then
+				rm -r "${extractdir}/${filetopreserve}"
+				# Write the file path in a tmp file, to rebuild a full file list as it is rebuilt upon update
+				if [ ! -f "${modsdir}/.removedfiles.tmp" ]; then
+					touch "${modsdir}/.removedfiles.tmp"
+				fi
+					echo "${filetopreserve}" >> "${modsdir}/.removedfiles.tmp"
+			fi
+		done
+	fi
+}
+
+fn_print_dots "Update addons/mods"
+sleep 0.5
+fn_mods_check_installed
+fn_print_info_nl "Update addons/mods: ${installedmodscount} addons/mods will be updated"
+fn_script_log_info "${installedmodscount} mods or addons will be updated"
+fn_mods_installed_list
+# Go through all available commands, get details and display them to the user
+for ((ulindex=0; ulindex < ${#installedmodslist[@]}; ulindex++)); do
+	# Current mod is the "ulindex" value of the array we're going through
+	currentmod="${installedmodslist[ulindex]}"
+	fn_mod_get_info
+	# Display installed mods and the update policy
+	if [ -z "${modkeepfiles}" ]; then
+		# If modkeepfiles is not set for some reason, that's a problem
+		fn_script_log_error "Could not find update policy for ${modprettyname}"
+		fn_print_error_nl "Could not find update policy for ${modprettyname}"
+		exitcode="1"
+		core_exit.sh
+	# If the mod won't get updated
+	elif [ "${modkeepfiles}" == "NOUPDATE" ]; then
+		echo -e "	* ${red}{modprettyname}${default} (won't be updated)"
+	# If the mode is just overwritten
+	elif [ "${modkeepfiles}" == "OVERWRITE" ]; then
+		echo -e "	* ${modprettyname} (overwrite)"
+	else
+		echo -e "	* ${yellow}${modprettyname}${default} (retain common custom files)"
+	fi
+done
+sleep 1
+
+## Update
+# List all installed mods and apply update
+# Reset line value
+installedmodsline="1"
+while [ ${installedmodsline} -le ${installedmodscount} ]; do
+	currentmod="$(sed "${installedmodsline}q;d" "${modsinstalledlistfullpath}")"
+	if [ -n "${currentmod}" ]; then
+		fn_mod_get_info
+		# Don not update mod if the policy is set to "NOUPDATE"
+		if [ "${modkeepfiles}" == "NOUPDATE" ]; then
+			fn_print_info "${modprettyname} will not be updated to preserve custom files"
+			fn_script_log_info "${modprettyname} will not be updated to preserve custom files"
+		else
+			echo ""
+			echo "==> Updating ${modprettyname}"
+			fn_create_mods_dir
+			fn_mods_clear_tmp_dir
+			fn_mods_create_tmp_dir
+			fn_mod_install_files
+			fn_mod_lowercase
+			fn_remove_cfg_files
+			fn_mod_create_filelist
+			fn_mod_copy_destination
+			fn_mod_add_list
+			fn_mod_tidy_files_list
+			fn_mods_clear_tmp_dir
+		fi
+		((installedmodsline++))
+	else
+		fn_print_fail "No mod was selected"
+		fn_script_log_fatal "No mod was selected"
+		exitcode="1"
+		core_exit.sh
+	fi
+done
+echo ""
+fn_print_ok_nl "Mods update complete"
+fn_script_log_info "Mods update complete"
+
+core_exit.sh

+ 1 - 0
lgsm/functions/command_update_functions.sh

@@ -4,6 +4,7 @@
 # Website: https://gameservermanagers.com
 # Description: Deletes the functions dir to allow re-downloading of functions from GitHub.
 
+local commandname="UPDATE LGSM"
 local commandaction="Update LGSM"
 local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
 

+ 4 - 1
lgsm/functions/command_validate.sh

@@ -9,6 +9,7 @@ local commandaction="Validate"
 local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
 
 fn_validation(){
+	echo ""
 	echo -e "	* Validating may overwrite some customised files."
 	echo -en "	* https://developer.valvesoftware.com/wiki/SteamCMD#Validate"
 	sleep 3
@@ -19,7 +20,9 @@ fn_validation(){
 
 	cd "${rootdir}/steamcmd"
 
-	if [ $(command -v stdbuf) ]; then
+	# Detects if unbuffer command is available for 32 bit distributions only.
+	info_distro.sh
+	if [ $(command -v stdbuf) ]&&[ "${arch}" != "x86_64" ]; then
 		unbuffer="stdbuf -i0 -o0 -e0"
 	fi
 

+ 6 - 6
lgsm/functions/core_dl.sh

@@ -54,7 +54,6 @@ fn_dl_extract(){
 	extractdir="${3}"
 	# extracts archives
 	echo -ne "extracting ${filename}..."
-	fn_script_log_info "Extracting download"
 	mime=$(file -b --mime-type "${filedir}/${filename}")
 
 	if [ "${mime}" == "application/gzip" ]||[ "${mime}" == "application/x-gzip" ]; then
@@ -72,6 +71,7 @@ fn_dl_extract(){
 		core_exit.sh
 	else
 		fn_print_ok_eol_nl
+		fn_script_log_pass "Extracting download: OK"
 	fi
 }
 
@@ -80,12 +80,12 @@ fn_fetch_trap(){
 	echo ""
 	echo -ne "downloading ${filename}..."
 	fn_print_canceled_eol_nl
-	fn_script_log_info "downloading ${filename}...CANCELED"
+	fn_script_log_info "Downloading ${filename}...CANCELED"
 	sleep 1
 	rm -f "${filedir}/${filename}" | tee -a "${scriptlog}"
 	echo -ne "downloading ${filename}..."
 	fn_print_removed_eol_nl
-	fn_script_log_info "downloading ${filename}...REMOVED"
+	fn_script_log_info "Downloading ${filename}...REMOVED"
 	core_exit.sh
 }
 
@@ -117,7 +117,7 @@ fn_fetch_file(){
 			# trap to remove part downloaded files
 			trap fn_fetch_trap INT
 			# if larger file shows progress bar
-			if [ ${filename##*.} == "bz2" ]||[ ${filename##*.} == "gz" ]||[ ${filename##*.} == "zip" ]||[ ${filename##*.} == "jar" ]; then
+			if [ "${filename##*.}" == "bz2" ]||[ "${filename##*.}" == "gz" ]||[ "${filename##*.}" == "zip" ]||[ "${filename##*.}" == "jar" ]; then
 				echo -ne "downloading ${filename}..."
 				sleep 1
 				curlcmd=$(${curlcmd} --progress-bar --fail -L -o "${filedir}/${filename}" "${fileurl}")
@@ -130,7 +130,7 @@ fn_fetch_file(){
 			if [ ${exitcode} -ne 0 ]; then
 				fn_print_fail_eol_nl
 				if [ -f "${scriptlog}" ]; then
-					fn_script_log_fatal "downloading ${filename}: FAIL"
+					fn_script_log_fatal "Downloading ${filename}: FAIL"
 				fi
 				echo -e "${fileurl}" | tee -a "${scriptlog}"
 				echo "${curlcmd}" | tee -a "${scriptlog}"
@@ -138,7 +138,7 @@ fn_fetch_file(){
 			else
 				fn_print_ok_eol_nl
 				if [ -f "${scriptlog}" ]; then
-					fn_script_log_pass "downloading ${filename}: OK"
+					fn_script_log_pass "Downloading ${filename}: OK"
 				fi
 			fi
 			# remove trap

+ 31 - 0
lgsm/functions/core_functions.sh

@@ -182,6 +182,21 @@ functionfile="${FUNCNAME}"
 fn_fetch_function
 }
 
+command_mods_install.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
+
+command_mods_update.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
+
+command_mods_remove.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
+
 command_fastdl.sh(){
 functionfile="${FUNCNAME}"
 fn_fetch_function
@@ -215,6 +230,11 @@ functionfile="${FUNCNAME}"
 fn_fetch_function
 }
 
+check_executable.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
+
 check_glibc.sh(){
 functionfile="${FUNCNAME}"
 fn_fetch_function
@@ -278,6 +298,17 @@ functionfile="${FUNCNAME}"
 fn_fetch_function
 }
 
+# Mods
+
+mods_list.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
+
+mods_core.sh(){
+functionfile="${FUNCNAME}"
+fn_fetch_function
+}
 
 # Dev
 

+ 20 - 2
lgsm/functions/core_getopt.sh

@@ -43,6 +43,12 @@ case "${getopt}" in
 		command_install.sh;;
 	ai|auto-install)
 		fn_autoinstall;;
+	mi|mods-install)
+		command_mods_install.sh;;
+	mu|mods-update)
+		command_mods_update.sh;;
+	mr|mods-remove)
+		command_mods_remove.sh;;
 	dd|detect-deps)
 		command_dev_detect_deps.sh;;
 	dg|detect-glibc)
@@ -76,6 +82,9 @@ case "${getopt}" in
 		echo -e "${blue}debug\t${default}d  |See the output of the server directly to your terminal."
 		echo -e "${blue}install\t${default}i  |Install the server."
 		echo -e "${blue}auto-install\t${default}ai |Install the server, without prompts."
+		echo -e "${blue}mods-install\t${default}mi |View and install available mods/addons."
+		echo -e "${blue}mods-update\t${default}mu |Update installed mods/addons."
+		echo -e "${blue}mods-remove\t${default}mr |Remove installed mods/addons."
 	} | column -s $'\t' -t
 	esac
 }
@@ -517,14 +526,20 @@ case "${getopt}" in
 		command_install.sh;;
 	ai|auto-install)
 		fn_autoinstall;;
+	fd|fastdl)
+		command_fastdl.sh;;
+	mi|mods-install)
+		command_mods_install.sh;;
+	mu|mods-update)
+		command_mods_update.sh;;
+	mr|mods-remove)
+		command_mods_remove.sh;;
 	dd|detect-deps)
 		command_dev_detect_deps.sh;;
 	dg|detect-glibc)
 		command_dev_detect_glibc.sh;;
 	dl|detect-ldd)
 		command_dev_detect_ldd.sh;;
-	fd|fastdl)
-		command_fastdl.sh;;
 	*)
 	if [ -n "${getopt}" ]; then
 		echo -e "${red}Unknown command${default}: $0 ${getopt}"
@@ -552,6 +567,9 @@ case "${getopt}" in
 		echo -e "${blue}install\t${default}i  |Install the server."
 		echo -e "${blue}auto-install\t${default}ai |Install the server, without prompts."
 		echo -e "${blue}fastdl\t${default}fd |Generates or update a FastDL directory for your server."
+		echo -e "${blue}mods-install\t${default}mi |View and install available mods/addons."
+		echo -e "${blue}mods-update\t${default}mu |Update installed mods/addons."
+		echo -e "${blue}mods-remove\t${default}mr |Remove installed mods/addons."
 	} | column -s $'\t' -t
 	esac
 }

+ 2 - 2
lgsm/functions/core_messages.sh

@@ -165,9 +165,9 @@ fn_print_error(){
 
 fn_print_error_nl(){
 	if [ -n "${commandaction}" ]; then
-		echo -en "${creeol}[${red}ERROR ${default}] ${commandaction} ${servicename}: $@"
+		echo -en "${creeol}[${red}ERROR!${default}] ${commandaction} ${servicename}: $@"
 	else
-		echo -en "${creeol}[${red}ERROR ${default}] $@"
+		echo -en "${creeol}[${red}ERROR!${default}] $@"
 	fi
 	sleep 0.5
 	echo -en "\n"

+ 455 - 0
lgsm/functions/mods_core.sh

@@ -0,0 +1,455 @@
+#!/bin/bash
+# LGSM command_mods_install.sh function
+# Author: Daniel Gibbs
+# Contributor: UltimateByte
+# Website: https://gameservermanagers.com
+# Description: Core functions for mods list/install/update/remove
+
+local commandname="MODS"
+local commandaction="addons/mods"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+# Files and Directories
+modsdir="${lgsmdir}/mods"
+modstmpdir="${modsdir}/tmp"
+extractdir="${modstmpdir}/extract"
+modsinstalledlist="installed-mods.txt"
+modsinstalledlistfullpath="${modsdir}/${modsinstalledlist}"
+
+
+
+## Installation
+
+# Download management
+fn_mod_install_files(){
+	fn_fetch_file "${modurl}" "${modstmpdir}" "${modfilename}"
+	# Check if variable is valid checking if file has been downloaded and exists
+	if [ ! -f "${modstmpdir}/${modfilename}" ]; then
+		fn_print_failure "An issue occurred downloading ${modprettyname}"
+		fn_script_log_fatal "An issue occurred downloading ${modprettyname}"
+		core_exit.sh
+	fi
+	if [ ! -d "${extractdir}" ]; then
+		mkdir -p "${extractdir}"
+	fi
+	fn_dl_extract "${modstmpdir}" "${filename}" "${extractdir}"
+}
+
+# Convert mod files to lowercase if needed
+fn_mod_lowercase(){
+	if [ "${modlowercase}" == "LowercaseOn" ]; then
+
+		echo -ne "converting ${modprettyname} files to lowercase..."
+		sleep 0.5
+		fn_script_log_info "Converting ${modprettyname} files to lowercase"
+		files=$(find "${extractdir}" -depth | wc -l)
+		echo -en "\r"
+		while read -r src; do
+			dst=`dirname "${src}"`/`basename "${src}" | tr '[A-Z]' '[a-z]'`
+			if [ "${src}" != "${dst}" ]
+			then
+				[ ! -e "${dst}" ] && mv -T "${src}" "${dst}" || echo "${src} was not renamed"
+				local exitcode=$?
+				((renamedwc++))
+			fi
+			echo -ne "${renamedwc} / ${totalfileswc} / $files converting ${modprettyname} files to lowercase..." $'\r'
+			((totalfileswc++))
+		done < <(find "${extractdir}" -depth)
+		echo -ne "${renamedwc} / ${totalfileswc} / $files converting ${modprettyname} files to lowercase..."
+
+		if [ ${exitcode} -ne 0 ]; then
+			fn_print_fail_eol_nl
+			core_exit.sh
+		else
+			fn_print_ok_eol_nl
+		fi
+		sleep 0.5
+	fi
+}
+
+# Create ${modcommand}-files.txt containing the full extracted file/directory list
+fn_mod_create_filelist(){
+	echo -ne "building ${modcommand}-files.txt..."
+	sleep 0.5
+	# ${modsdir}/${modcommand}-files.txt
+	find "${extractdir}" -mindepth 1 -printf '%P\n' > "${modsdir}/${modcommand}-files.txt"
+	local exitcode=$?
+	if [ ${exitcode} -ne 0 ]; then
+		fn_print_fail_eol_nl
+		fn_script_log_fatal "Building ${modsdir}/${modcommand}-files.txt"
+		core_exit.sh
+	else
+		fn_print_ok_eol_nl
+		fn_script_log_pass "Building ${modsdir}/${modcommand}-files.txt"
+	fi
+	# Adding removed files if needed
+	if [ -f "${modsdir}/.removedfiles.tmp" ]; then
+		cat "${modsdir}/.removedfiles.tmp" >> "${modsdir}/${modcommand}-files.txt"
+	fi
+	sleep 0.5
+}
+
+# Copy the mod into serverfiles
+fn_mod_copy_destination(){
+	echo -ne "copying ${modprettyname} to ${modinstalldir}..."
+	sleep 0.5
+	cp -Rf "${extractdir}/." "${modinstalldir}/"
+	local exitcode=$?
+	if [ ${exitcode} -ne 0 ]; then
+		fn_print_fail_eol_nl
+		fn_script_log_fatal "Copying ${modprettyname} to ${modinstalldir}"
+	else
+		fn_print_ok_eol_nl
+		fn_script_log_pass "Copying ${modprettyname} to ${modinstalldir}"
+	fi
+}
+
+# Add the mod to the installed-mods.txt
+fn_mod_add_list(){
+	if [ ! -n "$(sed -n "/^${modcommand}$/p" "${modsinstalledlistfullpath}")" ]; then
+		echo "${modcommand}" >> "${modsinstalledlistfullpath}"
+		fn_script_log_info "${modcommand} added to ${modsinstalledlist}"
+	fi
+}
+
+# Prevent sensitive directories from being erased upon uninstall by removing them from: ${modcommand}-files.txt
+fn_mod_tidy_files_list(){
+	# Check file list validity
+	fn_check_mod_files_list
+	# Output to the user
+	echo -ne "tidy up ${modcommand}-files.txt..."
+	sleep 0.5
+	fn_script_log_info "Tidy up ${modcommand}-files.txt"
+	# Lines/files to remove from file list (end with ";" separator)
+	removefromlist="cfg;addons;"
+	# Loop through files to remove from file list,
+	# generate elements to remove from list
+	removefromlistamount="$(echo "${removefromlist}" | awk -F ';' '{ print NF }')"
+	# Test all subvalue of "removefromlist" using the ";" separator
+	for ((filesindex=1; filesindex < removefromlistamount; filesindex++)); do
+		# Put current file into test variable
+		removefilevar="$(echo "${removefromlist}" | awk -F ';' -v x=${filesindex} '{ print $x }')"
+		# Delete line(s) matching exactly
+		sed -i "/^${removefilevar}$/d" "${modsdir}/${modcommand}-files.txt"
+		# Exit on error
+		local exitcode=$?
+		if [ ${exitcode} -ne 0 ]; then
+			fn_print_fail_eol_nl
+			fn_script_log_fatal "Error while tidying line: ${removefilevar} from: ${modsdir}/${modcommand}-files.txt"
+			core_exit.sh
+			break
+		fi
+	done
+	fn_print_ok_eol_nl
+	# Sourcemod fix
+	# Remove metamod from sourcemod fileslist
+	if [ "${modcommand}" == "sourcemod" ]; then
+		# Remove addons/metamod & addons/metamod/sourcemod.vdf from ${modcommand}-files.txt
+		sed -i "/^addons\/metamod$/d" "${modsdir}/${modcommand}-files.txt"
+		sed -i "/^addons\/metamod\/sourcemod.vdf$/d" "${modsdir}/${modcommand}-files.txt"
+	fi
+}
+
+## Information Gathering
+
+# Get details of a mod any (relevant and unique, such as full mod name or install command) value
+fn_mod_get_info(){
+	# Variable to know when job is done
+	modinfocommand="0"
+	# Find entry in global array
+	for ((index=0; index <= ${#mods_global_array[@]}; index++)); do
+		# When entry is found
+		if [ "${mods_global_array[index]}" == "${currentmod}" ]; then
+			# Go back to the previous "MOD" separator
+			for ((index=index; index <= ${#mods_global_array[@]}; index--)); do
+				# When "MOD" is found
+				if [ "${mods_global_array[index]}" == "MOD" ]; then
+					# Get info
+					fn_mods_define
+					modinfocommand="1"
+					break
+				fi
+			done
+		fi
+		# Exit the loop if job is done
+		if [ "${modinfocommand}" == "1" ]; then
+			break
+		fi
+	done
+
+	# What happens if mod is not found
+	if [ "${modinfocommand}" == "0" ]; then
+		fn_script_log_error "Could not find information for ${currentmod}"
+		fn_print_error_nl "Could not find information for ${currentmod}"
+		exitcode="1"
+		core_exit.sh
+	fi
+}
+
+# Define all variables for a mod at once when index is set to a separator
+fn_mods_define(){
+if [ -z "$index" ]; then
+	fn_script_log_fatal "index variable not set. Please report an issue."
+	fn_print_error "index variable not set. Please report an issue."
+	echo "* https://github.com/GameServerManagers/LinuxGSM/issues"
+	core_exit.sh
+fi
+	modcommand="${mods_global_array[index+1]}"
+	modprettyname="${mods_global_array[index+2]}"
+	modurl="${mods_global_array[index+3]}"
+	modfilename="${mods_global_array[index+4]}"
+	modsubdirs="${mods_global_array[index+5]}"
+	modlowercase="${mods_global_array[index+6]}"
+	modinstalldir="${mods_global_array[index+7]}"
+	modkeepfiles="${mods_global_array[index+8]}"
+	modengines="${mods_global_array[index+9]}"
+	modgames="${mods_global_array[index+10]}"
+	modexcludegames="${mods_global_array[index+11]}"
+	modsite="${mods_global_array[index+12]}"
+	moddescription="${mods_global_array[index+13]}"
+}
+
+# Builds list of installed mods
+# using installed-mods.txt grabing mod info from mods_list.sh
+fn_mods_installed_list(){
+	fn_mods_count_installed
+	# Set/reset variables
+	installedmodsline="1"
+	installedmodslist=()
+	modprettynamemaxlength="0"
+	modsitemaxlength="0"
+	moddescriptionmaxlength="0"
+	modcommandmaxlength="0"
+	# Loop through every line of the installed mods list ${modsinstalledlistfullpath}
+	while [ ${installedmodsline} -le ${installedmodscount} ]; do
+		currentmod="$(sed "${installedmodsline}q;d" "${modsinstalledlistfullpath}")"
+		# Get mod info to make sure mod exists
+		fn_mod_get_info
+		# Add the mod to available commands
+		installedmodslist+=( "${modcommand}" )
+		# Increment line check
+		((installedmodsline++))
+	done
+	if [ -n "${installedmodscount}" ] ;then
+		fn_script_log_info "${installedmodscount} addons/mods are currently installed"
+	fi
+}
+
+# Loops through mods_global_array to define available mods & provide available commands for mods installation
+fn_mods_available(){
+	# First, reset variables
+	compatiblemodslist=()
+	availablemodscommands=()
+	# Find compatible games
+	# Find separators through the global array
+	for ((index="0"; index <= ${#mods_global_array[@]}; index++)); do
+		# If current value is a separator; then
+		if [ "${mods_global_array[index]}" == "${modseparator}" ]; then
+			# Set mod variables
+			fn_mods_define
+			# Test if game is compatible
+			fn_mod_compatible_test
+			# If game is compatible
+			if [ "${modcompatibility}" == "1" ]; then
+				# Put it into an array to prepare user output
+				compatiblemodslist+=( "${modprettyname}" "${modcommand}" "${modsite}" "${moddescription}" )
+				# Keep available commands in an array to make life easier
+				availablemodscommands+=( "${modcommand}" )
+			fi
+		fi
+	done
+}
+
+## Mod compatibility check
+
+# Find out if a game is compatible with a mod from a modgames (list of games supported by a mod) variable
+fn_compatible_mod_games(){
+	# Reset test value
+	modcompatiblegame="0"
+	# If value is set to GAMES (ignore)
+	if [ "${modgames}" != "GAMES" ]; then
+		# How many games we need to test
+		gamesamount="$(echo "${modgames}" | awk -F ';' '{ print NF }')"
+		# Test all subvalue of "modgames" using the ";" separator
+		for ((gamevarindex=1; gamevarindex < gamesamount; gamevarindex++)); do
+			# Put current game name into modtest variable
+			gamemodtest="$( echo "${modgames}" | awk -F ';' -v x=${gamevarindex} '{ print $x }' )"
+			# If game name matches
+			if [ "${gamemodtest}" == "${gamename}" ]; then
+				# Mod is compatible !
+				modcompatiblegame="1"
+			fi
+		done
+	fi
+}
+
+# Find out if an engine is compatible with a mod from a modengines (list of engines supported by a mod) variable
+fn_compatible_mod_engines(){
+	# Reset test value
+	modcompatibleengine="0"
+	# If value is set to ENGINES (ignore)
+	if [ "${modengines}" != "ENGINES" ]; then
+		# How many engines we need to test
+		enginesamount="$(echo "${modengines}" | awk -F ';' '{ print NF }')"
+		# Test all subvalue of "modengines" using the ";" separator
+		for ((gamevarindex=1; gamevarindex < ${enginesamount}; gamevarindex++)); do
+			# Put current engine name into modtest variable
+			enginemodtest="$( echo "${modengines}" | awk -F ';' -v x=${gamevarindex} '{ print $x }' )"
+			# If engine name matches
+			if [ "${enginemodtest}" == "${engine}" ]; then
+				# Mod is compatible!
+				modcompatibleengine="1"
+			fi
+		done
+	fi
+}
+
+# Find out if a game is not compatible with a mod from a modnotgames (list of games not supported by a mod) variable
+fn_not_compatible_mod_games(){
+	# Reset test value
+	modeincompatiblegame="0"
+	# If value is set to NOTGAMES (ignore)
+	if [ "${modexcludegames}" != "NOTGAMES" ]; then
+		# How many engines we need to test
+		excludegamesamount="$(echo "${modexcludegames}" | awk -F ';' '{ print NF }')"
+		# Test all subvalue of "modexcludegames" using the ";" separator
+		for ((gamevarindex=1; gamevarindex < excludegamesamount; gamevarindex++)); do
+			# Put current engine name into modtest variable
+			excludegamemodtest="$( echo "${modexcludegames}" | awk -F ';' -v x=${gamevarindex} '{ print $x }' )"
+			# If engine name matches
+			if [ "${excludegamemodtest}" == "${gamename}" ]; then
+				# Mod is compatible!
+				modeincompatiblegame="1"
+			fi
+		done
+	fi
+}
+
+# Sums up if a mod is compatible or not with modcompatibility=0/1
+fn_mod_compatible_test(){
+	# Test game and engine compatibility
+	fn_compatible_mod_games
+	fn_compatible_mod_engines
+	fn_not_compatible_mod_games
+	if [ "${modeincompatiblegame}" == "1" ]; then
+		modcompatibility="0"
+	elif [ "${modcompatibleengine}" == "1" ]||[ "${modcompatiblegame}" == "1" ]; then
+		modcompatibility="1"
+	else
+		modcompatibility="0"
+	fi
+}
+
+## Directory management
+
+# Create mods files and directories if it doesn't exist
+fn_create_mods_dir(){
+	# Create mod install directory
+	if [ ! -d "${modinstalldir}" ]; then
+		echo "creating mods install directory ${modinstalldir}..."
+		mkdir -p "${modinstalldir}"
+		exitcode=$?
+		if [ ${exitcode} -ne 0 ]; then
+			fn_print_fail_eol_nl
+			fn_script_log_fatal "Creating mod download dir ${modinstalldir}"
+			core_exit.sh
+		else
+			fn_print_ok_eol_nl
+			fn_script_log_pass "Creating mod download dir ${modinstalldir}"
+		fi
+		sleep 0.5
+	fi
+
+	# Create lgsm/data/${modsinstalledlist}
+	if [ ! -f "${modsinstalledlistfullpath}" ]; then
+		touch "${modsinstalledlistfullpath}"
+		fn_script_log_info "Created ${modsinstalledlistfullpath}"
+	fi
+}
+
+# Create tmp download mod directory
+fn_mods_create_tmp_dir(){
+	if [ ! -d "${modstmpdir}" ]; then
+		mkdir -p "${modstmpdir}"
+		exitcode=$?
+		echo -ne "creating mod download directory ${modstmpdir}..."
+		if [ ${exitcode} -ne 0 ]; then
+			fn_print_fail_eol_nl
+			fn_script_log_fatal "Creating mod download directory ${modstmpdir}"
+			core_exit.sh
+		else
+			fn_print_ok_eol_nl
+			fn_script_log_pass "Creating mod download directory ${modstmpdir}"
+		fi
+	fi
+}
+
+# Remove the tmp mod download directory when finished
+fn_mods_clear_tmp_dir(){
+	if [ -d "${modstmpdir}" ]; then
+		echo -ne "clearing mod download directory ${modstmpdir}..."
+		rm -r "${modstmpdir}"
+		exitcode=$?
+		if [ ${exitcode} -ne 0 ]; then
+			fn_print_fail_eol_nl
+			fn_script_log_fatal "Clearing mod download directory ${modstmpdir}"
+			core_exit.sh
+		else
+			fn_print_ok_eol_nl
+			fn_script_log_pass "Clearing mod download directory ${modstmpdir}"
+		fi
+
+	fi
+	# Clear temp file list as well
+	if [ -f "${modsdir}/.removedfiles.tmp" ]; then
+		rm "${modsdir}/.removedfiles.tmp"
+	fi
+}
+
+# Counts how many mods were installed
+fn_mods_count_installed(){
+	if [ -f "${modsinstalledlistfullpath}" ]; then
+		installedmodscount="$(wc -l < "${modsinstalledlistfullpath}")"
+	else
+		installedmodscount=0
+	fi
+}
+
+# Exits if no mods were installed
+fn_mods_check_installed(){
+	# Count installed mods
+	fn_mods_count_installed
+	# If no mods are found
+	if [ ${installedmodscount} -eq 0 ]; then
+		echo ""
+		fn_print_failure_nl "No installed mods or addons were found"
+		echo " * Install mods using LGSM first with: ./${selfname} mods-install"
+		fn_script_log_fail "No installed mods or addons were found."
+		core_exit.sh
+	fi
+}
+
+# Checks that mod files list exists and isn't empty
+fn_check_mod_files_list(){
+	# File list must exist and be valid before any operation on it
+	if [ -f "${modsdir}/${modcommand}-files.txt" ]; then
+	# How many lines is the file list
+		modsfilelistsize="$(wc -l < "${modsdir}/${modcommand}-files.txt")"
+		# If file list is empty
+		if [ "${modsfilelistsize}" -eq 0 ]; then
+			fn_print_failure "${modcommand}-files.txt is empty"
+			echo "* Unable to remove ${modprettyname}"
+			fn_script_log_fatal "${modcommand}-files.txt is empty: Unable to remove ${modprettyname}."
+			core_exit.sh
+		fi
+	else
+		fn_print_failure "${modsdir}/${modcommand}-files.txt does not exist"
+		fn_script_log_fatal "${modsdir}/${modcommand}-files.txt does not exist: Unable to remove ${modprettyname}."
+		core_exit.sh
+	fi
+}
+
+## Database initialisation
+
+mods_list.sh
+fn_mods_available

+ 73 - 0
lgsm/functions/mods_list.sh

@@ -0,0 +1,73 @@
+#!/bin/bash
+# LGSM mods_list.sh function
+# Author: Daniel Gibbs
+# Contributor: UltimateByte
+# Website: https://gameservermanagers.com
+# Description: Lists and defines available mods for LGSM supported servers; works along with mods_core.sh.
+# Usage: To add a mod, you need to add an array variable following the guide to set proper values;
+# Usage: Then add this array to the mods_global_array.
+# Usage: If needed, you can scrape the download URL first.
+
+local commandname="MODS"
+local commandaction="List Mods"
+local function_selfname="$(basename $(readlink -f "${BASH_SOURCE[0]}"))"
+
+# Get a proper URL for mods that don't provide a good one (optional)
+fn_script_log_info "Retrieving latest mods URLs"
+# Metamod
+metamodscrapeurl="http://www.gsptalk.com/mirror/sourcemod"
+metamodlatestfile="$(wget "${metamodscrapeurl}/?MD" -q -O -| grep "mmsource" | grep "\-linux" | head -n1 | awk -F '>' '{ print $3 }' | awk -F '<' '{ print $1}')"
+metamoddownloadurl="http://cdn.probablyaserver.com/sourcemod/"
+metamodurl="${metamoddownloadurl}/${metamodlatestfile}"
+# Sourcemod
+sourcemodmversion="1.8"
+sourcemodscrapeurl="https://sm.alliedmods.net/smdrop/${sourcemodmversion}/sourcemod-latest-linux"
+sourcemodlatestfile="$(wget "${sourcemodscrapeurl}" -q -O -)"
+sourcemoddownloadurl="https://sm.alliedmods.net/smdrop/${sourcemodmversion}"
+sourcemodurl="${sourcemoddownloadurl}/${sourcemodlatestfile}"
+
+# Define mods information (required)
+
+# Separator name
+modseparator="MOD"
+
+# REQUIRED: mod_info_name=( MOD "modcommand" "Pretty Name" "URL" "filename" "modsubdirs" "LowercaseOn/Off" "/files/to/keep;" "/install/path" "ENGINES" "GAMES" "NOTGAMES" "AUTHOR_URL" "Short Description" )
+# Example 1) Well made mod: mod_info_name=( MOD "awesomemod" "This is an Awesome Mod" "https://awesomemod.com/latest.zip" "awesomemod.zip" "0" "LowercaseOff" "OVERWRITE" "${systemdir}/addons" "source;unity3d;" "GAMES" "NOTGAMES" "https://awesomemod.com/" "This mod knows that 42 is the answer" )
+# Example 2) Poorly made mod: mod_info_name=( MOD "stupidmod" "This is a stupid mod" "${crappymodurl}" "StupidMod.zip" "2" "LowercaseOn" "cfg;data/crappymod;" "${systemdir}" "source;" "GAMES" "Garry's mod;Counter-Strike: Source;" "This mod is dumber than dumb" )
+# None of those values can be empty
+# index | Usage
+# [0] 	| MOD: separator, all mods must begin with it
+# [1] 	| "modcommand": the LGSM name and command to install the mod (must be unique and lowercase)
+# [2] 	| "Pretty Name": the common name people use to call the mod that will be displayed to the user
+# [3] 	| "URL": link to the mod archive file; can be a variable previously defined while scraping a URL
+# [4] 	| "filename": the output filename
+# [5]	| "modsubdirs": in how many subdirectories is the mod (none is 0) (not used at release, but could be in the future)
+# [6]	| "LowercaseOn/Off": LowercaseOff or LowercaseOn: enable/disable converting extracted files and directories to lowercase (some games require it)
+# [7] 	| "modinstalldir": the directory in which to install the mode ( use LGSM dir variables such as ${systemdir})
+# [8]	| "/files/to/keep;", files & directories that should not be overwritten upon update, separated and ended with a semicolon; you can also use "OVERWRITE" value to ignore the value or "NOUPDATE" to disallow updating; for files to keep upon uninstall, see fn_mod_tidy_files_list from mods_core.sh
+# [9] 	| "Supported Engines;": list them according to LGSM ${engine} variables, separated and ended with a semicolon, or use ENGINES to ignore the value
+# [10] 	| "Supported Games;": list them according to LGSM ${gamename} variables, separated and ended with a semicolon, or use GAMES to ignore the value
+# [11]	| "Unsupported Games;": list them according to LGSM ${gamename} variables, separated and ended with a semicolon, or use NOTGAMES to ignore the value (useful to exclude a game when using Supported Engines)
+# [12]	| "AUTHOR_URL" is the author's website, displayed to the user when chosing mods to install
+# [13]	| "Short Description" a description showed to the user upon installation/removal
+
+# Source mods
+mod_info_metamod=( MOD "metamod" "MetaMod" "${metamodurl}" "${metamodlatestfile}" "0" "LowercaseOff" "${systemdir}" "addons/metamod/metaplugins.ini;" "source;" "GAMES" "NOTGAMES" "https://www.sourcemm.net" "Plugins Framework" )
+mod_info_sourcemod=( MOD "sourcemod" "SourceMod" "${sourcemodurl}" "${sourcemodlatestfile}" "0" "LowercaseOff" "${systemdir}" "cfg;addons/sourcemod/configs;" "source;" "GAMES" "NOTGAMES" "http://www.sourcemod.net" "Admin Features (requires MetaMod)" )
+# Garry's Mod Addons
+mod_info_ulib=( MOD "ulib" "ULib" "https://codeload.github.com/TeamUlysses/ulib/zip/master" "ulib-master.zip" "0" "LowercaseOff" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://ulyssesmod.net" "Complete Framework" )
+mod_info_ulx=( MOD "ulx" "ULX" "https://codeload.github.com/TeamUlysses/ulx/zip/master" "ulx-master.zip" "0" "LowercaseOff" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://ulyssesmod.net" "Admin Panel (requires ULib)" )
+mod_info_utime=( MOD "utime" "UTime" "https://github.com/TeamUlysses/utime/archive/master.zip" "utime-master.zip" "0" "LowercaseOff" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://ulyssesmod.net" "Keep track of players play time" )
+mod_info_uclip=( MOD "uclip" "UClip" "https://github.com/TeamUlysses/uclip/archive/master.zip" "uclip-master.zip" "0" "LowercaseOff" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://ulyssesmod.net" "An alternative to noclip" )
+mod_info_acf=( MOD "acf" "Armoured Combat Framework" "https://github.com/nrlulz/ACF/archive/master.zip" "acf-master.zip" "0" "LowercaseOn" "${systemdir}/addons" "acf-master/lua/acf/shared/guns;" "ENGINES" "Garry's Mod;" "NOTGAMES" "https://github.com/nrlulz/ACF" "Realistic Wepons & Engines" )
+mod_info_acf_missiles=( MOD "acfmissiles" "ACF Missiles" "https://github.com/Bubbus/ACF-Missiles/archive/master.zip" "acf-missiles-master.zip" "0" "LowercaseOn" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "https://github.com/Bubbus/ACF-Missiles" "More missiles for ACF" )
+mod_info_acf_advdupe2=( MOD "advdupe2" "Advanced Duplicator 2" "https://github.com/wiremod/advdupe2/archive/master.zip" "advdupe2-master.zip" "0" "LowercaseOn" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://www.wiremod.com" "Save your constructions" )
+mod_info_darkrp=( MOD "darkrp" "DarkRP" "https://github.com/FPtje/DarkRP/archive/master.zip" "darkrp-master.zip" "0" "LowercaseOn" "${systemdir}/addons" "OVERWRITE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://darkrp.com" "Most popular gamemode" )
+mod_info_darkrpmodification=( MOD "darkrpmodification" "DarkRP Modification" "https://github.com/FPtje/darkrpmodification/archive/master.zip" "darkrpmodification-master.zip" "0" "LowercaseOff" "${systemdir}/addons" "NOUPDATE" "ENGINES" "Garry's Mod;" "NOTGAMES" "http://darkrp.com" "Customize DarkRP settings" )
+# Oxidemod
+mod_info_rustoxide=( MOD "rustoxide" "Oxide for Rust" "https://raw.githubusercontent.com/OxideMod/Snapshots/master/Oxide-Rust.zip" "Oxide-Rust_Linux.zip" "0" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "Rust;" "NOTGAMES" "http://oxidemod.org/downloads/oxide-for-rust.1659" "Allows for the use of plugins" )
+mod_info_hwoxide=( MOD "hwoxide" "Oxide for Hurtworld" "https://raw.githubusercontent.com/OxideMod/Snapshots/master/Oxide-Hurtworld.zip" "Oxide-Hurtworld_Linux.zip" "0" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "Hurtworld;" "NOTGAMES" "http://oxidemod.org/downloads/oxide-for-hurtworld.1332" "Allows for the use of plugins" )
+mod_info_sdtdoxide=( MOD "sdtdoxide" "Oxide for 7 Days To Die" "https://raw.githubusercontent.com/OxideMod/Snapshots/master/Oxide-7DaysToDie.zip" "Oxide-7DaysToDie_Linux.zip" "0" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "7 Days To Die;" "NOTGAMES" "http://oxidemod.org/downloads/oxide-for-7-days-to-die.813" "Allows for the use of plugins" )
+
+# REQUIRED: Set all mods info into the global array
+mods_global_array=( "${mod_info_metamod[@]}" "${mod_info_sourcemod[@]}" "${mod_info_ulib[@]}" "${mod_info_ulx[@]}" "${mod_info_utime[@]}" "${mod_info_uclip[@]}" "${mod_info_acf[@]}" "${mod_info_acf_missiles[@]}" "${mod_info_acf_sweps[@]}" "${mod_info_advdupe2[@]}" "${mod_info_darkrp[@]}" "${mod_info_darkrpmodification[@]}" "${mod_info_rustoxide[@]}" "${mod_info_hwoxide[@]}" "${mod_info_sdtdoxide[@]}" )