#!/usr/bin/env bash export LC_ALL=en_US.UTF-8 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" SCRIPTS_DIR="${TOOLKIT_PATH}/scripts" HELPER_DIR="${SCRIPTS_DIR}/helper" ASSETS_DIR="${SCRIPTS_DIR}/assets" POPSTARTER="${ASSETS_DIR}/POPStarter/POPSTARTER.ELF" POPS_DIR="${ICONS_DIR}/POPS" NEUTRINO_DIR="${ASSETS_DIR}/neutrino" LOGS_DIR="${TOOLKIT_PATH}/logs" LOG_FILE="${LOGS_DIR}/game-installer.log" MISSING_ART="${LOGS_DIR}/missing-art.log" MISSING_APP_ART="${LOGS_DIR}/missing-app-art.log" MISSING_ICON="${LOGS_DIR}/missing-icon.log" MISSING_VMC="${LOGS_DIR}/missing-vmc.log" GAMES_PATH="${TOOLKIT_PATH}/games" CONFIG_FILE="${SCRIPTS_DIR}/gamepath.cfg" OPL="${SCRIPTS_DIR}/OPL" PS1_LIST="${SCRIPTS_DIR}/tmp/ps1.list" PS2_LIST="${SCRIPTS_DIR}/tmp/ps2.list" ALL_GAMES="${SCRIPTS_DIR}/tmp/master.list" 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" "${SCRIPTS_DIR}/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}" > /dev/null 2>&1 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 if [ "$type" = "Error" ]; then echo "[X] $type: $error_1" | tee -a "${LOG_FILE}" else echo "[!] $type: $error_1" | tee -a "${LOG_FILE}" fi echo [ -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 menu..." > "${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="${SCRIPTS_DIR}/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" for dir in "$POPS_DIR"/*/; do [ -d "$dir" ] || continue VMC_FOLDER="$(basename "$dir")" COMMANDS+="cd /\n" COMMANDS+="cd POPS\n" 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" done COMMANDS+="cd /\n" COMMANDS+="cd POPS\n" COMMANDS+="rm icon.sys\n" COMMANDS+="rm list.ico\n" COMMANDS+="rm DISCS.TXT\n" COMMANDS+="rm VMCDIR.TXT\n" 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" <> "${LOG_FILE}" # Try activating the virtual environment twice before failing if ! source "${SCRIPTS_DIR}/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 "${SCRIPTS_DIR}/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" < "$icon_sys_filename" < "$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 } get_display_path() { if [[ "$GAMES_PATH" =~ ^/mnt/([a-zA-Z])(/.*)?$ ]]; then drive="${BASH_REMATCH[1]}" rest="${BASH_REMATCH[2]}" # If the rest is empty, default to empty string [[ -z "$rest" ]] && rest="" # Convert to Windows format display_path="${drive^^}:$(echo "$rest" | sed 's#/#\\#g')" else # For Linux paths, display_path is the same as GAMES_PATH display_path="$GAMES_PATH" fi } SPLASH() { clear cat << "EOF" _____ _____ _ _ _ | __ \ |_ _| | | | | | | | \/ __ _ _ __ ___ ___ | | _ __ ___| |_ __ _| | | ___ _ __ | | __ / _` | '_ ` _ \ / _ \ | || '_ \/ __| __/ _` | | |/ _ \ '__| | |_\ \ (_| | | | | | | __/ _| || | | \__ \ || (_| | | | __/ | \____/\__,_|_| |_| |_|\___| \___/_| |_|___/\__\__,_|_|_|\___|_| EOF } mkdir -p "${LOGS_DIR}" >/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 "${SCRIPTS_DIR}/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}" elif [ -n "$IN_NIX_SHELL" ]; then echo "Running in Nix environment - The Python dependencies are managed by the flake." >> "${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" else 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 get_display_path echo echo "Games folder: $display_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 # --- Detect & convert Windows path --- if [[ "$new_path" =~ ^[A-Za-z]: ]]; then # Convert backslashes to forward slashes win_path=$(echo "$new_path" | sed 's#\\#/#g') # If there's no slash after the colon (C:Games), insert it if [[ "$win_path" =~ ^[A-Za-z]:[^/] ]]; then win_path="${win_path:0:2}/${win_path:2}" fi # Extract drive letter and lowercase it drive=$(echo "$win_path" | cut -d':' -f1 | tr '[:upper:]' '[:lower:]') # Remove the drive and colon safely path_without_drive=$(echo "$win_path" | sed 's#^[A-Za-z]:##') # Build Linux path new_path="/mnt/$drive$path_without_drive" fi # ----------------------------------- 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 get_display_path 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: ${display_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" >> "${LOG_FILE}" echo "Linux Games Folder: $GAMES_PATH" >> "${LOG_FILE}" echo "Games Folder: $display_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 | tee -a "${LOG_FILE}" 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 | tee -a "${LOG_FILE}" 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 if [[ -n "$VIRTUAL_ENV" ]]; then deactivate fi # 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." echo "Created: $dir/list.ico" | tee -a "${LOG_FILE}" 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." echo "Created: $dir/del.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 for $game_id. 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}" "${HELPER_DIR}/art_downloader.py" "$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" <> "${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 [ -f "${ARTWORK_DIR}/tmp/art.zip" ]; 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=@${ARTWORK_DIR}/tmp/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 menu..."