Files
PSBBN-Definitive-English-Patch/scripts/Game-Installer.sh
CosmicScale 5e0e96d14b Update test
2025-08-14 13:41:17 +01:00

2563 lines
99 KiB
Bash
Executable File

#!/usr/bin/env bash
version_check="2.10"
# Set paths
TOOLKIT_PATH="$(pwd)"
ICONS_DIR="${TOOLKIT_PATH}/icons"
ARTWORK_DIR="${ICONS_DIR}/art"
VMC_ICON_DIR="${ICONS_DIR}/ico/vmc"
HELPER_DIR="${TOOLKIT_PATH}/scripts/helper"
ASSETS_DIR="${TOOLKIT_PATH}/scripts/assets"
POPSTARTER="${ASSETS_DIR}/POPStarter/POPSTARTER.ELF"
POPS_DIR="${ICONS_DIR}/POPS"
NEUTRINO_DIR="${ASSETS_DIR}/neutrino"
LOG_FILE="${TOOLKIT_PATH}/logs/game-installer.log"
MISSING_ART=${TOOLKIT_PATH}/logs/missing-art.log
MISSING_APP_ART=${TOOLKIT_PATH}/logs/missing-app-art.log
MISSING_ICON=${TOOLKIT_PATH}/logs/missing-icon.log
MISSING_VMC=${TOOLKIT_PATH}/logs/missing-vmc.log
GAMES_PATH="${TOOLKIT_PATH}/games"
CONFIG_FILE="${TOOLKIT_PATH}/scripts/gamepath.cfg"
OPL="${TOOLKIT_PATH}/scripts/storage/OPL"
PS1_LIST="${TOOLKIT_PATH}/scripts/tmp/ps1.list"
PS2_LIST="${TOOLKIT_PATH}/scripts/tmp/ps2.list"
ALL_GAMES="${TOOLKIT_PATH}/scripts/tmp/master.list"
current_branch=$(git rev-parse --abbrev-ref HEAD)
if ! git remote | xargs -n1 git ls-remote --heads 2>/dev/null | grep -q "refs/heads/$current_branch$"; then
echo "Testing is over. Please delete the ${TOOLKIT_PATH} folder"
echo "and clone the main repository."
echo
read -n 1 -s -r -p "Press any key to exit..." </dev/tty
rm -rf "${TOOLKIT_PATH}/scripts"
echo
fi
path_arg="$1"
prevent_sleep_start() {
if command -v xdotool >/dev/null; then
(
while true; do
xdotool key shift >/dev/null 2>&1
sleep 50
done
) &
SLEEP_PID=$!
elif command -v dbus-send >/dev/null; then
if dbus-send --session --dest=org.freedesktop.ScreenSaver \
--type=method_call --print-reply \
/ScreenSaver org.freedesktop.DBus.Introspectable.Introspect \
>/dev/null 2>&1; then
(
while true; do
dbus-send --session \
--dest=org.freedesktop.ScreenSaver \
--type=method_call \
/ScreenSaver org.freedesktop.ScreenSaver.SimulateUserActivity \
>/dev/null 2>&1
sleep 50
done
) &
SLEEP_PID=$!
elif dbus-send --session --dest=org.kde.screensaver \
--type=method_call --print-reply \
/ScreenSaver org.freedesktop.DBus.Introspectable.Introspect \
>/dev/null 2>&1; then
(
while true; do
dbus-send --session \
--dest=org.kde.screensaver \
--type=method_call \
/ScreenSaver org.kde.screensaver.simulateUserActivity \
>/dev/null 2>&1
sleep 50
done
) &
SLEEP_PID=$!
fi
fi
}
prevent_sleep_stop() {
if [[ -n "$SLEEP_PID" ]]; then
kill "$SLEEP_PID" 2>/dev/null
wait "$SLEEP_PID" 2>/dev/null
unset SLEEP_PID
fi
}
clean_up() {
# Remove unwanted directories inside $ICONS_DIR except 'art' and 'ico'
for item in "$ICONS_DIR"/*; do
if [ -d "$item" ] && [[ $(basename "$item") != art && $(basename "$item") != ico ]]; then
sudo rm -rf "$item"
fi
done
# Remove all directories inside ${GAMES_PATH}/APPS
find "${GAMES_PATH}/APPS" -mindepth 1 -maxdepth 1 -type d | while IFS= read -r dir; do
sudo rm -rf -- "$dir"
done
sudo umount -l "${OPL}" >> "${LOG_FILE}" 2>&1
# Remove listed files
sudo rm -rf "${ARTWORK_DIR}/tmp" "${ICONS_DIR}/ico/tmp" "${TOOLKIT_PATH}/scripts/tmp" 2>>"$LOG_FILE" \
|| { echo "Error: Cleanup failed. See ${LOG_FILE} for details."; exit 1; }
}
exit_script() {
prevent_sleep_stop
clean_up
if [[ -n "$path_arg" ]]; then
cp "${LOG_FILE}" "${path_arg}"
fi
}
trap 'echo; exit 130' INT
trap exit_script EXIT
error_msg() {
type=$1
error_1="$2"
error_2="$3"
error_3="$4"
error_4="$5"
echo
echo "$type: $error_1" | tee -a "${LOG_FILE}"
[ -n "$error_2" ] && echo "$error_2" | tee -a "${LOG_FILE}"
[ -n "$error_3" ] && echo "$error_3" | tee -a "${LOG_FILE}"
[ -n "$error_4" ] && echo "$error_4" | tee -a "${LOG_FILE}"
echo
if [ "$type" = "Error" ]; then
sudo umount -l "${OPL}" >> "${LOG_FILE}" 2>&1
read -n 1 -s -r -p "Press any key to return to the main menu..." </dev/tty
echo
exit 1;
else
read -n 1 -s -r -p "Press any key to continue..." </dev/tty
echo
fi
}
UNMOUNT_OPL() {
sync
if ! sudo umount -l "${OPL}" >> "${LOG_FILE}" 2>&1; then
error_msg "Error" "Failed to unmount $DEVICE."
fi
}
MOUNT_OPL() {
echo | tee -a "${LOG_FILE}"
echo "Mounting OPL partition..." >> "${LOG_FILE}" 2>&1
mkdir -p "${OPL}" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${OPL}."
sudo mount -o uid=$UID,gid=$(id -g) ${DEVICE}3 "${OPL}" >> "${LOG_FILE}" 2>&1
# Handle possibility host system's `mount` is using Fuse
if [ $? -ne 0 ] && hash mount.exfat-fuse; then
echo "Attempting to use exfat.fuse..." | tee -a "${LOG_FILE}"
sudo mount.exfat-fuse -o uid=$UID,gid=$(id -g) ${DEVICE}3 "${OPL}" >> "${LOG_FILE}" 2>&1
fi
if [ $? -ne 0 ]; then
error_msg "Error" "Failed to mount OPL partition."
fi
# Create necessary folders if they don't exist
for folder in APPS ART CFG CHT LNG THM VMC CD DVD bbnl; do
dir="${OPL}/${folder}"
[[ -d "$dir" ]] || mkdir -p "$dir" || {
error_msg "Error" "Failed to create $dir."
}
done
}
HDL_TOC() {
rm -f "$hdl_output"
hdl_output=$(mktemp)
if ! sudo "${HELPER_DIR}/HDL Dump.elf" toc "$DEVICE" 2>>"${LOG_FILE}" > "$hdl_output"; then
rm -f "$hdl_output"
error_msg "Error" "Failed to extract list of partitions." " " "APA partition could be broken on ${DEVICE}"
fi
}
PFS_COMMANDS() {
PFS_COMMANDS=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" >> "${LOG_FILE}" 2>&1)
if echo "$PFS_COMMANDS" | grep -q "Exit code is"; then
error_msg "Error" "PFS Shell returned an error. See ${LOG_FILE}"
fi
}
process_psu_files() {
local target_dir="$1"
if find "$target_dir" -maxdepth 1 -type f \( -iname "*.psu" \) | grep -q .; then
echo "Processing PSU files in: $target_dir" | tee -a "${LOG_FILE}"
for file in "$target_dir"/*.psu "$target_dir"/*.PSU; do
[ -e "$file" ] || continue # Skip if no PSU files exist
echo "Extracting $file..."
"${HELPER_DIR}/PSU Extractor.elf" "$file" >> "${LOG_FILE}" 2>&1
done
fi
}
POPS_SIZE_CKECK() {
# Get total size of VCD files only on PC
LOCAL_SIZE=0
while IFS= read -r file; do
if [[ -f "$POPS_FOLDER/$file" ]]; then
size=$(du --block-size=1M "$POPS_FOLDER/$file" | cut -f1)
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
error_msg "Error" "Failed to calclate the size of local .VCD files. See ${LOG_FILE}"
fi
LOCAL_SIZE=$((LOCAL_SIZE + size))
fi
done <<< "$files_only_in_local"
# Get total size of VCD files on PS2 drive
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
COMMANDS+="ls -l\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
ps1_size=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>/dev/null)
if echo "$ps1_size" | grep -q "Exit code is"; then
echo "$ps1_size" >> "${LOG_FILE}"
error_msg "Error" "PFS Shell returned an error. See ${LOG_FILE}"
fi
ps1_size=$(echo "$ps1_size" | grep -iE "\.vcd$" | sort)
# Sum the total size in bytes
REMOTE_SIZE=$(echo "$ps1_size" | awk '{sum += $2} END {print sum}')
# Round up to MB and MiB
remote_mb=$(awk -v size="$REMOTE_SIZE" 'BEGIN {printf "%d", (size + 1000000 - 1) / 1000000}')
POPS_SIZE=$((remote_mb + LOCAL_SIZE))
echo | tee -a "${LOG_FILE}"
echo "Total size of PS1 games: $POPS_SIZE MB" | tee -a "${LOG_FILE}"
# Get the POPS partition size in MB
HDL_TOC
POPS_PARTITION=$(grep '__\.POPS' "$hdl_output" | awk '{print $4}' | grep -oE '[0-9]+')
echo "Available space: ${POPS_PARTITION} MB"| tee -a "${LOG_FILE}"
# Check if POPS_SIZE is greater than POPS_PARTITION - 128
THRESHOLD=$((POPS_PARTITION - 128))
if [ "$POPS_SIZE" -gt "$THRESHOLD" ]; then
error_msg "Error" "Total size of PS1 games is ${POPS_SIZE} MB, exceeds available space of ${THRESHOLD} MB." " " "Remove some VCD files from the local POPS folder and try again."
fi
}
POPS_SYNC() {
echo | tee -a "${LOG_FILE}"
echo "Preparing to $INSTALL_TYPE PS1 games..." | tee -a "${LOG_FILE}"
rm -f "$POPS_FOLDER"/*.[eE][lL][fF] 2>> "${LOG_FILE}"
# Generate the local file list directly in a variable
local_files=$( { ls -1 "$POPS_FOLDER" | grep -Ei '\.VCD$' | sort; } 2>> "${LOG_FILE}" )
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
error_msg "Error" "Failed to create list of local .VCD files. See ${LOG_FILE}"
fi
# Build the commands for PFS Shell
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
COMMANDS+="ls\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
# Get the PS1 file list directly from PFS Shell output, filtered and sorted
ps1_files=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>/dev/null)
if echo "$ps1_files" | grep -q "Exit code is"; then
echo "$ps1_files" >> "${LOG_FILE}"
error_msg "Error" "PFS Shell returned an error. See ${LOG_FILE}"
fi
ps1_files=$(echo "$ps1_files" | grep -iE "\.vcd$" | sort)
if [ "$INSTALL_TYPE" = "copy" ] && [ -f "${OPL}/ps1.list" ]; then
# Create an array of POPS files for easy comparison
mapfile -t pops_array < <(echo "$ps1_files")
# Initialize a temporary file
temp_list="${TOOLKIT_PATH}/scripts/tmp/ps1.list.tmp"
# Track whether any POPS file is missing from ps1.list
missing_from_list=false
while IFS= read -r line; do
vcd_file=$(echo "$line" | awk -F '|' '{print $5}')
if printf '%s\n' "${pops_array[@]}" | grep -Fxq "$vcd_file"; then
echo "$line" >> "$temp_list"
fi
done < "${OPL}/ps1.list"
# Check if any file in __.POPS is missing from ps1.list
for pops_file in "${pops_array[@]}"; do
if ! grep -Fq "|$pops_file" "${OPL}/ps1.list"; then
missing_from_list=true
break
fi
done
if $missing_from_list; then
echo "A file in __.POPS is missing from ps1.list — deleting ps1.list"
rm -f "${OPL}/ps1.list"
else
[ -f "$temp_list" ] && ! cp "$temp_list" "${OPL}/ps1.list" 2>>"${LOG_FILE}" && error_msg "Error" "Failed to copy $temp_list to ${OPL}/ps1.list"
fi
fi
# Compute differences and store them in variables
files_only_in_local=$(comm -23 <(echo "$local_files") <(echo "$ps1_files"))
if [ "$INSTALL_TYPE" = "sync" ] || [ ! -f "${OPL}/ps1.list" ]; then
files_only_in_ps2=$(comm -13 <(echo "$local_files") <(echo "$ps1_files"))
if [ "$INSTALL_TYPE" != "sync" ] && [ -n "$files_only_in_ps2" ]; then
error_msg "Warning" "Could not find ps1.list. PS1 games will be synced instead."
fi
fi
cd "$POPS_FOLDER" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to POPS folder."
# Delete PS1 VCDs
if [ -n "$files_only_in_ps2" ]; then
echo | tee -a "${LOG_FILE}"
echo "Deleteing PS1 games:" | tee -a "${LOG_FILE}"
echo "$files_only_in_ps2" | tee -a "${LOG_FILE}"
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
# Add delete commands for files_only_in_ps2
if [ -n "$files_only_in_ps2" ]; then
while IFS= read -r file; do
COMMANDS+="rm \"$file\"\n"
done <<< "$files_only_in_ps2"
fi
COMMANDS+="umount\n"
COMMANDS+="exit"
# Execute the combined commands with PFS Shell
PFS_COMMANDS
else
if [ "$INSTALL_TYPE" = "sync" ]; then
echo | tee -a "${LOG_FILE}"
echo "No PS1 games to delete." | tee -a "${LOG_FILE}"
fi
fi
# Copy PS1 VCDs
if [ -z "$files_only_in_local" ]; then
echo | tee -a "${LOG_FILE}"
echo "No PS1 games to copy." | tee -a "${LOG_FILE}"
else
POPS_SIZE_CKECK
echo | tee -a "${LOG_FILE}"
echo "Copying PS1 games:" | tee -a "${LOG_FILE}"
echo "$files_only_in_local" | tee -a "${LOG_FILE}"
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
# Add put commands for files_only_in_local
if [ -n "$files_only_in_local" ]; then
while IFS= read -r file; do
COMMANDS+="put \"$file\"\n"
done <<< "$files_only_in_local"
fi
COMMANDS+="umount\n"
COMMANDS+="exit"
# Execute the combined commands with PFS Shell
echo | tee -a "${LOG_FILE}"
echo -n "Copying..."
PFS_COMMANDS
echo | tee -a "${LOG_FILE}"
echo "PS1 games copied successfully." | tee -a "${LOG_FILE}"
fi
cd ${TOOLKIT_PATH} 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to $TOOLKIT_PATH."
}
VMC_TITLE() {
local title="$1"
# Remove colons
title="${title//:/}"
local disc_number=""
if [[ "$title" =~ \(Disc\ [0-9]+\) ]]; then
disc_number="${BASH_REMATCH[0]}"
title="${title//$disc_number/}"
title="${title%" "}" # Trim trailing space
# Truncate to 24 chars if disc number present
if (( ${#title} > 24 )); then
title="${title:0:24}"
fi
else
# No disc number: truncate to 32 chars max
if (( ${#title} > 32 )); then
title="${title:0:32}"
fi
fi
# Split into words for top row
IFS=' ' read -r -a words <<< "$title"
# Build top line: add full words without exceeding 16 chars
local top=""
local top_len=0
for word in "${words[@]}"; do
local add_len=$(( ${#word} + (top_len > 0 ? 1 : 0) ))
if (( top_len + add_len <= 16 )); then
top+="${top:+ }$word"
((top_len += add_len))
else
break
fi
done
# Bottom line is remainder of title after top line
local bottom="${title:$top_len}"
bottom="${bottom#" "}" # Remove leading space
# If bottom is 1 char and top has more than one word, consider shifting last word
if (( ${#bottom} == 1 )); then
IFS=' ' read -r -a top_words <<< "$top"
if (( ${#top_words[@]} > 1 )); then
local last_word="${top_words[-1]}"
local new_top="${top% ${last_word}}"
local proposed_bottom="${last_word} $bottom"
if (( ${#proposed_bottom} <= 16 )); then
top="$new_top"
bottom="$proposed_bottom"
fi
fi
fi
if [[ -n "$disc_number" ]]; then
if (( ${#bottom} > 4 )); then
truncated_bottom="${bottom:0:4}"
truncated_bottom="${truncated_bottom%" "}" # Remove trailing space before ...
bottom="${truncated_bottom}... ${disc_number}"
else
bottom="${bottom:+$bottom }${disc_number}"
fi
else
if (( ${#bottom} > 16 )); then
bottom="${bottom:0:13}"
bottom="${bottom%" "}" # Remove trailing space
bottom="${bottom}..."
fi
fi
python3 "${HELPER_DIR}/txt_to_icon_sys.py" "${ASSETS_DIR}/POPStarter/icon.sys" "$top" "$bottom"
}
GROUP_VMC() {
if [ "$VMC_GROUP_FOLDER" = "GP_Konami JPN" ] || [ "$VMC_GROUP_FOLDER" = "GP_Konami PAL" ] || [ "$VMC_GROUP_FOLDER" = "GP_Konami USA" ]; then
cp "${VMC_ICON_DIR}/KONAMI.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Tomba! USA" ]; then
cp "${VMC_ICON_DIR}/TOMBA.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Tombi! PAL" ]; then
cp "${VMC_ICON_DIR}/TOMBI.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Tomba! JAP" ]; then
cp "${VMC_ICON_DIR}/TOMBA-JPN.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Square JAP" ] || [ "$VMC_GROUP_FOLDER" = "GP_Square USA" ]; then
cp "${VMC_ICON_DIR}/SQUARE.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Arc the Lad USA" ] || [ "$VMC_GROUP_FOLDER" = "GP_Arc the Lad JPN" ]; then
cp "${VMC_ICON_DIR}/ARK-THE-LAD.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Armored Core JPN" ] || [ "$VMC_GROUP_FOLDER" = "GP_Armored Core USA" ]; then
cp "${VMC_ICON_DIR}/ARMORED-CORE.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Gran Turismo JPN" ] || [ "$VMC_GROUP_FOLDER" = "GP_Gran Turismo PAL" ] || [ "$VMC_GROUP_FOLDER" = "GP_Gran Turismo USA" ]; then
cp "${VMC_ICON_DIR}/GRAN-TURISMO.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Tekken JPN" ] || [ "$VMC_GROUP_FOLDER" = "GP_Tekken PAL" ] || [ "$VMC_GROUP_FOLDER" = "GP_Tekken USA" ]; then
cp "${VMC_ICON_DIR}/TEKKEN.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Monster Rancher" ]; then
cp "${VMC_ICON_DIR}/MONSTER-RANCHER.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_Monster Farm JPN" ]; then
cp "${VMC_ICON_DIR}/MONSTER-FARM.ico" ./list.ico
elif [ "$VMC_GROUP_FOLDER" = "GP_PopoloCrois JPN" ]; then
cp "${VMC_ICON_DIR}/POPOLOCROIS.ico" ./list.ico
fi
}
CREATE_VMC() {
declare -A disc_groups
declare -A first_disc_folder
declare -A vmc_groups_by_id
current_group=""
echo | tee -a "${LOG_FILE}"
echo -n "Creating VMCs for PS1 games..." | tee -a "${LOG_FILE}"
if ! mkdir -p "${POPS_DIR}"; then
error_msg "Error" "Failed to create VMC folder."
fi
# First pass: Group file names by base title
exec 3< "$PS1_LIST"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
base_title="${title%%(Disc*}"
base_title="${base_title%" "}" # Remove trailing space
disc_groups["$base_title"]+="$title|$file_name"$'\n'
done
exec 3<&-
exec 3< "${HELPER_DIR}/vmc_groups.list"
while IFS= read -r line <&3; do
line="${line%%$'\r'}" # Remove trailing carriage return (CR)
[[ -z "$line" ]] && continue
if [[ "$line" == GP_* ]]; then
current_group="$line"
elif [[ $line =~ ^[A-Z]{4}_[0-9]{3}\.[0-9]{2} ]]; then
game_id="${line%%|*}"
vmc_groups_by_id["$game_id"]="$current_group"
fi
done
exec 3<&-
# Second pass: Create folders, DISCS.TXT, and VMCDIR.TXT
exec 3< "$PS1_LIST"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
folder_name="${file_name%.*}"
base_title="${title%%(Disc*}"
base_title="${base_title%" "}"
mkdir -p "${POPS_DIR}/$folder_name"
cd "${POPS_DIR}/$folder_name"
if ! cp "${ICONS_DIR}/ico/vmc/$game_id.ico" ./list.ico 2>/dev/null; then
cp "${ICONS_DIR}/ico/vmc/VMC.ico" ./list.ico
echo "$game_id $title" >> "${MISSING_VMC}"
fi
VMC_TITLE "$title"
# Prepare disc list for DISCS.TXT
IFS=$'\n' read -rd '' -a entries <<< "${disc_groups[$base_title]}"
if ((${#entries[@]} > 1)); then
# Determine first disc folder
first_entry="${entries[0]}"
first_file_name="${first_entry##*|}"
first_folder="${first_file_name%.*}"
# Prepare up to 4 lines for DISCS.TXT
disc_list=()
for ((i = 0; i < ${#entries[@]} && i < 4; i++)); do
disc_list+=("${entries[i]##*|}")
done
# Write DISCS.TXT in the first 4 folders only
for ((i = 0; i < ${#disc_list[@]}; i++)); do
disc_file_name="${entries[i]##*|}"
disc_folder="${disc_file_name%.*}"
mkdir -p "${POPS_DIR}/$disc_folder"
printf "%s\n" "${disc_list[@]}" > "${POPS_DIR}/$disc_folder/DISCS.TXT"
done
# Write VMCDIR.TXT in all folders
for disc_entry in "${entries[@]}"; do
disc_file_name="${disc_entry##*|}"
disc_folder="${disc_file_name%.*}"
mkdir -p "${POPS_DIR}/$disc_folder"
printf "%s" "$first_folder" > "${POPS_DIR}/$disc_folder/VMCDIR.TXT"
done
# Overwrite VMCDIR.TXT in all discs with the group ID if it exists and create group VMC
if [[ -n "${vmc_groups_by_id[$game_id]}" ]]; then
VMC_GROUP_FOLDER="${vmc_groups_by_id[$game_id]}"
mkdir -p "${POPS_DIR}/$VMC_GROUP_FOLDER"
cd "${POPS_DIR}/$VMC_GROUP_FOLDER"
GROUP_VMC
GP_TITLE="${vmc_groups_by_id[$game_id]#GP_}"
python3 "${HELPER_DIR}/txt_to_icon_sys.py" "${ASSETS_DIR}/POPStarter/icon.sys" "$GP_TITLE" "VMC Group"
for disc_entry in "${entries[@]}"; do
disc_file_name="${disc_entry##*|}"
disc_folder="${disc_file_name%.*}"
mkdir -p "${POPS_DIR}/$disc_folder"
printf "%s" "${vmc_groups_by_id[$game_id]}" > "${POPS_DIR}/$disc_folder/VMCDIR.TXT"
done
fi
else
# Check if game ID exists in VMC group mapping and make group VMC if necessary
if [[ -n "${vmc_groups_by_id[$game_id]}" ]]; then
VMC_GROUP_FOLDER="${vmc_groups_by_id[$game_id]}"
mkdir -p "${POPS_DIR}/$VMC_GROUP_FOLDER"
cd "${POPS_DIR}/$VMC_GROUP_FOLDER"
GROUP_VMC
GP_TITLE="${vmc_groups_by_id[$game_id]#GP_}"
python3 "${HELPER_DIR}/txt_to_icon_sys.py" "${ASSETS_DIR}/POPStarter/icon.sys" "$GP_TITLE" "VMC Group"
printf "%s" "${vmc_groups_by_id[$game_id]}" > "${POPS_DIR}/$folder_name/VMCDIR.TXT"
fi
fi
done
cd "${TOOLKIT_PATH}"
exec 3<&-
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __common\n"
COMMANDS+="cd POPS\n"
for dir in "$POPS_DIR"/*/; do
[ -d "$dir" ] || continue
VMC_FOLDER="$(basename "$dir")"
COMMANDS+="mkdir '${VMC_FOLDER}'\n"
COMMANDS+="cd '${VMC_FOLDER}'\n"
COMMANDS+="lcd '${POPS_DIR}/${VMC_FOLDER}'\n"
COMMANDS+="rm icon.sys\n"
COMMANDS+="put icon.sys\n"
COMMANDS+="rm list.ico\n"
COMMANDS+="put list.ico\n"
COMMANDS+="rm DISCS.TXT\n"
COMMANDS+="put DISCS.TXT\n"
COMMANDS+="rm VMCDIR.TXT\n"
COMMANDS+="put VMCDIR.TXT\n"
COMMANDS+="cd ..\n"
done
COMMANDS+="cd ..\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" >> "${LOG_FILE}" 2>&1
echo | tee -a "${LOG_FILE}"
}
OPL_SIZE_CKECK() {
if [ "$INSTALL_TYPE" = "sync" ]; then
opl_size=$(df -m --output=size "${OPL}" | tail -n 1 | awk '{$1=$1};1')
available_mb=$((opl_size - 128))
needed_mb=$(ls -l "${GAMES_PATH}/CD" "${GAMES_PATH}/DVD" | awk '{s+=$5} END {print int((s + 1048575) / 1048576)}')
elif [ "$INSTALL_TYPE" = "copy" ]; then
opl_freespace=$(df -m "${OPL}/" | awk 'NR==2 {print $4}')
available_mb=$((opl_freespace - 128))
cd_size=$(rsync -rL --ignore-existing --exclude=".*" --dry-run --out-format="%l" "${GAMES_PATH}/CD/" "${OPL}/CD/" | awk '{s+=$1} END {printf "%.0f\n", s / (1024*1024)}')
dvd_size=$(rsync -rL --ignore-existing --exclude=".*" --dry-run --out-format="%l" "${GAMES_PATH}/DVD/" "${OPL}/DVD/" | awk '{s+=$1} END {printf "%.0f\n", s / (1024*1024)}')
needed_mb=$((cd_size + dvd_size))
fi
if (( available_mb < needed_mb )); then
error_msg "Error" "Total size of PS2 games are ${needed_mb} MB, exceeds available space of ${available_mb} MB." " " "Remove some ISO/ZSO files from the local CD/DVD folders and try again."
fi
}
# Function to find available space
APA_SIZE_CHECK() {
HDL_TOC
# Extract the "used" value, remove "MB" and any commas
used=$(cat "$hdl_output" | awk '/used:/ {print $6}' | sed 's/,//; s/MB//')
capacity=129960
# Calculate available space (capacity - used)
available=$((capacity - used))
pp_max=$(((available / 8) - 1))
}
app_success_check() {
local name="$1"
if [ $exit_code -ne 0 ]; then
error_msg "Error" "Failed to update $name. See "${LOG_FILE}" for details."
else
echo | tee -a "${LOG_FILE}"
echo "Successfully updated $name." | tee -a "${LOG_FILE}"
fi
}
ps2_rsync_check() {
local type="$1"
# Check if PS2 sync/update failed
if [ $cd_status -ne 0 ] || [ $dvd_status -ne 0 ]; then
error_msg "Error" "Failed to $INSTALL_TYPE PS2 games. See ${LOG_FILE} for details."
else
echo | tee -a "${LOG_FILE}"
echo "PS2 games successfully $type." | tee -a "${LOG_FILE}"
fi
}
update_apps() {
local name="$1"
local source="$2"
local destination="$3"
local options="$4"
echo | tee -a "${LOG_FILE}"
echo "Checking for $name updates..." | tee -a "${LOG_FILE}"
local needs_update=false
if [[ "$name" == "NHDDL" || "$name" == "OPL" || "$name" == "POPStarter" ]]; then
if [ -f "$source" ] && [ -f "$destination" ]; then
local src_hash
local dst_hash
src_hash=$(md5sum "$source" | awk '{print $1}')
dst_hash=$(md5sum "$destination" | awk '{print $1}')
if [ "$src_hash" != "$dst_hash" ]; then
needs_update=true
fi
else
needs_update=true
fi
elif [[ "$name" == "Neutrino" ]]; then
if [[ -f "${OPL}/neutrino/version.txt" ]]; then
current_ver=$(<"${OPL}/neutrino/version.txt")
current_ver="${current_ver//v/}" # Remove 'v' from current version
fi
latest_ver=$(<"${NEUTRINO_DIR}/version.txt")
latest_ver="${latest_ver//v/}" # Remove 'v' from latest version
if [[ -n "$current_ver" ]]; then
echo "Current version is $current_ver" | tee -a "${LOG_FILE}"
fi
# Compare versions
if [[ "$(echo -e "$current_ver\n$latest_ver" | sort -V | tail -n 1)" != "$current_ver" ]]; then
needs_update=true
fi
else
local output
output=$(rsync $options --dry-run "$source" "$destination")
if [ $(echo "$output" | wc -l) -ne 1 ]; then
needs_update=true
fi
fi
if [ "$needs_update" = true ]; then
echo "Updating $name..." | tee -a "${LOG_FILE}"
rsync $options "$source" "$destination" >>"${LOG_FILE}" 2>&1
exit_code=${PIPESTATUS[0]}
app_success_check "$name"
else
echo "$name is already up-to-date." | tee -a "${LOG_FILE}"
fi
}
install_pops() {
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __common\n"
COMMANDS+="ls\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
pops_folder=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>/dev/null)
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __common\n"
COMMANDS+="cd POPS\n"
COMMANDS+="ls\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
pops_files=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>/dev/null)
if echo "$pops_folder" | grep -q "POPS/"; then
mkfolder="NO"
else
mkfolder="YES"
fi
if echo "$pops_folder" | grep -q "POPS/" && echo "$pops_files" | grep -q "POPS\.ELF" && echo "$pops_files" | grep -q "IOPRP252\.IMG"; then
echo "POPS-binaries are already installed."| tee -a "${LOG_FILE}"
else
echo "Checking for POPS binaries..." | tee -a "${LOG_FILE}"
# Check POPS files exist
if [[ -f "${ASSETS_DIR}/POPS-binaries-main/POPS.ELF" && -f "${ASSETS_DIR}/POPS-binaries-main/IOPRP252.IMG" ]]; then
echo | tee -a "${LOG_FILE}"
echo "Both POPS.ELF and IOPRP252.IMG exist in ${ASSETS_DIR}." | tee -a "${LOG_FILE}"
echo "Skipping download." | tee -a "${LOG_FILE}"
else
echo "One or both files are missing in ${ASSETS_DIR}." | tee -a "${LOG_FILE}"
# Check if POPS-binaries-main.zip exists
if [[ -f "${ASSETS_DIR}/POPS-binaries-main.zip" && ! -f "${ASSETS_DIR}/POPS-binaries-main.zip.st" ]]; then
echo "POPS-binaries-main.zip found in ${ASSETS_DIR}. Extracting..." | tee -a "${LOG_FILE}"
if ! unzip -o "${ASSETS_DIR}/POPS-binaries-main.zip" -d "${ASSETS_DIR}" >> "${LOG_FILE}" 2>&1; then
error_msg "Warning" "Failed to extract POPS binaries"
fi
else
echo "Downloading POPS binaries..." | tee -a "${LOG_FILE}"
if ! axel -a https://archive.org/download/pops-binaries-PS2/POPS-binaries-main.zip -o "${ASSETS_DIR}"; then
error_msg "Warning" "Failed to download POPS binaries."
fi
if ! unzip -o "${ASSETS_DIR}/POPS-binaries-main.zip" -d "${ASSETS_DIR}" >> "${LOG_FILE}" 2>&1; then
error_msg "Warning" "Failed to extract POPS binaries"
fi
fi
# Check if both POPS.ELF and IOPRP252.IMG exist after extraction
if [[ -f "${ASSETS_DIR}/POPS-binaries-main/POPS.ELF" && -f "${ASSETS_DIR}/POPS-binaries-main/IOPRP252.IMG" ]]; then
echo "POPS binaries successfully extracted." | tee -a "${LOG_FILE}"
else
error_msg "Warning" "One or both files (POPS.ELF, IOPRP252.IMG) are missing after extraction." "Without these files PS1 games will not be playable."
fi
fi
echo "Installing POPS binaries..." | tee -a "${LOG_FILE}"
# Copy POPS files to __common
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __common\n"
if [[ "$mkfolder" == "YES" ]]; then
COMMANDS+="mkdir POPS\n"
fi
COMMANDS+="cd POPS\n"
COMMANDS+="lcd '${ASSETS_DIR}/POPS-binaries-main'\n"
COMMANDS+="put POPS.ELF\n"
COMMANDS+="put IOPRP252.IMG\n"
COMMANDS+="cd /\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
PFS_COMMANDS
echo "POPS-binaries successfully installed." | tee -a "${LOG_FILE}"
fi
}
install_elf() {
local dir=$1
# Check if any ELF files exist in the source directory
if ! find "${dir}/APPS" -maxdepth 1 -type f \( -iname "*.elf" \) | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "No ELF files to install in: ${dir}/APPS" | tee -a "${LOG_FILE}"
else
echo | tee -a "${LOG_FILE}"
echo "Processing ELF files in: ${dir}/APPS/"
for file in "${dir}/APPS/"*.elf "${dir}/APPS/"*.ELF; do
[ -e "$file" ] || continue # Skip if no ELF files exist
# Extract filename without path and extension
elf=$(basename "$file")
elf_no_ext="${elf%.*}"
echo "Installing ${dir}/APPS/$elf..." | tee -a "${LOG_FILE}"
app_name="${elf_no_ext%%(*}" # Remove anything after an open bracket '('
app_name="${app_name%%[Vv][0-9]*}" # Remove versioning (e.g., v12 or V12)
app_name=$(echo "$app_name" | sed -E 's/[cC][oO][mM][pP][rR][eE][sS][sS][eE][dD].*//') # Remove "compressed"
app_name=$(echo "$app_name" | sed -E 's/[pP][aA][cC][kK][eE][dD].*//') # Remove "packed"
app_name=$(echo "$app_name" | sed 's/\.*$//') # Trim trailing full stops
AppDB_check=$(echo "$app_name" | sed 's/[ _-]//g' | tr 'a-z' 'A-Z')
# Check $HELPER_DIR/AppDB.csv for match in first column to $AppDB_check, set $title based on second column from file if found. If no match found, set $title with the remaining code
match=$(awk -F'|' -v key="$AppDB_check" '$1 && index(key, $1) == 1 {print $2; exit}' "$HELPER_DIR/AppDB.csv")
if [[ -n "$match" ]]; then
title="$match"
else
# Use the processed name if no match is found
app_name="${app_name//[_-]/ }" # Replace underscores and hyphens with spaces
app_name="${app_name%"${app_name##*[![:space:]]}"}" # Trim trailing spaces again
app_name=$(echo "$app_name" | sed 's/\.*$//') # Trim trailing full stops again
app_name_before=$(echo "$app_name") # Save the string
app_name=$(echo "$app_name" | sed 's/\([a-z]\)\([A-Z]\)/\1 \2/g') # Add a space before capital letters when preceded by a lowercase letter
# Check if spaces were added by comparing before and after
if [[ "$app_name" != "$app_name_before" ]]; then
space_added=true
else
space_added=false
fi
# Process for title case and exceptions
input_str="$app_name"
# List of terms to ensure spaces before and after
terms=("3d" "3D" "ps2" "PS2" "ps1" "PS1")
# Loop over the terms
for term in "${terms[@]}"; do
input_str="${input_str//${term}/ ${term}}" # Ensure space before the term
input_str="${input_str//${term}/${term} }" # Ensure space after the term
done
# Special case for "hdd" and "HDD" - add spaces only if the string is longer than 5 characters
if [[ ${#input_str} -gt 5 ]]; then
input_str="${input_str//hdd/ hdd }"
input_str="${input_str//HDD/ HDD }"
fi
# Check if the string contains any lowercase letters
if ! echo "$input_str" | grep -q '[a-z]'; then
input_str="${input_str,,}" # Convert the entire string to lowercase
fi
result=""
# Define words to exclude from uppercase conversion (only consonant-only words)
exclude_list="by cry cyst crypt dry fly fry glyph gym gypsy hymn lynx my myth myrrh ply pry rhythm shy sky spy sly sty sync tryst why wry"
# Now process each word
for word in $input_str; do
# Handle words 3 characters or shorter, but only if no space was added by sed
if [[ ${#word} -le 3 ]] && ! $space_added && ! echo "$exclude_list" | grep -wi -q "$word"; then
result+=" ${word^^}" # Convert to uppercase
# Handle consonant-only words (only if not in exclusion list)
elif [[ "$word" =~ ^[b-df-hj-np-tv-z0-9]+$ ]] && ! echo "$exclude_list" | grep -w -q "$word"; then
result+=" ${word^^}" # Uppercase if the word is consonant-only and not in the exclusion list
else
result+=" ${word^}" # Capitalize first letter for all other words
fi
title="${result# }"
done
# Remove leading space and ensure no double spaces are left
result="${result#"${result%%[![:space:]]*}"}" # Remove leading spaces
title=$(echo "$result" | sed 's/ / /g') # Replace double spaces with single spaces
fi
title_id=$(echo "$title" | tr '[:lower:]' '[:upper:]' | tr -cd 'A-Z0-9' | cut -c1-11) # Replace spaces with underscores & capitalize
# Create the new folder in the destination directory
elf_dir="${dir}/APPS/$title_id"
mkdir -p "${elf_dir}" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create directory $elf_dir."
if [[ $dir == $GAMES_PATH ]]; then
cp "${dir}/APPS/$elf" "${elf_dir}" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to copy $elf to $elf_dir."
elif [[ $dir == $OPL ]]; then
mv "${dir}/APPS/$elf" "${elf_dir}" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to move $elf to $elf_dir."
fi
if [[ "$title_id" == "LAUNCHDISC" ]]; then
publisher="github.com/cosmicscale"
elif [[ "$title_id" == "HDDOSD" ]]; then
publisher="Sony Computer Entertainment"
elif [[ "$title_id" == "BBNAVIGATOR" ]]; then
publisher="Sony Computer Entertainment"
elif [[ "$title_id" == "LAUNCHELF" ]]; then
publisher="israpps.github.io"
title="wLaunchELF 4.43x_isr-EXFAT-MMCE"
else
publisher=""
fi
cat > "${elf_dir}/title.cfg" <<EOL
title=$title
boot=$elf
Title=$title
CfgVersion=8
Developer=$publisher
Genre=Homebrew
EOL
done
fi
}
activate_python() {
echo >> "${LOG_FILE}"
echo "Activating Python virtual environment..." >> "${LOG_FILE}"
echo
echo -n "Preparing to $INSTALL_TYPE PS2 games..."
sleep 5
echo | tee -a "${LOG_FILE}"
# Try activating the virtual environment twice before failing
if ! source "${TOOLKIT_PATH}/scripts/venv/bin/activate" 2>>"${LOG_FILE}"; then
echo -n "Failed to activate the Python virtual environment. Retrying..." | tee -a "${LOG_FILE}"
sleep 2
echo | tee -a "${LOG_FILE}"
if ! source "${TOOLKIT_PATH}/scripts/venv/bin/activate" 2>>"${LOG_FILE}"; then
error_msg "Error" "Failed to activate the Python virtual environment."
fi
fi
}
convert_zso() {
if [[ "$LAUNCHER" != "NEUTRINO" ]]; then
return
fi
if [[ "$INSTALL_TYPE" == "sync" ]]; then
search_dirs=("${GAMES_PATH}/CD" "${GAMES_PATH}/DVD")
else
search_dirs=("${GAMES_PATH}/CD" "${GAMES_PATH}/DVD" "${OPL}/CD" "${OPL}/DVD")
fi
# Only run if .zso files exist
if find "${search_dirs[@]}" -type f -iname "*.zso" | grep -q .; then
error_msg "Warning" "Games in the compressed ZSO format have been found." "Neutrino does not support compressed ZSO files." " " "ZSO files will be converted to ISO files before proceeding."
# Convert ZSO to ISO
while IFS= read -r -d '' zso_file; do
iso_file="${zso_file%.*}.iso"
echo "Converting: $zso_file -> $iso_file" | tee -a "${LOG_FILE}"
python3 -u "${HELPER_DIR}/ziso.py" -c 0 "$zso_file" "$iso_file" | tee -a "${LOG_FILE}"
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
rm -f "$iso_file"
error_msg "Error" "Failed to uncompress $zso_file"
fi
rm -f "$zso_file"
done < <(find "${search_dirs[@]}" -type f -iname "*.zso" -print0)
fi
}
PP_NAME() {
# Format game id correctly for partition
title_id=$(echo "$game_id" | sed -E 's/_(...)\./-\1/;s/\.//')
# Sanitize title by keeping only uppercase A-Z, 0-9, and underscores, and removing any trailing underscores
sanitized_title=$(echo "$title" | sed 's/²/2/g; s/³/3/g' | iconv -f UTF-8 -t ASCII//TRANSLIT | tr 'a-z' 'A-Z' | sed 's/[^A-Z0-9]/_/g' | sed 's/^_//; s/_$//; s/__*/_/g')
PARTITION_LABEL=$(printf "PP.%s.%s" "$title_id" "$sanitized_title" | cut -c 1-32 | sed 's/_$//')
}
create_info_sys() {
local title="$1"
local title_id="$2"
local publisher="$3"
local content_type="255"
if [ "$title_id" = "BBNAVIGATOR" ]; then
content_type="0"
fi
cat > "$info_sys_filename" <<EOL
title = $title
title_id = $title_id
title_sub_id = 0
release_date =
developer_id =
publisher_id = $publisher
note =
content_web =
image_topviewflag = 0
image_type = 0
image_count = 1
image_viewsec = 600
copyright_viewflag = 0
copyright_imgcount = 0
genre =
parental_lock = 1
effective_date = 0
expire_date = 0
violence_flag = 0
content_type = $content_type
content_subtype = 0
EOL
if [ -f "$info_sys_filename" ]; then
echo "Created: $info_sys_filename" | tee -a "${LOG_FILE}"
else
error_msg "Error" "Failed to create $info_sys_filename"
fi
}
create_icon_sys() {
local title="$1"
local publisher="$2"
cat > "$icon_sys_filename" <<EOL
PS2X
title0=$title
title1=$publisher
bgcola=58
bgcol0=0,3,43
bgcol1=0,0,10
bgcol2=1,0,9
bgcol3=0,1,19
lightdir0=1.0,-1.0,1.0
lightdir1=-1.0,1.0,-1.0
lightdir2=0.0,0.0,0.0
lightcolamb=64,64,64
lightcol0=64,64,64
lightcol1=16,16,16
lightcol2=0,0,0
uninstallmes0=
uninstallmes1=
uninstallmes2=
EOL
if [ -f "$icon_sys_filename" ]; then
echo "Created: $icon_sys_filename" | tee -a "${LOG_FILE}"
else
error_msg "Error" "Failed to create $icon_sys_filename"
fi
}
create_bbnl_cfg() {
local file_name="$1"
local title_id="$2"
local arg="$3"
{
echo "file_name=$file_name"
echo "title_id=$title_id"
echo "launcher=ELF"
if [ -n "$arg" ]; then
echo "arg=$arg"
fi
} > "$bbnl_cfg"
if [ -f "$bbnl_cfg" ]; then
echo "Created: $bbnl_cfg" | tee -a "${LOG_FILE}"
else
error_msg "Error" "Failed to create $bbnl_cfg"
fi
}
APP_ART() {
png_file="${ARTWORK_DIR}/${title_id}.png"
# Copy the matching PNG file from ART_DIR, or default to APP.png
if [ -f "$png_file" ]; then
cp "$png_file" "$dir/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $dir/jkt_001.png" | tee -a "${LOG_FILE}"
cp "$png_file" "${GAMES_PATH}/ART/${elf}_COV.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create ${GAMES_PATH}/ART/${elf}_COV.png. See ${LOG_FILE} for details."
echo "Created: ${GAMES_PATH}/ART/${elf}_COV.png" | tee -a "${LOG_FILE}"
else
echo "Artwork not found locally for $title_id. Attempting to download from the PSBBN art database..." | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="$png_file" \
"https://raw.githubusercontent.com/CosmicScale/psbbn-art-database/main/apps/${title_id}.png"
if [[ -s "$png_file" ]]; then
echo "Successfully downloaded artwork for $title_id" | tee -a "${LOG_FILE}"
cp "$png_file" "$dir/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $dir/jkt_001.png" | tee -a "${LOG_FILE}"
cp "$png_file" "${GAMES_PATH}/ART/${elf}_COV.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create ${GAMES_PATH}/ART/${elf}_COV.png. See ${LOG_FILE} for details."
echo "Created: ${GAMES_PATH}/ART/${elf}_COV.png" | tee -a "${LOG_FILE}"
else
rm -f "$png_file"
cp "$ARTWORK_DIR/APP.png" "$dir/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $dir/jkt_001.png using default image." | tee -a "${LOG_FILE}"
echo "$title_id,$title,$elf" >> "${MISSING_APP_ART}"
fi
fi
}
SPLASH() {
clear
cat << "EOF"
_____ _____ _ _ _
| __ \ |_ _| | | | | |
| | \/ __ _ _ __ ___ ___ | | _ __ ___| |_ __ _| | | ___ _ __
| | __ / _` | '_ ` _ \ / _ \ | || '_ \/ __| __/ _` | | |/ _ \ '__|
| |_\ \ (_| | | | | | | __/ _| || | | \__ \ || (_| | | | __/ |
\____/\__,_|_| |_| |_|\___| \___/_| |_|___/\__\__,_|_|_|\___|_|
EOF
}
mkdir -p "${TOOLKIT_PATH}/logs" >/dev/null 2>&1
if ! echo "########################################################################################################" | tee -a "${LOG_FILE}" >/dev/null 2>&1; then
sudo rm -f "${LOG_FILE}"
if ! echo "########################################################################################################" | tee -a "${LOG_FILE}" >/dev/null 2>&1; then
error_msg "Error" "Cannot create log file."
fi
fi
date >> "${LOG_FILE}"
echo >> "${LOG_FILE}"
echo "Tootkit path: $TOOLKIT_PATH" >> "${LOG_FILE}"
echo >> "${LOG_FILE}"
cat /etc/*-release >> "${LOG_FILE}" 2>&1
echo >> "${LOG_FILE}"
echo "Path: $path_arg" >> "${LOG_FILE}"
echo >> "${LOG_FILE}"
clear
clean_up
sudo rm -f "${MISSING_ART}" "${MISSING_APP_ART}" "${MISSING_ICON}" "${MISSING_VMC}" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to remove missing artwork files. See ${LOG_FILE} for details."
mkdir -p "${TOOLKIT_PATH}/scripts/tmp" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create tmp folder. See ${LOG_FILE} for details."
DEVICE=$(sudo blkid -t TYPE=exfat | grep OPL | awk -F: '{print $1}' | sed 's/[0-9]*$//')
if [[ -z "$DEVICE" ]]; then
clear
error_msg "Error" "Unable to detect the PS2 drive. Please ensure the drive is properly connected." "If this is your first time using the installer, select 'Install PSBBN' from the main menu."
fi
echo "OPL partition found on $DEVICE" >> "${LOG_FILE}"
SPLASH
# Find all mounted volumes associated with the device
mounted_volumes=$(lsblk -ln -o MOUNTPOINT "$DEVICE" | grep -v "^$")
# Iterate through each mounted volume and unmount it
echo "Unmounting volumes associated with $DEVICE..." >> "${LOG_FILE}"
for mount_point in $mounted_volumes; do
echo "Unmounting $mount_point..." >> "${LOG_FILE}"
if sudo umount "$mount_point"; then
echo "Successfully unmounted $mount_point." >> "${LOG_FILE}"
else
error_msg "Error" "Failed to unmount $mount_point. Please unmount manually."
fi
done
HDL_TOC
MOUNT_OPL
psbbn_version=$(head -n 1 "${OPL}/version.txt" 2>/dev/null)
# Compare using sort -V
if [ "$(printf '%s\n' "$psbbn_version" "$version_check" | sort -V | head -n1)" != "$version_check" ]; then
echo "Warning: Your PSBBN Definitive Patch version ($psbbn_version) is older than the recommended version ($version_check)."
echo "It is strongly recommended to update by selecting 'Install PSBBN' from the main menu."
echo "Proceed with caution."
echo
while true; do
read -rp "Do you want to continue anyway? [Y]es / [N]o: " response
case "$response" in
[Yy]* )
rm -f "${OPL}/conf_apps.cfg" || error_msg "Error" "Failed to delete ${OPL}/conf_apps.cfg."
break
;;
[Nn]* )
exit 0
;;
* )
echo "Please answer Y (yes) or N (no)."
;;
esac
done
fi
# Check if the Python virtual environment exists
if [ -f "./scripts/venv/bin/activate" ]; then
echo "The Python virtual environment exists." >> "${LOG_FILE}"
else
error_msg "Error" "The Python virtual environment does not exist. Run 01-Setup.sh and try again."
fi
if [[ -n "$path_arg" ]]; then
if [[ -d "$path_arg" ]]; then
GAMES_PATH="$path_arg"
fi
elif [[ -f "$CONFIG_FILE" && -s "$CONFIG_FILE" ]]; then
cfg_path="$(<"$CONFIG_FILE")"
if [[ -d "$cfg_path" ]]; then
GAMES_PATH="$cfg_path"
fi
fi
if [[ -z "$path_arg" ]]; then
echo
echo "Games folder: $GAMES_PATH" | tee -a "${LOG_FILE}"
echo
while true; do
read -p "Would you like to change the location of the local games folder? (y/n): " answer
case "$answer" in
[Yy])
echo
read -p "Enter new path for games folder: " new_path
if [[ -d "$new_path" ]]; then
# Remove trailing slash unless it's the root directory
new_path="${new_path%/}"
[[ "$new_path" == "" ]] && new_path="/"
GAMES_PATH="$new_path"
echo "$GAMES_PATH" > "$CONFIG_FILE"
break
else
echo "Invalid path. Please try again." | tee -a "${LOG_FILE}"
echo
fi
;;
[Nn])
break
;;
*)
echo
echo "Please enter y or n."
;;
esac
done
fi
# Create necessary folders if they don't exist
for folder in APPS ART CFG CHT LNG THM VMC POPS CD DVD; do
dir="${GAMES_PATH}/${folder}"
[[ -d "$dir" ]] || mkdir -p "$dir" || {
error_msg "Error" "Failed to create $dir. Make sure you have write permissions to $GAMES_PATH"
}
done
# Check if GAMES_PATH is custom
if [[ "${GAMES_PATH}" != "${TOOLKIT_PATH}/games" ]]; then
echo "Using custom game path." >> "${LOG_FILE}"
cp "${TOOLKIT_PATH}/games/APPS/"{BOOT.ELF,Launch-Disc.elf,HDD-OSD.elf,PSBBN.ELF} "${GAMES_PATH}/APPS" >> "${LOG_FILE}" 2>&1
else
echo "Using default game path." >> "${LOG_FILE}"
fi
POPS_FOLDER="${GAMES_PATH}/POPS"
SPLASH
echo "Choose an install option:"
echo
echo " 1) Synchronize All Games and Apps:"
echo
echo " - Installs all games and apps currently found in the games folder on your PC."
echo " - Deletes any games or apps from the PS2 drive that are not present in the"
echo " games folder, ensuring the PS2 drive matches the contents of your PC."
echo
echo " WARNING: Any games and apps that are not in the games folder on your PC will be"
echo " permanently removed from the PS2 drive during synchronization."
echo
echo " 2) Add Additional Games and Apps:"
echo
echo " - Installs new games and apps found in the games folder on your PC."
echo " - Scans for newly added or removed games and apps, then updates the game list"
echo " in the PSBBN Game Collection and HDD-OSD accordingly."
echo
while true; do
read -p "Enter 1 or 2: " choice
case "$choice" in
1) INSTALL_TYPE="sync" DESC1="Synchronize"; break ;;
2) INSTALL_TYPE="copy" DESC1="Add Games and Apps"; break ;;
*) echo; echo "Invalid choice. Please enter 1 or 2." ;;
esac
done
if [ "$INSTALL_TYPE" = "sync" ] && \
! find "${GAMES_PATH}/POPS" -maxdepth 1 -type f -iname "*.vcd" -print -quit | grep -q . && \
! find "${GAMES_PATH}/CD" -maxdepth 1 -type f \( -iname "*.iso" -o -iname "*.zso" \) -print -quit | grep -q . && \
! find "${GAMES_PATH}/DVD" -maxdepth 1 -type f \( -iname "*.iso" -o -iname "*.zso" \) -print -quit | grep -q .; then
echo
echo "Warning: No games found in the games folder: ${GAMES_PATH}"
echo "All games on the PS2 drive will be deleted."
echo
while true; do
read -p "Are you sure you wish to continue? (y/n): " confirm
case "$confirm" in
[Yy]) break ;;
[Nn]) echo "Operation cancelled."; exit 1 ;;
*) echo; echo "Please enter y or n." ;;
esac
done
fi
SPLASH
echo "Please choose a game launcher:"
echo
echo " 1) Open PS2 Loader (OPL)"
echo
echo " - 100% open-source game and application loader:"
echo " https://github.com/ps2homebrew/Open-PS2-Loader"
echo
echo " 2) Neutrino"
echo
echo " - Small, fast, and modular PS2 device emulator:"
echo " https://github.com/rickgaiser/neutrino"
echo
while true; do
read -p "Enter 1 or 2: " choice
case "$choice" in
1) LAUNCHER="OPL"; DESC2="Open PS2 Loader (OPL)"; break ;;
2) LAUNCHER="NEUTRINO"; DESC2="Neutrino"; break ;;
*) echo; echo "Invalid choice. Please enter 1 or 2." ;;
esac
done
ps1_games_found=false
# Only populate ps1_games if INSTALL_TYPE=copy
if [ "$INSTALL_TYPE" = "copy" ]; then
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
COMMANDS+="ls -l\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
ps1_games=$(echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>/dev/null)
if echo "$ps1_games" | grep -qi '\.vcd$'; then
ps1_games_found=true
fi
fi
# Check conditions for sync or copy
if { [ "$INSTALL_TYPE" = "sync" ] && find "${GAMES_PATH}/POPS" -maxdepth 1 -type f -iname "*.vcd" -print -quit 2>/dev/null | grep -q .; } \
|| { [ "$INSTALL_TYPE" = "copy" ] && { find "${GAMES_PATH}/POPS" -maxdepth 1 -type f -iname "*.vcd" -print -quit 2>/dev/null | grep -q . || [ "$ps1_games_found" = true ]; }; }; then
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __common\n"
COMMANDS+="cd POPS\n"
COMMANDS+="lcd '${ASSETS_DIR}/POPStarter'\n"
SPLASH
echo "Would you like to enable 'HDTVFIX' for PS1 games?"
echo
echo "Enable this if your TV cannot display 240p and PS1 games show a blank screen."
echo
while true; do
read -p "Yes or No (y/n): " HDTVFIX
case "$HDTVFIX" in
[Yy])
COMMANDS+="rm CHEATS.TXT\n"
COMMANDS+="put CHEATS.TXT\n"
break
;;
[Nn])
COMMANDS+="rm CHEATS.TXT\n"
break
;;
*)
echo
echo "Please enter y or n."
;;
esac
done
COMMANDS+="umount\n"
COMMANDS+="exit"
PFS_COMMANDS
fi
SPLASH
echo "PS2 Drive Detected: $DEVICE" | tee -a "${LOG_FILE}"
echo "Games Folder: $GAMES_PATH" | tee -a "${LOG_FILE}"
echo "Install Type: $DESC1" | tee -a "${LOG_FILE}"
echo "Game Launcher: $DESC2" | tee -a "${LOG_FILE}"
if [ -n "$HDTVFIX" ]; then
case "$HDTVFIX" in
[Yy]) HDTVFIX="Yes" ;;
[Nn]) HDTVFIX="No" ;;
esac
echo "HDTV fix for PS1 Games: $HDTVFIX"
fi
echo
read -n 1 -s -r -p "Press any key to continue..."
echo
prevent_sleep_start
# Delete existing BBL partitions
HDL_TOC
delete_partition=$(grep -o 'PP\.[^ ]\+' "$hdl_output")
echo >> "${LOG_FILE}"
echo "Existing PP Partitions:" >> "${LOG_FILE}"
echo "$delete_partition" >> "${LOG_FILE}"
if [ -n "$delete_partition" ]; then
COMMANDS="device ${DEVICE}\n"
while IFS= read -r partition; do
COMMANDS+="rmpart ${partition}\n"
done <<< "$delete_partition"
COMMANDS+="exit"
echo | tee -a "${LOG_FILE}"
echo "Deleting PP partitions..." | tee -a "${LOG_FILE}"
PFS_COMMANDS
HDL_TOC
delete_partition=$(grep -o 'PP\.[^ ]\+' "$hdl_output")
if [ -n "$delete_partition" ]; then
echo "Unable to delete the following partitions:"
echo $delete_partition
error_msg "Error" "Failed to delete existing PP partitions."
else
echo "Existing PP partitions sucessfully deleted." | tee -a "${LOG_FILE}"
fi
else
echo "No PP partitions to delete." | tee -a "${LOG_FILE}"
fi
update_apps "POPStarter" "${POPSTARTER}" "${OPL}/bbnl/POPSTARTER.ELF" "-ut --progress"
install_pops
update_apps "OPL" "${ASSETS_DIR}/OPL/OPNPS2LD.ELF" "${OPL}/bbnl/OPNPS2LD.ELF" "-ut --progress"
update_apps "NHDDL" "${ASSETS_DIR}/NHDDL/nhddl.elf" "${OPL}/bbnl/nhddl.elf" "-ut --progress"
update_apps "Neutrino" "${NEUTRINO_DIR}/" "${OPL}/neutrino/" "-rut --progress --delete --exclude='.*'"
################################### Synchronize Games & Apps ###################################
if [ "$INSTALL_TYPE" = "sync" ]; then
echo | tee -a "${LOG_FILE}"
echo "Preparing to sync apps..." | tee -a "${LOG_FILE}"
cd "${GAMES_PATH}/APPS/" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${GAMES_PATH}/APPS."
process_psu_files "${GAMES_PATH}/APPS/"
install_elf "${GAMES_PATH}"
rsync -rut --progress --delete --exclude='.*' "${GAMES_PATH}/APPS/" "${OPL}/APPS/" >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed sync apps. See $LOG_FILE for details."
find "${OPL}/APPS/" -maxdepth 1 -type f -exec rm -f {} + 2>>"${LOG_FILE}" || error_msg "Error" "Failed to tidy up ${OPL}/APPS/"
POPS_SYNC
activate_python
convert_zso
OPL_SIZE_CKECK
cd=$(rsync -rL --progress --ignore-existing --delete --exclude='.*' --dry-run "${GAMES_PATH}/CD/" "${OPL}/CD/")
dvd=$(rsync -rL --progress --ignore-existing --delete --exclude='.*' --dry-run "${GAMES_PATH}/DVD/" "${OPL}/DVD/")
# Check if either output contains more than one line
if [ $(echo "$cd" | wc -l) -ne 1 ] || [ $(echo "$dvd" | wc -l) -ne 1 ]; then
needs_update=true
fi
if [ "$needs_update" = true ]; then
echo "Total size of PS2 games to be synced: $needed_mb MB" | tee -a "${LOG_FILE}"
echo "Available space: $available_mb MB" | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
echo "Syncing PS2 games..." | tee -a "${LOG_FILE}"
rsync -rL --progress --ignore-existing --delete --exclude='.*' "${GAMES_PATH}/CD/" "${OPL}/CD/" 2>>"${LOG_FILE}" | tee -a "${LOG_FILE}"
cd_status=${PIPESTATUS[0]}
rsync -rL --progress --ignore-existing --delete --exclude='.*' "${GAMES_PATH}/DVD/" "${OPL}/DVD/" 2>>"${LOG_FILE}" | tee -a "${LOG_FILE}"
dvd_status=${PIPESTATUS[0]}
ps2_rsync_check Synced
else
echo "PS2 games are already up-to-date." | tee -a "${LOG_FILE}"
fi
################################### Add Games & Apps ###################################
elif [ "$INSTALL_TYPE" = "copy" ]; then
echo | tee -a "${LOG_FILE}"
echo "Preparing to copy apps..." | tee -a "${LOG_FILE}"
cd "${OPL}/APPS/" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${OPL}/APPS."
process_psu_files "${GAMES_PATH}/APPS/"
process_psu_files "${OPL}/APPS/"
rm -rf "${OPL}/APPS/PSBBN"
install_elf "${GAMES_PATH}"
install_elf "${OPL}"
find "${GAMES_PATH}/APPS/" -mindepth 1 -maxdepth 1 -type d -exec cp -r {} "${OPL}/APPS/" \; || error_msg "Error" "Failed copy apps. See $LOG_FILE for details."
POPS_SYNC
activate_python
convert_zso
OPL_SIZE_CKECK
if (( needed_mb > 0 )); then
echo "Total size of PS2 games to be copied: $needed_mb MB" | tee -a "${LOG_FILE}"
echo "Available space: $available_mb MB" | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
echo "Copying PS2 games..."
# Update PS2 CD games
rsync -rL --progress --ignore-existing --exclude=".*" "${GAMES_PATH}/CD/" "${OPL}/CD/" 2>>"${LOG_FILE}" | tee -a "${LOG_FILE}"
cd_status=${PIPESTATUS[0]}
# Update PS2 DVD games
rsync -rL --progress --ignore-existing --exclude=".*" "${GAMES_PATH}/DVD/" "${OPL}/DVD/" 2>>"${LOG_FILE}" | tee -a "${LOG_FILE}"
dvd_status=${PIPESTATUS[0]}
ps2_rsync_check copied
else
echo "No PS2 games to copy." | tee -a "${LOG_FILE}"
fi
fi
# Sends a list of apps and games synced/copied to the log file
echo >> "${LOG_FILE}"
echo "APPS on PS2 drive:" >> "${LOG_FILE}"
ls -1 "${OPL}/APPS/" >> "${LOG_FILE}" 2>&1
echo >> "${LOG_FILE}"
echo "PS1 games on PS2 drive:" >> "${LOG_FILE}"
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount __.POPS\n"
COMMANDS+="ls\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
echo -e "$COMMANDS" | sudo "${HELPER_DIR}/PFS Shell.elf" 2>&1 | grep -i '\.vcd$' >> "${LOG_FILE}"
echo >> "${LOG_FILE}"
echo "PS2 games on PS2 drive:" >> "${LOG_FILE}"
ls -1 "${OPL}/CD/" >> "${LOG_FILE}" 2>&1
ls -1 "${OPL}/DVD/" >> "${LOG_FILE}" 2>&1
# Create games list of PS1 and PS2 games to be installed
if find "${GAMES_PATH}/POPS" -maxdepth 1 -type f \( -iname "*.vcd" \) | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "Creating PS1 games list..." | tee -a "${LOG_FILE}"
python3 -u "${HELPER_DIR}/list-builder.py" "${GAMES_PATH}" "${PS1_LIST}" | tee -a "${LOG_FILE}"
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
error_msg "Error" "Failed to create PS1 games list."
fi
fi
if find "${OPL}/CD" "${OPL}/DVD" -maxdepth 1 -type f \( -iname "*.iso" -o -iname "*.zso" \) | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "Creating PS2 games list..." | tee -a "${LOG_FILE}"
python3 -u "${HELPER_DIR}/list-builder.py" "${OPL}" "${PS2_LIST}" | tee -a "${LOG_FILE}"
if [ "${PIPESTATUS[0]}" -ne 0 ]; then
error_msg "Error" "Failed to create PS2 games list."
fi
fi
if [[ "$INSTALL_TYPE" = "copy" && -f "${OPL}/ps1.list" ]]; then
cat "${OPL}/ps1.list" >> "${PS1_LIST}"
# Remove duplicate lines
sort -u "${PS1_LIST}" -o "${PS1_LIST}"
fi
if [ -f "${PS1_LIST}" ]; then
python3 "${HELPER_DIR}/list-sorter.py" "${PS1_LIST}" || error_msg "Error" "Failed to sort PS1 games list."
fi
if [ -f "${PS2_LIST}" ]; then
python3 "${HELPER_DIR}/list-sorter.py" "${PS2_LIST}" || error_msg "Error" "Failed to sort PS2 games list."
fi
# Deactivate the virtual environment
deactivate
# Create master list combining PS1 and PS2 games to a single list
if [[ ! -f "${PS1_LIST}" && ! -f "${PS2_LIST}" ]] && find "${GAMES_PATH}/CD" "${GAMES_PATH}/DVD" -maxdepth 1 -type f \( -iname "*.iso" -o -iname "*.zso" \) | grep -q .; then
error_msg "Error" "Failed to create games list."
fi
if [[ -f "${PS1_LIST}" ]] && [[ ! -f "${PS2_LIST}" ]]; then
{ cat "${PS1_LIST}" > "${ALL_GAMES}"; } 2>> "${LOG_FILE}"
elif [[ ! -f "${PS1_LIST}" ]] && [[ -f "${PS2_LIST}" ]]; then
{ cat "${PS2_LIST}" >> "${ALL_GAMES}"; } 2>> "${LOG_FILE}"
elif [[ -f "${PS1_LIST}" ]] && [[ -f "${PS2_LIST}" ]]; then
{ cat "${PS1_LIST}" > "${ALL_GAMES}"; } 2>> "${LOG_FILE}"
{ cat "${PS2_LIST}" >> "${ALL_GAMES}"; } 2>> "${LOG_FILE}"
fi
rm -f "${OPL}/ps1.list"
# Check for master.list
if [[ -s "${ALL_GAMES}" ]]; then
# Count the number of games to be installed
[ -f "$PS1_LIST" ] && ! cp "${PS1_LIST}" "${OPL}" && error_msg "Error" "Failed to copy $PS1_LIST to ${OPL}"
count=$(grep -c '^[^[:space:]]' "${ALL_GAMES}")
echo | tee -a "${LOG_FILE}"
echo "Number of games to install: $count" | tee -a "${LOG_FILE}"
echo
echo "Games list successfully created."| tee -a "${LOG_FILE}"
echo >> "${LOG_FILE}"
echo "master.list:" >> "${LOG_FILE}"
cat "${ALL_GAMES}" >> "${LOG_FILE}"
fi
################################### Creating Assets ###################################
echo
echo -n "Preparing to create assets..."
echo | tee -a "${LOG_FILE}"
mkdir -p "${ICONS_DIR}/bbnl" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${ICONS_DIR}/bbnl."
mkdir -p "${ICONS_DIR}/SAS" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${ICONS_DIR}/SAS."
mkdir -p "${ICONS_DIR}/APPS" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${ICONS_DIR}/APPS."
mkdir -p "${ARTWORK_DIR}/tmp" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${ARTWORK_DIR}/tmp."
mkdir -p "${TOOLKIT_PATH}/icons/ico/tmp/vmc" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${TOOLKIT_PATH}/icons/ico/tmp/vmc."
mkdir -p "${TOOLKIT_PATH}/icons/ico/vmc" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create ${TOOLKIT_PATH}/icons/ico/tmp/vmc."
# Set maximum number of items for the Game Channel (799 + 1 for chosen launcher)
pp_cap="799"
################################### Assets for SAS Apps ###################################
SOURCE_DIR="${OPL}/APPS"
APA_SIZE_CHECK
if [ "$pp_max" -gt "$pp_cap" ]; then
pp_max="$pp_cap"
fi
echo "Max Partitions: $pp_max" >> "${LOG_FILE}"
SAS_COUNT="0"
for dir in "${SOURCE_DIR}"/*/; do
[[ -d "$dir" ]] || continue
# Stop if we've reached the limit
if [ "$SAS_COUNT" -ge "$pp_max" ]; then
error_msg "Warning" "Insufficient space to create BBL partitions for remaining SAS apps." " " "The first $pp_max apps will appear in the PSBBN Game Channel." "All apps will appear in OPL."
break
fi
# Check for .elf/.ELF file
if find "$dir" -maxdepth 1 -type f -iname "*.elf" | grep -q . && \
[[ -f "$dir/icon.sys" && -f "$dir/title.cfg" ]]; then
cp -r "$dir" "${ICONS_DIR}/SAS" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to copy $dir. See ${LOG_FILE} for details."
SAS_COUNT=$((SAS_COUNT + 1))
fi
done
if ! find "${ICONS_DIR}/SAS" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "No SAS apps to process." | tee -a "${LOG_FILE}"
else
echo | tee -a "${LOG_FILE}"
echo "Creating Assets for SAS Apps:" | tee -a "${LOG_FILE}"
# Loop through each folder in the 'SAS' directory, sorted in reverse alphabetical order
while IFS= read -r dir; do
title_id=$(basename "$dir")
echo | tee -a "${LOG_FILE}"
if [ -f "$dir/list.icn" ]; then
echo "Processing $title_id..." | tee -a "${LOG_FILE}"
mv "$dir/list.icn" "$dir/list.ico" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to convert $dir/list.icn."
echo "Converted list.icn: $dir/list.ico" | tee -a "${LOG_FILE}"
[ -f "$dir/del.icn" ] && mv "$dir/del.icn" "$dir/del.ico" | echo "Converted del.icn: $dir/del.ico" | tee -a "${LOG_FILE}"
else
echo "list.icn not found in $dir." | tee -a "${LOG_FILE}"
cp "${ICONS_DIR}/ico/app.ico" "$dir/list.ico" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $dir/list.ico using default icon."
cp "${ICONS_DIR}/ico/app-del.ico" "$dir/del.ico" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create $dir/del.ico. See ${LOG_FILE} for details."
echo "Created: $dir/del.ico using default icon."
fi
# Convert the icon.sys file
icon_sys_filename="$dir/icon.sys"
python3 "${HELPER_DIR}/icon_sys_to_txt.py" "$icon_sys_filename" >> "${LOG_FILE}" 2>&1
mv "$dir/icon.txt" "$icon_sys_filename" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to convert $icon_sys_filename"
echo "Converted icon.sys: $icon_sys_filename" | tee -a "${LOG_FILE}"
while IFS='=' read -r key value; do
key=$(echo "$key" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value=$(echo "$value" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Remove non-ASCII and non-printable characters
value=$(printf '%s' "$value" | LC_ALL=C tr -cd '\40-\176')
case "$key" in
title) title="$value" ;;
boot) elf="$value" ;;
Developer) publisher="$value" ;;
esac
done < "$dir/title.cfg"
# Generate the info.sys file
info_sys_filename="$dir/info.sys"
create_info_sys "$title" "$title_id" "$publisher"
APP_ART
# Generate the bbnl cfg file
bbnl_cfg="${ICONS_DIR}/bbnl/$title_id.cfg"
create_bbnl_cfg "/APPS/$title_id/$elf" "$title_id"
cp "${ASSETS_DIR}/BBNL"/{boot.kelf,system.cnf} "$dir" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create boot.kelf or system.cnf. See ${LOG_FILE} for details."
echo "Created: $dir/boot.kelf" | tee -a "${LOG_FILE}"
echo "Created: $dir/system.cnf" | tee -a "${LOG_FILE}"
done < <(find "${ICONS_DIR}/SAS" -mindepth 1 -maxdepth 1 -type d | sort)
fi
################################### Assets for ELF Files ###################################
pp_max=$(( pp_max - SAS_COUNT ))
echo "PP Max after SAS: $pp_max" >> "${LOG_FILE}"
APP_COUNT=0
for dir in "${SOURCE_DIR}"/*/; do
[[ -d "$dir" ]] || continue
# Stop if we've reached the max
if [ "$APP_COUNT" -ge "$pp_max" ]; then
error_msg "Warning" "Insufficient space to create BBL partitions for remaining ELF files." " " "The first $pp_max apps will appear in the PSBBN Game Channel." "All apps will appear in OPL."
break
fi
# Check for .elf/.ELF file
if find "$dir" -maxdepth 1 -type f -iname "*.elf" | grep -q . && \
[[ ! -f "$dir/icon.sys" && -f "$dir/title.cfg" ]]; then
cp -r "$dir" "${ICONS_DIR}/APPS" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to copy $dir. See ${LOG_FILE} for details."
APP_COUNT=$((APP_COUNT + 1))
fi
done
if ! find "${ICONS_DIR}/APPS" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "No ELF files to process." | tee -a "${LOG_FILE}"
else
echo | tee -a "${LOG_FILE}"
echo "Creating Assets for ELF files:" | tee -a "${LOG_FILE}"
# Loop through each folder in the 'APPS' directory, sorted in reverse alphabetical order
while IFS= read -r dir; do
title_id=$(basename "$dir")
while IFS='=' read -r key value; do
key=$(echo "$key" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
value=$(echo "$value" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Remove non-ASCII and non-printable characters
value=$(printf '%s' "$value" | LC_ALL=C tr -cd '\40-\176')
case "$key" in
title) title="$value" ;;
boot) elf="$value" ;;
Developer) publisher="$value" ;;
esac
done < "$dir/title.cfg"
echo | tee -a "${LOG_FILE}"
info_sys_filename="$dir/info.sys"
if [[ "$title_id" == "LAUNCHELF" ]]; then
title="LaunchELF"
fi
create_info_sys "$title" "$title_id" "$publisher"
# Generate the icon.sys file
icon_sys_filename="$dir/icon.sys"
create_icon_sys "$title"
if [[ "$title_id" == "LAUNCHELF" ]]; then
cp "${ICONS_DIR}/ico/wle.ico" "$dir/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $dir/list.ico" | tee -a "${LOG_FILE}"
cp "${ICONS_DIR}/ico/wle-del.ico" "$dir/del.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/del.ico. See ${LOG_FILE} for details."
echo "Created: $dir/del.ico" | tee -a "${LOG_FILE}"
elif [[ "$title_id" == "BBNAVIGATOR" ]]; then
cp "${ICONS_DIR}/ico/psbbn.ico" "$dir/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
cp "${ICONS_DIR}/ico/psbbn-del.ico" "$dir/del.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
cp
echo "Created: $dir/list.ico" | tee -a "${LOG_FILE}"
elif [[ "$title_id" == "HDDOSD" ]]; then
cp "${ICONS_DIR}/ico/hdd-osd.ico" "$dir/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $dir/list.ico" | tee -a "${LOG_FILE}"
else
cp "${ICONS_DIR}/ico/app.ico" "$dir/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $dir/list.ico" | tee -a "${LOG_FILE}"
cp "${ICONS_DIR}/ico/app-del.ico" "$dir/del.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $dir/del.ico. See ${LOG_FILE} for details."
echo "Created: $dir/del.ico" | tee -a "${LOG_FILE}"
fi
if [[ "$title_id" == "BBNAVIGATOR" ]]; then
cp "${ARTWORK_DIR}/PSBBN.png" "${GAMES_PATH}/ART/${elf}_COV.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create ${GAMES_PATH}/ART/${elf}_COV.png. See ${LOG_FILE} for details."
else
APP_ART
fi
bbnl_cfg="${ICONS_DIR}/bbnl/$title_id.cfg"
create_bbnl_cfg "/APPS/$(basename "$dir")/$elf" "$title_id"
cp "${ASSETS_DIR}/BBNL"/{boot.kelf,system.cnf} "$dir" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create boot.kelf, or system.cnf. See ${LOG_FILE} for details."
echo "Created: $dir/boot.kelf" | tee -a "${LOG_FILE}"
echo "Created: $dir/system.cnf" | tee -a "${LOG_FILE}"
done < <(find "${ICONS_DIR}/APPS" -mindepth 1 -maxdepth 1 -type d | sort -r)
fi
################################### Assets for Games ###################################
if [ -f "$ALL_GAMES" ]; then
echo | tee -a "${LOG_FILE}"
echo "Downloading OPL artwork for games..." | tee -a "${LOG_FILE}"
# First loop: Run the art downloader script for each game_id if artwork doesn't already exist
exec 3< "$ALL_GAMES"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
# Skip downloading if disc_type is "POPS"
if [[ "$disc_type" == "POPS" ]]; then
continue
fi
png_file_cover="${GAMES_PATH}/ART/${game_id}_COV.png"
png_file_disc="${GAMES_PATH}/ART/${game_id}_ICO.png"
if [[ -f "$png_file_cover" ]]; then
echo "OPL Artwork for $game_id already exists. Skipping download." | tee -a "${LOG_FILE}"
else
# Attempt to download artwork using wget
echo -n "OPL Artwork not found locally. Attempting to download from archive.org..." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="$png_file_cover" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS2/${game_id}/${game_id}_COV.png"
#wget --quiet --timeout=10 --tries=3 --output-document="$png_file_disc" \
#"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS2/${game_id}/${game_id}_ICO.png"
missing_files=()
if [[ ! -s "$png_file_cover" ]]; then
[[ -f "$png_file_cover" ]] && rm -f "$png_file_cover"
missing_files+=("cover")
fi
if [[ ! -s "$png_file_disc" ]]; then
[[ -f "$png_file_disc" ]] && rm -f "$png_file_disc"
missing_files+=("disc")
fi
if [[ -f "$png_file_cover" || -f "$png_file_disc" ]]; then
if [[ ${#missing_files[@]} -eq 0 ]]; then
echo "Successfully downloaded OPL artwork for $game_id" | tee -a "${LOG_FILE}"
else
echo "Successfully downloaded some OPL artwork for $game_id, but missing: ${missing_files[*]}" | tee -a "${LOG_FILE}"
fi
else
echo "Failed to download OPL artwork for $game_id" | tee -a "${LOG_FILE}"
fi
fi
done
exec 3<&-
else
echo | tee -a "${LOG_FILE}"
echo "No OPL artwork to download." | tee -a "${LOG_FILE}"
fi
GAME_COUNT=$(grep -c '^[^[:space:]]' "${ALL_GAMES}")
pp_max=$(( pp_max - APP_COUNT ))
if [ "$GAME_COUNT" -gt "$pp_max" ]; then
error_msg "Warning" "Insufficient space to create BBL partitions for remaining games." " " "The first $pp_max games will appear in the PSBBN Game Channel." "All PS2 games will appear in OPL/NHDDL."
# Overwrite master.list with the first $pp_max lines
head -n "$pp_max" "$ALL_GAMES" > "${ALL_GAMES}.tmp"
mv "${ALL_GAMES}.tmp" "$ALL_GAMES" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to updated master.list."
echo "Updated master.list:" >> "${LOG_FILE}"
cat "$ALL_GAMES" >> "${LOG_FILE}"
echo >> "${LOG_FILE}"
fi
[ -f "$ALL_GAMES" ] && [ ! -s "$ALL_GAMES" ] && rm -f "$ALL_GAMES"
if [ -f "$ALL_GAMES" ]; then
echo | tee -a "${LOG_FILE}"
echo "Downloading PSBBN artwork for games..." | tee -a "${LOG_FILE}"
# First loop: Run the art downloader script for each game_id if artwork doesn't already exist
exec 3< "$ALL_GAMES"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
# Check if the artwork file already exists
png_file="${ARTWORK_DIR}/${game_id}.png"
if [[ -f "$png_file" ]]; then
echo "Artwork for $game_id already exists. Skipping download." | tee -a "${LOG_FILE}"
else
# Attempt to download artwork using wget
echo -n "Artwork not found locally. Attempting to download from the PSBBN art database..." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="$png_file" \
"https://raw.githubusercontent.com/CosmicScale/psbbn-art-database/main/art/${game_id}.png"
if [[ -s "$png_file" ]]; then
echo "Successfully downloaded artwork for $game_id" | tee -a "${LOG_FILE}"
else
# If wget fails, run the art downloader
[[ -f "$png_file" ]] && rm -f "$png_file"
echo "Trying IGN for $game_id" | tee -a "${LOG_FILE}"
node "${HELPER_DIR}/art_downloader.js" "$game_id" 2>&1 | tee -a "${LOG_FILE}"
fi
fi
done
exec 3<&-
# Define input directory
input_dir="${ARTWORK_DIR}/tmp"
# Check if the directory contains any files
if compgen -G "${input_dir}/*" > /dev/null; then
echo | tee -a "${LOG_FILE}"
echo "Converting artwork..." | tee -a "${LOG_FILE}"
for file in "${input_dir}"/*; do
# Extract the base filename without the path or extension
base_name=$(basename "${file%.*}")
# Define output filename with .png extension
output="${ARTWORK_DIR}/tmp/${base_name}.png"
# Get image dimensions using identify
dimensions=$(identify -format "%w %h" "$file")
width=$(echo "$dimensions" | cut -d' ' -f1)
height=$(echo "$dimensions" | cut -d' ' -f2)
# Check if width >= 256 and height >= width
if [[ $width -ge 256 && $height -ge $width ]]; then
# Determine whether the image is square
if [[ $width -eq $height ]]; then
# Square: Resize without cropping
echo "Resizing square image $file"
convert "$file" -resize 256x256! -depth 8 -alpha off "$output"
else
# Not square: Resize and crop
echo "Resizing and cropping $file"
convert "$file" -resize 256x256^ -crop 256x256+0+44 -depth 8 -alpha off "$output"
fi
rm -f "$file"
else
echo "Skipping $file: does not meet size requirements" | tee -a "${LOG_FILE}"
rm -f "$file"
fi
done
else
echo | tee -a "${LOG_FILE}"
echo "No artwork to convert in ${input_dir}" | tee -a "${LOG_FILE}"
fi
cp ${ARTWORK_DIR}/tmp/* ${ARTWORK_DIR} >> "${LOG_FILE}" 2>&1
echo | tee -a "${LOG_FILE}"
echo "Downloading HDD-OSD icons for games:" | tee -a "${LOG_FILE}"
exec 3< "$ALL_GAMES"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
ico_file="${ICONS_DIR}/ico/$game_id.ico"
if [[ ! -s "$ico_file" ]]; then
# Attempt to download icon using wget
echo -n "Icon not found locally for $game_id. Attempting to download from the HDD-OSD icon database..." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="$ico_file" \
"https://raw.githubusercontent.com/CosmicScale/HDD-OSD-Icon-Database/main/ico/${game_id}.ico"
if [[ -s "$ico_file" ]]; then
echo "Successfully downloaded icon for ${game_id}." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
else
# If wget fails, run the art downloader
[[ -f "$ico_file" ]] && rm -f "$ico_file"
png_file_cov="${TOOLKIT_PATH}/icons/ico/tmp/${game_id}_COV.png"
png_file_cov2="${TOOLKIT_PATH}/icons/ico/tmp/${game_id}_COV2.png"
png_file_lab="${TOOLKIT_PATH}/icons/ico/tmp/${game_id}_LAB.png"
echo -n "Icon not found on database. Downloading icon assets for $game_id..." | tee -a "${LOG_FILE}"
if [[ -s "${GAMES_PATH}/ART/${game_id}_COV.png" ]]; then
cp "${GAMES_PATH}/ART/${game_id}_COV.png" "${png_file_cov}"
fi
if [[ "$disc_type" == "POPS" ]]; then
wget --quiet --timeout=10 --tries=3 --output-document="${png_file_cov}" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS1/${game_id}/${game_id}_COV.png"
fi
if [[ -s "$png_file_cov" && "$disc_type" != "POPS" ]]; then
wget --quiet --timeout=10 --tries=3 --output-document="$png_file_cov2" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS2/${game_id}/${game_id}_COV2.png"
wget --quiet --timeout=10 --tries=3 --output-document="$png_file_lab" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS2/${game_id}/${game_id}_LAB.png"
elif [[ -s "$png_file_cov" && "$disc_type" == "POPS" ]]; then
wget --quiet --timeout=10 --tries=3 --output-document="$png_file_cov2" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS1/${game_id}/${game_id}_COV2.png"
wget --quiet --timeout=10 --tries=3 --output-document="$png_file_lab" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS1/${game_id}/${game_id}_LAB.png"
fi
echo | tee -a "${LOG_FILE}"
if [[ ! -s "$png_file_lab" ]]; then
if [[ "${game_id:2:1}" == "E" ]]; then
if [[ "$disc_type" != "POPS" ]]; then
cp "${ASSETS_DIR}/Icon-templates/PS2_LAB_PAL.png" "${png_file_lab}"
else
cp "${ASSETS_DIR}/Icon-templates/PS1_LAB_PAL.png" "${png_file_lab}"
fi
elif [[ "${game_id:2:1}" == "U" || "${game_id:0:1}" == "L" ]]; then
if [[ "$disc_type" != "POPS" ]]; then
cp "${ASSETS_DIR}/Icon-templates/PS2_LAB_USA.png" "${png_file_lab}"
else
cp "${ASSETS_DIR}/Icon-templates/PS1_LAB_USA.png" "${png_file_lab}"
fi
else
if [[ "$disc_type" != "POPS" ]]; then
cp "${ASSETS_DIR}/Icon-templates/PS2_LAB_JPN.png" "${png_file_lab}"
else
cp "${ASSETS_DIR}/Icon-templates/PS1_LAB_JPN.png" "${png_file_lab}"
fi
fi
fi
if [[ -s "$png_file_cov" && -s "$png_file_cov2" && -s "$png_file_lab" ]]; then
echo "Creating HDD-OSD icon for $game_id..." | tee -a "${LOG_FILE}"
if [[ "$disc_type" != "POPS" ]]; then
if [[ "${game_id:2:1}" == "E" ]]; then
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 2
else
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 1
fi
else
if [[ "${game_id:2:1}" == "U" || "${game_id:0:1}" == "L" ]]; then
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 3
elif [[ "${game_id:2:1}" == "E" ]]; then
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 6
else
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 5
fi
fi
echo | tee -a "${LOG_FILE}"
else
echo "Insufficient assets to create icon for $game_id." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
fi
fi
fi
done
exec 3<&-
echo | tee -a "${LOG_FILE}"
if [ -f "$PS1_LIST" ]; then
echo "Downloading VMC icons:" | tee -a "${LOG_FILE}"
exec 3< "$PS1_LIST"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
ico_file="${ICONS_DIR}/ico/vmc/$game_id.ico"
if [[ ! -s "$ico_file" ]]; then
# Attempt to download icon using wget
echo -n "VMC icon not found locally for $game_id. Attempting to download from the HDD-OSD icon database..." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="$ico_file" \
"https://raw.githubusercontent.com/CosmicScale/HDD-OSD-Icon-Database/main/vmc/${game_id}.ico"
if [[ -s "$ico_file" ]]; then
echo "Successfully downloaded VMC icon for ${game_id}." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
else
# If wget fails, run the art downloader
[[ -f "$ico_file" ]] && rm -f "$ico_file"
png_file_lgo="${TOOLKIT_PATH}/icons/ico/tmp/${game_id}_LGO.png"
echo -n "VMC icon not found on database. Downloading icon assets for $game_id..." | tee -a "${LOG_FILE}"
wget --quiet --timeout=10 --tries=3 --output-document="${png_file_lgo}" \
"https://archive.org/download/OPLM_ART_2024_09/OPLM_ART_2024_09.zip/PS1/${game_id}/${game_id}_LGO.png"
fi
if [[ -s "$png_file_lgo" ]]; then
echo| tee -a "${LOG_FILE}"
echo -n "Creating VMC icon for $game_id..." | tee -a "${LOG_FILE}"
"${HELPER_DIR}/ps2iconmaker.sh" $game_id -t 8
echo | tee -a "${LOG_FILE}"
elif [[ ! -s "$ico_file" ]] && [[ ! -s "$png_file_lgo" ]]; then
echo | tee -a "${LOG_FILE}"
echo "Insufficient assets to create VMC icon for $game_id." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
fi
fi
done
exec 3<&-
fi
cp "${ICONS_DIR}/ico/tmp/"*.ico "${ICONS_DIR}/ico/" >/dev/null 2>&1
cp "${ICONS_DIR}/ico/tmp/vmc/"*.ico "${ICONS_DIR}/ico/vmc" >/dev/null 2>&1
echo | tee -a "${LOG_FILE}"
echo "Creating Assets for Games:" | tee -a "${LOG_FILE}"
# Read the file line by line
exec 3< "$ALL_GAMES"
while IFS='|' read -r title game_id publisher disc_type file_name <&3; do
echo | tee -a "${LOG_FILE}"
echo "Processing $title..."
title_id=$(echo "$game_id" | sed -E 's/_(...)\./-\1/;s/\.//')
# Create a sub-folder named after the game_id
game_dir="$ICONS_DIR/$game_id"
mkdir -p "$game_dir" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to create $dir."
cp "${ASSETS_DIR}/BBNL"/{boot.kelf,system.cnf} "${game_dir}" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create boot.kelf, or system.cnf. See ${LOG_FILE} for details."
echo "Created: $game_dir/boot.kelf" | tee -a "${LOG_FILE}"
echo "Created: $game_dir/system.cnf" | tee -a "${LOG_FILE}"
# Generate the info.sys file
info_sys_filename="$game_dir/info.sys"
create_info_sys "$title" "$title_id" "$publisher"
if [ ${#title} -gt 48 ]; then
game_title_icon="${title:0:45}..."
else
game_title_icon="$title"
fi
# Generate the icon.sys file
icon_sys_filename="$game_dir/icon.sys"
create_icon_sys "$game_title_icon" "$publisher"
# Copy the matching .png file and rename it to jkt_001.png
png_file="${TOOLKIT_PATH}/icons/art/${game_id}.png"
if [[ -s "$png_file" ]]; then
cp "$png_file" "${game_dir}/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $game_dir/jkt_001.png" | tee -a "${LOG_FILE}"
else
echo "$game_id $title" >> "${MISSING_ART}"
if [[ "$disc_type" == "POPS" ]]; then
cp "${TOOLKIT_PATH}/icons/art/ps1.png" "${game_dir}/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $game_dir/jkt_001.png using default PS1 image." | tee -a "${LOG_FILE}"
else
cp "${TOOLKIT_PATH}/icons/art/ps2.png" "${game_dir}/jkt_001.png" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/jkt_001.png. See ${LOG_FILE} for details."
echo "Created: $game_dir/jkt_001.png using default PS2 image." | tee -a "${LOG_FILE}"
fi
fi
ico_file="${ICONS_DIR}/ico/$game_id.ico"
if [[ -f "$ico_file" ]]; then
cp "${ICONS_DIR}/ico/$game_id.ico" "${game_dir}/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $game_dir/list.ico"
else
echo "$game_id $title" >> "${MISSING_ICON}"
case "$disc_type" in
DVD)
cp "${ICONS_DIR}/ico/dvd.ico" "${game_dir}/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $game_dir/list.ico using default DVD icon." | tee -a "${LOG_FILE}"
;;
CD)
cp "${ICONS_DIR}/ico/cd.ico" "${game_dir}/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $game_dir/list.ico using default CD icon." | tee -a "${LOG_FILE}"
;;
POPS)
cp "${ICONS_DIR}/ico/ps1.ico" "${game_dir}/list.ico" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create $game_dir/list.ico. See ${LOG_FILE} for details."
echo "Created: $game_dir/list.ico using default PS1 icon." | tee -a "${LOG_FILE}"
;;
esac
fi
PP_NAME
# Generate the BBNL cfg file
# Determine the launcher value for this specific game
if [[ "$disc_type" == "POPS" ]]; then
launcher_value="POPS"
else
launcher_value="$LAUNCHER"
fi
bbnl_label="${PARTITION_LABEL:3}"
bbnl_cfg="${ICONS_DIR}/bbnl/$bbnl_label.cfg"
cat > "$bbnl_cfg" <<EOL
file_name=$file_name
title_id=$game_id
disc_type=$disc_type
launcher=$launcher_value
EOL
echo "Created: $bbnl_cfg" | tee -a "${LOG_FILE}"
done
exec 3<&-
else
echo | tee -a "${LOG_FILE}"
echo "No games to process." | tee -a "${LOG_FILE}"
fi
bbnl_cfg="${ICONS_DIR}/bbnl/LAUNCHER.cfg"
echo | tee -a "${LOG_FILE}"
if [ "$LAUNCHER" = "OPL" ]; then
cp "${ASSETS_DIR}/BBNL"/{boot.kelf,system.cnf} "${ASSETS_DIR}/OPL" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create boot.kelf, or system.cnf for OPL. See ${LOG_FILE} for details."
create_bbnl_cfg "/bbnl/OPNPS2LD.ELF" "LAUNCHER"
elif [ "$LAUNCHER" = "NEUTRINO" ]; then
cp "${ASSETS_DIR}/BBNL"/{boot.kelf,system.cnf} "${ASSETS_DIR}/NHDDL" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to create boot.kelf, or system.cnf for NHDDL. See ${LOG_FILE} for details."
create_bbnl_cfg "/bbnl/nhddl.elf" "LAUNCHER" "-mode=ata"
fi
# Copy OPL files
dirs=(
"${GAMES_PATH}/ART"
"${GAMES_PATH}/CFG"
"${GAMES_PATH}/CHT"
"${GAMES_PATH}/LNG"
"${GAMES_PATH}/THM"
"${GAMES_PATH}/VMC"
)
# Flag to track if any files exist
files_exist=false
echo | tee -a "${LOG_FILE}"
# Check each directory and copy files if not empty
for dir in "${dirs[@]}"; do
if [ -d "$dir" ] && [ -n "$(find "$dir" -type f ! -name '.*' -print -quit 2>/dev/null)" ]; then
# Create the subdirectory in the destination path using the directory name
folder_name=$(basename "$dir")
dest_dir="${OPL}/$folder_name"
# Copy non-hidden files to the corresponding destination subdirectory
if [ "$folder_name" == "CFG" ] || [ "$folder_name" == "VMC" ]; then
echo "Copying OPL $folder_name files..." | tee -a "${LOG_FILE}"
find "$dir" -type f ! -name '.*' -exec cp --update=none {} "$dest_dir" \; >> "${LOG_FILE}" 2>&1
else
if [ -n "$(find "$dir" -mindepth 1 ! -name '.*' -print -quit)" ]; then
echo "Copying OPL $folder_name files..." | tee -a "${LOG_FILE}"
cp -r "$dir"/* "$dest_dir" >> "${LOG_FILE}" 2>&1
fi
fi
files_exist=true
fi
done
# Print message based on the check
if ! $files_exist; then
echo "No OPL files to copy." | tee -a "${LOG_FILE}"
fi
echo | tee -a "${LOG_FILE}"
echo "Copying BBNL configs..." | tee -a "${LOG_FILE}"
rm -f "${OPL}"/bbnl/*.cfg >> "${LOG_FILE}" 2>&1
cp "${ICONS_DIR}"/bbnl/*.cfg "${OPL}/bbnl" 2>> "${LOG_FILE}" || error_msg "Error" "Failed to copy BBNL config files. See ${LOG_FILE} for details."
echo | tee -a "${LOG_FILE}"
echo "All assets have been sucessfully created." | tee -a "${LOG_FILE}"
echo | tee -a "${LOG_FILE}"
echo -n "Unmounting OPL partition..." | tee -a "${LOG_FILE}"
UNMOUNT_OPL
echo | tee -a "${LOG_FILE}"
if [ -f "$PS1_LIST" ]; then
CREATE_VMC
fi
################################### Create BBNL Partitions ###################################
if find "${ICONS_DIR}/SAS" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | grep -q .; then
echo "Creating BBNL Partitions for SAS Apps:" | tee -a "${LOG_FILE}"
while IFS= read -r dir; do
folder_name=$(basename "$dir")
pp_name="PP.$folder_name"
APA_SIZE_CHECK
# Check the value of available
if [ "$available" -lt 8 ]; then
error_msg "Warning" "Insufficient space for another partition."
break
fi
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mkpart $pp_name 8M PFS\n"
COMMANDS+="mount $pp_name\n"
COMMANDS+="mkdir res\n"
COMMANDS+="cd res\n"
COMMANDS+="lcd '${ICONS_DIR}/SAS/$folder_name'\n"
COMMANDS+="put info.sys\n"
COMMANDS+="put jkt_001.png\n"
COMMANDS+="cd /\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
PFS_COMMANDS
cd "${ICONS_DIR}/SAS/$folder_name" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${ICONS_DIR}/SAS/$folder_name."
sudo "${HELPER_DIR}/HDL Dump.elf" modify_header "${DEVICE}" "$pp_name" >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed to modify header of $pp_name"
echo "Created $pp_name" | tee -a "${LOG_FILE}"
done < <(find "${ICONS_DIR}/SAS" -mindepth 1 -maxdepth 1 -type d | sort -r)
fi
if find "${ICONS_DIR}/APPS" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | grep -q .; then
echo | tee -a "${LOG_FILE}"
echo "Creating BBNL Partitions for ELF files:" | tee -a "${LOG_FILE}"
while IFS= read -r dir; do
APA_SIZE_CHECK
# Check the value of available
if [ "$available" -lt 8 ]; then
error_msg "Warning" "Insufficient space for another partition."
break
fi
folder_name=$(basename "$dir")
pp_name="PP.$folder_name"
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mkpart $pp_name 8M PFS\n"
COMMANDS+="mount $pp_name\n"
if [ "$pp_name" = "PP.DISC" ]; then
COMMANDS+="lcd '${ASSETS_DIR}/DISC'\n"
COMMANDS+="put PS1VModeNeg.elf\n"
fi
COMMANDS+="mkdir res\n"
COMMANDS+="cd res\n"
COMMANDS+="lcd '${ICONS_DIR}/APPS/$folder_name'\n"
COMMANDS+="put info.sys\n"
if [ "$pp_name" != "PP.BBNAVIGATOR" ]; then
COMMANDS+="put jkt_001.png\n"
fi
COMMANDS+="cd /\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
PFS_COMMANDS
if [ "$pp_name" = "PP.LAUNCHDISC" ]; then
cd "${ASSETS_DIR}/DISC" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${ASSETS_DIR}/DISC."
sudo "${HELPER_DIR}/HDL Dump.elf" modify_header "${DEVICE}" "$pp_name" >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed to modify header of $pp_name."
else
cd "${ICONS_DIR}/APPS/$folder_name" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${ICONS_DIR}/APPS/$folder_name."
sudo "${HELPER_DIR}/HDL Dump.elf" modify_header "${DEVICE}" "$pp_name" >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed to modify header of $pp_name."
fi
echo "Created $pp_name" | tee -a "${LOG_FILE}"
done < <(find "${ICONS_DIR}/APPS" -mindepth 1 -maxdepth 1 -type d | sort -r)
fi
# Create PP.LAUNCHER
APA_SIZE_CHECK
# Check the value of available
if [ "$available" -lt 8 ]; then
error_msg "Warning" "Insufficient space for another partition."
else
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mkpart PP.LAUNCHER 8M PFS\n"
COMMANDS+="mount PP.LAUNCHER\n"
COMMANDS+="mkdir res\n"
COMMANDS+="cd res\n"
if [ "$LAUNCHER" = "OPL" ]; then
cd "${ASSETS_DIR}/OPL"
COMMANDS+="put info.sys\n"
COMMANDS+="lcd '${ARTWORK_DIR}'\n"
COMMANDS+="put OPENPS2LOAD.png\n"
COMMANDS+="rename OPENPS2LOAD.png jkt_001.png\n"
COMMANDS+="cd /\n"
elif [ "$LAUNCHER" = "NEUTRINO" ]; then
cd "${ASSETS_DIR}/NHDDL"
COMMANDS+="put info.sys\n"
COMMANDS+="lcd '${ARTWORK_DIR}'\n"
COMMANDS+="put NHDDL.png\n"
COMMANDS+="rename NHDDL.png jkt_001.png\n"
COMMANDS+="cd /\n"
fi
COMMANDS+="umount\n"
COMMANDS+="exit"
echo >> "${LOG_FILE}"
PFS_COMMANDS
sudo "${HELPER_DIR}/HDL Dump.elf" modify_header "${DEVICE}" PP.LAUNCHER >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed to modify header of PP.LAUNCHER."
echo | tee -a "${LOG_FILE}"
echo "Created PP.LAUNCHER" | tee -a "${LOG_FILE}"
fi
if [ -f "$ALL_GAMES" ]; then
# Read all lines in reverse order
mapfile -t reversed_lines < <(tac "$ALL_GAMES")
echo | tee -a "${LOG_FILE}"
echo "Creating BBNL Partitions for Games:" | tee -a "${LOG_FILE}"
i=0
# Reverse the lines of the file using tac and process each line
for line in "${reversed_lines[@]}"; do
IFS='|' read -r title game_id publisher disc_type file_name <<< "$line"
APA_SIZE_CHECK
# Check the value of available
if [ "$available" -lt 8 ]; then
error_msg "Warning" "Insufficient space for another partition."
break
fi
PP_NAME
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mkpart ${PARTITION_LABEL} 8M PFS\n"
COMMANDS+="mount ${PARTITION_LABEL}\n"
COMMANDS+="cd /\n"
# Navigate into the sub-directory named after the gameid
COMMANDS+="lcd '${ICONS_DIR}/${game_id}'\n"
COMMANDS+="mkdir res\n"
COMMANDS+="cd res\n"
COMMANDS+="put info.sys\n"
COMMANDS+="put jkt_001.png\n"
if [[ "$disc_type" == "POPS" ]]; then
COMMANDS+="lcd '${ASSETS_DIR}/POPStarter'\n"
COMMANDS+="put bg.png\n"
COMMANDS+="lcd '${ASSETS_DIR}/POPStarter/eng'\n"
COMMANDS+="put 1.png\n"
COMMANDS+="put 2.png\n"
COMMANDS+="put man.xml\n"
fi
COMMANDS+="umount\n"
COMMANDS+="exit\n"
PFS_COMMANDS
cd "${ICONS_DIR}/$game_id" 2>>"${LOG_FILE}" || error_msg "Error" "Failed to navigate to ${ICONS_DIR}/$game_id."
sudo "${HELPER_DIR}/HDL Dump.elf" modify_header "${DEVICE}" "${PARTITION_LABEL}" >> "${LOG_FILE}" 2>&1 || error_msg "Error" "Failed to modify header of ${PARTITION_LABEL}."
echo "Created $PARTITION_LABEL" | tee -a "${LOG_FILE}"
echo >> "${LOG_FILE}"
((i++))
done
fi
################################### Submit missing artwork to the PSBBN Art Database ###################################
cp "${MISSING_ART}" "${ARTWORK_DIR}/tmp" >> "${LOG_FILE}" 2>&1
cp "${MISSING_APP_ART}" "${ARTWORK_DIR}/tmp" >> "${LOG_FILE}" 2>&1
cp "${MISSING_ICON}" "${ICONS_DIR}/ico/tmp" >> "${LOG_FILE}" 2>&1
cp "${MISSING_VMC}" "${ICONS_DIR}/ico/tmp/" >> "${LOG_FILE}" 2>&1
cd "${ICONS_DIR}/ico/tmp/"
rm *.png >/dev/null 2>&1
if [ -d "${ICONS_DIR}/ico/tmp/vmc" ] && [ -z "$(ls -A "${ICONS_DIR}/ico/tmp/vmc")" ]; then
rmdir "${ICONS_DIR}/ico/tmp/vmc"
fi
zip -r "${ARTWORK_DIR}/tmp/ico.zip" * >/dev/null 2>&1
cd "${ARTWORK_DIR}/tmp/"
zip -r "${ARTWORK_DIR}/tmp/art.zip" * >/dev/null 2>&1
if [ "$(ls -A "${ARTWORK_DIR}/tmp")" ]; then
echo | tee -a "${LOG_FILE}"
echo "Contributing to the PSBBN art & HDD-OSD databases..." | tee -a "${LOG_FILE}"
# Upload the file using transfer.sh
upload_url=$(curl -F "reqtype=fileupload" -F "time=72h" -F "fileToUpload=@art.zip" https://litterbox.catbox.moe/resources/internals/api.php)
if [[ "$upload_url" == https://* ]]; then
echo "File uploaded successfully: $upload_url" | tee -a "${LOG_FILE}"
# Send a POST request to Webhook.site with the uploaded file URL
webhook_url="https://webhook.site/PSBBN"
curl -X POST -H "Content-Type: application/json" \
-d "{\"url\": \"$upload_url\"}" \
"$webhook_url" >/dev/null 2>&1
else
error_msg "Warning" "Failed to upload the file."
fi
else
echo | tee -a "${LOG_FILE}"
echo "No art work or icons to contribute." | tee -a "${LOG_FILE}"
fi
HDL_TOC
cat "$hdl_output" >> "${LOG_FILE}"
rm -f "$hdl_output"
echo | tee -a "${LOG_FILE}"
echo "Game installer script complete." | tee -a "${LOG_FILE}"
echo
read -n 1 -s -r -p "Press any key to return to the main menu..." </dev/tty
echo