PSBBN Definitive English Patch 2.0

This commit is contained in:
CosmicScale 2024-12-08 12:04:58 +00:00
parent 90bb3546c2
commit 821e5e95ee
204 changed files with 47759 additions and 10 deletions

63
01-Setup.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash
echo -e "\e[8;30;100t"
clear
echo " _____ _ ";
echo " / ___| | | ";
echo " \ \`--. ___| |_ _ _ _ __ ";
echo " \`--. \/ _ \ __| | | | '_ \ ";
echo " /\__/ / __/ |_| |_| | |_) |";
echo " \____/ \___|\__|\__,_| .__/ ";
echo " | | ";
echo " |_| ";
echo
echo "This script installs all dependencies required for the 'PSBBN Installer' and 'Game Installer'."
echo "It must be run first."
echo
echo "Press any key to continue..."
read -n 1 -s
# Update package list and install necessary packages
sudo apt update && sudo apt install -y axel imagemagick python3-venv python3-pip nodejs npm
if [ $? -ne 0 ]; then
echo
echo "Error: Package installation failed."
read -p "Press any key to exit..."
exit 1
fi
# Check if mkfs.exfat exists, and install exfat-fuse if not
if ! command -v mkfs.exfat &> /dev/null; then
echo
echo "mkfs.exfat not found. Installing exfat-fuse..."
sudo apt install -y exfat-fuse
if [ $? -ne 0 ]; then
echo
echo "Error: Failed to install exfat-fuse."
read -p "Press any key to exit..."
exit 1
fi
fi
# Setup Python virtual environment and install Python dependencies
python3 -m venv venv
if [ $? -ne 0 ]; then
echo
echo "Error: Failed to create Python virtual environment."
read -p "Press any key to exit..."
exit 1
fi
source venv/bin/activate
pip install lz4 natsort
if [ $? -ne 0 ]; then
echo
echo "Error: Failed to install Python dependencies."
read -p "Press any key to exit..."
deactivate
exit 1
fi
deactivate
echo
echo "Setup completed successfully!"
read -p "Press any key to exit..."

520
02-PSBBN-Installer.sh Executable file
View File

@ -0,0 +1,520 @@
#!/bin/bash
# Set terminal size: 100 columns and 40 rows
echo -e "\e[8;40;100t"
# Set paths
TOOLKIT_PATH=$(pwd)
ASSETS_DIR="${TOOLKIT_PATH}/assets"
INSTALL_LOG="${TOOLKIT_PATH}/PSBBN-installer.log"
clear
echo "####################################################################">> ${INSTALL_LOG};
date >> ${INSTALL_LOG}
# Choose the PS2 storage device
while true; do
clear
echo " ______ _________________ _ _ _____ _ _ _ ";
echo " | ___ \/ ___| ___ \ ___ \ \ | | |_ _| | | | | | ";
echo " | |_/ /\ \`--.| |_/ / |_/ / \| | | | _ __ ___| |_ __ _| | | ___ _ __ ";
echo " | __/ \`--. \ ___ \ ___ \ . \` | | || '_ \/ __| __/ _\` | | |/ _ \ '__|";
echo " | | /\__/ / |_/ / |_/ / |\ | _| || | | \__ \ || (_| | | | __/ | ";
echo " \_| \____/\____/\____/\_| \_/ \___/_| |_|___/\__\__,_|_|_|\___|_| ";
echo " ";
echo " Written by CosmicScale"
echo
echo | tee -a ${INSTALL_LOG}
lsblk -p -o MODEL,NAME,SIZE,LABEL,MOUNTPOINT | tee -a ${INSTALL_LOG}
echo | tee -a ${INSTALL_LOG}
read -p "Choose your PS2 HDD from the list above (e.g., /dev/sdx): " DEVICE
# Validate input
if [[ $DEVICE =~ ^/dev/sd[a-z]$ ]]; then
echo
echo -e "Are you sure you want to write to ${DEVICE}?" | tee -a ${INSTALL_LOG}
read -p "This will erase all data on the drive. (yes/no): " CONFIRM
if [[ $CONFIRM == "yes" ]]; then
break
else
echo "Aborted." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
fi
done
# 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 | tee -a ${INSTALL_LOG}
echo "Unmounting volumes associated with $DEVICE..."
for mount_point in $mounted_volumes; do
echo "Unmounting $mount_point..." | tee -a ${INSTALL_LOG}
if sudo umount "$mount_point"; then
echo "Successfully unmounted $mount_point." | tee -a ${INSTALL_LOG}
else
echo "Failed to unmount $mount_point. Please unmount manually." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
done
echo "All volumes unmounted for $DEVICE."
# URL of the webpage
URL="https://archive.org/download/psbbn-definitive-english-patch-v2"
echo | tee -a ${INSTALL_LOG}
echo "Checking for latest version of the PSBBN Definitive English patch..." | tee -a ${INSTALL_LOG}
# Download the HTML of the page
HTML_FILE=$(mktemp)
wget -O "$HTML_FILE" "$URL" >> ${INSTALL_LOG} 2>&1
# Extract .gz links and dates into a combined list
COMBINED_LIST=$(grep -oP '(?<=<td><a href=")[^"]+\.gz' "$HTML_FILE" | \
paste -d' ' <(grep -oP '(?<=<td>)[^<]+(?=</td>)' "$HTML_FILE" | \
grep -E '^\d{2}-\w{3}-\d{4}') -)
# Sort the combined list by date (most recent first), and get the latest file
LATEST=$(echo "$COMBINED_LIST" | sort -r | head -n 1 | cut -d' ' -f2)
if [ -z "$LATEST" ]; then
echo "Cound not find latest version."
# If $LATEST is empty, check for psbbn-definitive-image*.gz file
IMAGE_FILE=$(ls ${ASSETS_DIR}/psbbn-definitive-image*.gz 2>/dev/null)
if [ -n "$IMAGE_FILE" ]; then
# If image file exists, set LATEST to the image file name
LATEST=$(basename "$IMAGE_FILE")
echo "Found local file: ${LATEST}" | tee -a ${INSTALL_LOG}
else
rm "$HTML_FILE"
echo "Failed to download PSBBN image file. Aborting." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
else
echo "Latest version of PSBBN Definitive English patch is $LATEST"
fi
# Check for and delete older 'psbbn-definitive-image*.gz' files
for file in "${ASSETS_DIR}"/psbbn-definitive-image*.gz; do
if [[ -f "$file" && "$(basename "$file")" != "$LATEST" ]]; then
echo "Deleting old file: $file" | tee -a ${INSTALL_LOG}
rm "$file"
fi
done
# Check if the file exists in ${ASSETS_DIR}
if [[ -f "${ASSETS_DIR}/${LATEST}" && ! -f "${ASSETS_DIR}/${LATEST}.st" ]]; then
echo "File ${LATEST} already exists in ${ASSETS_DIR}. Skipping download." | tee -a ${INSTALL_LOG}
else
# Construct the full URL for the .gz file and download it
ZIP_URL="$URL/$LATEST"
# Proceed with download
echo "Downloading ${LATEST}..." | tee -a ${INSTALL_LOG}
axel -n 8 -a "$ZIP_URL" -o "${ASSETS_DIR}"
# Check if the file was downloaded successfully
if [[ -f "${ASSETS_DIR}/${LATEST}" && ! -f "${ASSETS_DIR}/${LATEST}.st" ]]; then
echo "Download completed: ${LATEST}" | tee -a ${INSTALL_LOG}
else
rm "$HTML_FILE"
echo "Download failed for ${LATEST}. Please check your internet connection and try again." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
# Clean up
rm "$HTML_FILE"
fi
echo | tee -a ${INSTALL_LOG}
echo "Checking for POPS binaries..."
# Check POPS files exist
if [[ -f "${ASSETS_DIR}/POPS-binaries-main/POPS.ELF" && -f "${ASSETS_DIR}/POPS-binaries-main/IOPRP252.IMG" ]]; then
echo "Both POPS.ELF and IOPRP252.IMG exist in ${ASSETS_DIR}. Skipping download." | tee -a ${INSTALL_LOG}
else
echo "One or both files are missing in ${ASSETS_DIR}." | tee -a ${INSTALL_LOG}
# 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 | tee -a ${INSTALL_LOG}
echo "POPS-binaries-main.zip found in ${ASSETS_DIR}. Extracting..." | tee -a ${INSTALL_LOG}
unzip -o ${ASSETS_DIR}/POPS-binaries-main.zip -d ${ASSETS_DIR} >> ${INSTALL_LOG} 2>&1
else
echo | tee -a ${INSTALL_LOG}
echo "Downloading POPS binaries..." | tee -a ${INSTALL_LOG}
axel -a https://archive.org/download/pops-binaries-PS2/POPS-binaries-main.zip -o "${ASSETS_DIR}"
unzip -o ${ASSETS_DIR}/POPS-binaries-main.zip -d ${ASSETS_DIR} >> ${INSTALL_LOG} 2>&1
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 | tee -a ${INSTALL_LOG}
echo "POPS binaries successfully extracted." | tee -a ${INSTALL_LOG}
else
echo | tee -a ${INSTALL_LOG}
echo "Error: One or both files (POPS.ELF, IOPRP252.IMG) are missing after extraction." | tee -a ${INSTALL_LOG}
read -p "You can install POPS manually later. Press any key to continue..." | tee -a ${INSTALL_LOG}
fi
fi
PSBBN_IMAGE="${ASSETS_DIR}/${LATEST}"
# Write the PSBBN image
echo | tee -a ${INSTALL_LOG}
echo "Writing the PSBBN image to ${DEVICE}..." | tee -a ${INSTALL_LOG}
if gunzip -c ${PSBBN_IMAGE} | sudo dd of=${DEVICE} bs=4M status=progress 2>&1 | tee -a ${INSTALL_LOG} ; then
sync
echo
echo "Verifying installation..."
if sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc "${DEVICE}" | grep -q '__common'; then
echo "Verification successful. PSBBN image installed successfully." | tee -a ${INSTALL_LOG}
else
echo "Error: Verification failed on ${DEVICE}." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
else
echo "Error: Failed to write the image to ${DEVICE}." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
# Function to find available space
function function_space() {
output=$(sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc ${DEVICE} 2>&1)
# Check for the word "aborting" in the output
if echo "$output" | grep -q "aborting"; then
echo "${DEVICE}: APA partition is broken; aborting." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
# Extract the "used" value, remove "MB" and any commas
used=$(echo "$output" | awk '/used:/ {print $6}' | sed 's/,//; s/MB//')
capacity=124416
# Calculate available space (capacity - used)
available=$((capacity - used))
}
# Call the function retreive avaliable space
function_space
# Divide available space by 128 to calculate the maximum number of partitions
PP=$(((available - 18560) / 128))
# Loop until the user enters a valid number of partitions
while true; do
echo | tee -a ${INSTALL_LOG}
echo " #########################################################################################"
echo " # 'OPL Launcher' partitions are used to launch games from the 'Game Channel.' #"
echo " # Consider how many games you want to install, and plan for future expansion. #"
echo " # Additional 'OPL Launcher' partitions cannot be created after setup. #"
echo " # #"
echo " # Note: The more partitions you create, the longer it will take to load the game list. #"
echo " # Fewer 'OPL Launcher' partitions leave more space for the 'Music' partition #"
echo " # and the 'POPS' partition for PS1 games. #"
echo " # #"
echo " # A good starting point is 200 partitions, but feel free to experiment. #"
echo " #########################################################################################"
echo
read -p "Enter the number of \"OPL Launcher\" partitions you would like (1-$PP): " PARTITION_COUNT
# Check if input is a valid number within the specified range
if [[ "$PARTITION_COUNT" =~ ^[0-9]+$ ]] && [ "$PARTITION_COUNT" -ge 1 ] && [ "$PARTITION_COUNT" -le $PP ]; then
break # Exit the loop if the input is valid
else
echo "Invalid input. Please enter a number between 1 and $PP." | tee -a ${INSTALL_LOG}
fi
done
GB=$(((available + 2048 - 10368 - (PARTITION_COUNT * 128)) / 1024))
# Prompt user for partition size for music, validate input, and keep asking until valid input is provided
while true; do
echo | tee -a ${INSTALL_LOG}
echo "What size would you like the \"Music\" partition to be?" | tee -a ${INSTALL_LOG}
echo "Remaining space will be allocated to the __.POPS partition for PS1 games"
echo "Minimum 10 GB, Available space: $GB GB" | tee -a ${INSTALL_LOG}
read -p "Enter partition size (in GB): " gb_size
# Check if the input is a valid number
if [[ ! "$gb_size" =~ ^[0-9]+$ ]]; then
echo "Invalid input. Please enter a valid number." | tee -a ${INSTALL_LOG}
continue
fi
# Check if the value is within the valid range
if (( gb_size >= 10 && gb_size <= GB )); then
echo "Valid partition size: $gb_size GB" | tee -a ${INSTALL_LOG}
break # Exit the loop if the input is valid
else
echo "Invalid size. Please enter a value between 10 and $GB GB." | tee -a ${INSTALL_LOG}
fi
done
music_partition=$((gb_size * 1024 - 2048))
pops_partition=$((available - (PARTITION_COUNT * 128) - music_partition -128))
GB=$((pops_partition / 1024))
echo | tee -a ${INSTALL_LOG}
echo "$GB GB alocated for __.POPS partition." | tee -a ${INSTALL_LOG}
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mkpart __linux.8 ${music_partition}M REISER\n"
COMMANDS+="mkpart __.POPS ${pops_partition}M PFS\n"
COMMANDS+="mkpart +OPL 128M PFS\nexit"
echo -e "$COMMANDS" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${INSTALL_LOG} 2>&1
# Call the function to retrieve available space
function_space
echo | tee -a ${INSTALL_LOG}
echo "Creating $PARTITION_COUNT \"OPL Launcher\" partitions..." | tee -a ${INSTALL_LOG}
# Set starting partition number
START_PARTITION_NUMBER=1
# Initialize a counter for the successfully created partitions
successful_count=0
# Loop to create the specified number of partitions
for ((i = 0; i < PARTITION_COUNT; i++)); do
# Check if available space is at least 128 MB
if [ "$available" -lt 128 ]; then
echo | tee -a ${INSTALL_LOG}
echo "Insufficient space for another partition." | tee -a ${INSTALL_LOG}
break
fi
# Calculate the current partition number (starting at $START_PARTITION_NUMBER)
PARTITION_NUMBER=$((START_PARTITION_NUMBER + i))
# Generate the partition label dynamically (PP.001, PP.002, etc.)
PARTITION_LABEL=$(printf "PP.%03d" "$PARTITION_NUMBER")
# Build the command to create this partition
COMMAND="device ${DEVICE}\nmkpart ${PARTITION_LABEL} 128M PFS\nexit"
# Run the partition creation command in PFS Shell
echo -e "$COMMAND" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${INSTALL_LOG} 2>&1
# Increment the count of successfully created partitions
((successful_count++))
# Call function_space after exiting PFS Shell to update the available space
function_space
done
# Display the total number of partitions created successfully
echo | tee -a ${INSTALL_LOG}
echo "$successful_count \"OPL Launcher\" partitions created successfully." | tee -a ${INSTALL_LOG}
echo | tee -a ${INSTALL_LOG}
echo "Modifying partition headers..." | tee -a ${INSTALL_LOG}
cd "${TOOLKIT_PATH}/assets/"
# After partitions are created, modify the header for each partition
for ((i = START_PARTITION_NUMBER; i < START_PARTITION_NUMBER + PARTITION_COUNT; i++)); do
PARTITION_LABEL=$(printf "PP.%03d" "$i")
sudo "${TOOLKIT_PATH}/helper/HDL Dump.elf" modify_header "${DEVICE}" "${PARTITION_LABEL}" >> ${INSTALL_LOG} 2>&1
done
echo | tee -a ${INSTALL_LOG}
echo "Making \"res\" folders..." | tee -a ${INSTALL_LOG}
# make 'res' directory on all PP partitions
COMMANDS="device ${DEVICE}\n"
for ((i = START_PARTITION_NUMBER; i < START_PARTITION_NUMBER + PARTITION_COUNT; i++)); do
PARTITION_LABEL=$(printf "PP.%03d" "$i")
COMMANDS+="mount ${PARTITION_LABEL}\n"
COMMANDS+="mkdir res\n"
COMMANDS+="umount\n"
done
COMMANDS+="exit"
# Pipe all commands to PFS Shell for mounting, copying, and unmounting
echo -e "$COMMANDS" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${INSTALL_LOG} 2>&1
echo | tee -a ${INSTALL_LOG}
echo "Installing POPS and OPL..." | tee -a ${INSTALL_LOG}
# Copy POPS files and OPL to relevent partitions
COMMANDS="device ${DEVICE}\n"
COMMANDS+="mount +OPL\n"
COMMANDS+="put OPNPS2LD.ELF\n"
COMMANDS+="umount\n"
COMMANDS+="mount __common\n"
COMMANDS+="mkdir POPS\n"
COMMANDS+="cd POPS\n"
COMMANDS+="put IGR_BG.TM2\n"
COMMANDS+="put IGR_NO.TM2\n"
COMMANDS+="put IGR_YES.TM2\n"
COMMANDS+="lcd POPS-binaries-main\n"
COMMANDS+="put POPS.ELF\n"
COMMANDS+="put IOPRP252.IMG\n"
COMMANDS+="cd ..\n"
COMMANDS+="umount\n"
COMMANDS+="exit"
# Pipe all commands to PFS Shell for mounting, copying, and unmounting
echo -e "$COMMANDS" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${INSTALL_LOG} 2>&1
cd "${TOOLKIT_PATH}"
#//////////////////////////////////////////////// APA-Jail code by Berion ////////////////////////////////////////////////
function function_disk_size_check() {
LBA_MAX=$(sudo blockdev --getsize ${DEVICE})
if [ ${LBA_MAX} -gt 4294967296 ]; then
echo -e "ERROR: Disk size exceeding 2TiB. Formatting aborted." | tee -a ${INSTALL_LOG}
function_exit
fi
}
function function_apajail_magic_number() {
echo ${MAGIC_NUMBER} | xxd -r -p > /tmp/apajail_magic_number.bin
sudo dd if=/tmp/apajail_magic_number.bin of=${DEVICE} bs=8 count=1 seek=28 conv=notrunc >> ${INSTALL_LOG} 2>&1
}
function function_make_ps2_dirs() {
if [ ! -d "/tmp/ps2_dirs" ]; then
mkdir /tmp/ps2_dirs
fi
if [ "$(echo ${DEVICE} | grep -o /dev/loop)" = "/dev/loop" ]; then
sudo mount ${DEVICE}p${PARTITION_NUMBER} /tmp/ps2_dirs
else sudo mount ${DEVICE}${PARTITION_NUMBER} /tmp/ps2_dirs
fi
cd /tmp/ps2_dirs
sudo mkdir -p APPS/ # Open PS2 Loader: applications
sudo mkdir -p ART/ # Open PS2 Loader: disc covers (<GameID>_COV.png, <GameID>_ICO.png, <GameID>_SCR.png etc.)
sudo mkdir -p CFG/ # Open PS2 Loader: per game configs (<GameID>.cfg)
sudo mkdir -p CHT/ # Open PS2 Loader: cheats (<GameID>.cht)
sudo mkdir -p CD/ # Open PS2 Loader: CD disc images (*.iso, *.zso)
sudo mkdir -p DVD/ # Open PS2 Loader: DVD disc images (*.iso, *.zso)
sudo mkdir -p LNG # Open PS2 Loader: Language files
sudo mkdir -p THM/ # Open PS2 Loader: theme dirs (thm_<ThemeName>/*)
sudo mkdir -p VMC/ # Open PS2 Loader: non-ECC PS2 Memory Card images (generic or <GameID>_0.bin, <GameID>_1.bin)
sync
sudo umount -l /tmp/ps2_dirs
}
function function_apa_checksum_fix() {
sudo dd if=${DEVICE} of=/tmp/apa_header_full.bin bs=512 count=2 >> ${INSTALL_LOG} 2>&1
"${TOOLKIT_PATH}/helper/PS2 APA Header Checksum Fixer.elf" /tmp/apa_header_full.bin | sed -n 8p | awk '{print $6}' | xxd -r -p > /tmp/apa_header_checksum.bin
sudo dd if=/tmp/apa_header_checksum.bin of=${DEVICE} conv=notrunc >> ${INSTALL_LOG} 2>&1
}
function function_clear_temp() {
sudo rm /tmp/apa_header_address.bin &> /dev/null
sudo rm /tmp/apa_header_boot.bin &> /dev/null
sudo rm /tmp/apa_header_checksum.bin &> /dev/null
sudo rm /tmp/apa_header_full.bin &> /dev/null
sudo rm /tmp/apa_journal.bin &> /dev/null
sudo rm /tmp/apa_header_probe.bin &> /dev/null
sudo rm /tmp/apa_header_size.bin &> /dev/null
sudo rm /tmp/apajail_magic_number.bin &> /dev/null
sudo rm /tmp/apa_index.xz &> /dev/null
sudo rm /tmp/gpt_2nd.xz &> /dev/null
}
echo | tee -a ${INSTALL_LOG}
echo "Running APA-Jail by Berion..." | tee -a ${INSTALL_LOG}
# Hashed out for testing. Larger drive support most likley possible when using a restored disc image from a smaller drive
# function_disk_size_check
# Signature injection (type A2):
MAGIC_NUMBER="4150414A2D413200"
function_apajail_magic_number
# Setting up MBR:
{
echo -e ",128GiB,17\n,32MiB,17\n,,07" | sudo sfdisk ${DEVICE}
sudo partprobe ${DEVICE}
if [ "$(echo ${DEVICE} | grep -o /dev/loop)" = "/dev/loop" ]; then
sudo mkfs.ext2 -L "RECOVERY" ${DEVICE}p2
sudo mkfs.exfat -c 32K -L "OPL" ${DEVICE}p3
else
sleep 4
sudo mkfs.ext2 -L "RECOVERY" ${DEVICE}2
sudo mkfs.exfat -c 32K -L "OPL" ${DEVICE}3
fi
} >> ${INSTALL_LOG} 2>&1
PARTITION_NUMBER=3
function_make_ps2_dirs
# Finalising recovery:
if [ ! -d "${TOOLKIT_PATH}/storage/hdd/recovery" ]; then
mkdir -p ${TOOLKIT_PATH}/storage/hdd/recovery
fi
if [ "$(echo ${DEVICE} | grep -o /dev/loop)" = "/dev/loop" ]; then
sudo mount ${DEVICE}p2 ${TOOLKIT_PATH}/storage/hdd/recovery
else sudo mount ${DEVICE}2 ${TOOLKIT_PATH}/storage/hdd/recovery
fi
sudo dd if=${DEVICE} bs=128M count=1 status=noxfer 2>> ${INSTALL_LOG} | xz -z > /tmp/apa_index.xz 2>> ${INSTALL_LOG}
sudo cp /tmp/apa_index.xz ${TOOLKIT_PATH}/storage/hdd/recovery
LBA_MAX=$(sudo blockdev --getsize ${DEVICE})
LBA_GPT_BUP=$(echo $(($LBA_MAX-33)))
sudo dd if=${DEVICE} skip=${LBA_GPT_BUP} bs=512 count=33 status=noxfer 2>> ${INSTALL_LOG} | xz -z > /tmp/gpt_2nd.xz 2>> ${INSTALL_LOG}
sudo cp /tmp/gpt_2nd.xz ${TOOLKIT_PATH}/storage/hdd/recovery
sync
sudo umount -l ${TOOLKIT_PATH}/storage/hdd/recovery
rmdir ${TOOLKIT_PATH}/storage/hdd/recovery
function_apa_checksum_fix
function_clear_temp
unset DEVICE
unset LBA_GPT_BUP
unset LBA_MAX
unset MAGIC_NUMBER
unset PARTITION_NUMBER
#/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
# Run the command and capture output
output=$(sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc ${DEVICE} 2>&1)
# Check for the word "aborting" in the output
if echo "$output" | grep -q "aborting"; then
echo "Error: APA partition is broken on ${DEVICE}. Install failed." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
if sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc "${DEVICE}" | grep -q '__.POPS' && \
sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc "${DEVICE}" | grep -q '__linux.8' && \
sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc "${DEVICE}" | grep -q "PP.${PARTITION_COUNT}" && \
sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc "${DEVICE}" | grep -q '+OPL'; then
echo "All partitions were created successfully." | tee -a ${INSTALL_LOG}
else
echo "Error: Some partitions are missing on ${DEVICE}." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
# Check if 'OPL' is found in the 'lsblk' output and if it matches the device
if ! lsblk -p -o NAME,LABEL | grep "OPL" | grep -q "${DEVICE}"; then
echo "Error: APA-Jail failed on ${DEVICE}." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
echo | tee -a ${INSTALL_LOG}
read -p "PSBBN successfully installed. Press any key to exit. " | tee -a ${INSTALL_LOG}

463
03-Game-Installer.sh Executable file
View File

@ -0,0 +1,463 @@
#!/bin/bash
# Set terminal size: 100 columns and 40 rows
echo -e "\e[8;40;100t"
# Set paths
TOOLKIT_PATH=$(pwd)
ICONS_DIR="${TOOLKIT_PATH}/icons"
ARTWORK_DIR="${ICONS_DIR}/art"
POPSTARTER_DIR="${TOOLKIT_PATH}/assets/POPSTARTER.ELF"
LOG_FILE="${TOOLKIT_PATH}/game-installer.log"
MISSING_ART=${TOOLKIT_PATH}/missing-art.log
# Modify this path if your games are stored in a different location:
GAMES_PATH="${TOOLKIT_PATH}/games"
POPS_FOLDER="${GAMES_PATH}/POPS"
ALL_GAMES="${GAMES_PATH}/master.list"
cd ${TOOLKIT_PATH}
clear
date >> ${LOG_FILE}
# Choose the PS2 storage device
while true; do
clear
echo "####################################################################">> ${LOG_FILE};
echo " _____ _____ _ _ _ ";
echo " | __ \ |_ _| | | | | | ";
echo " | | \/ __ _ _ __ ___ ___ | | _ __ ___| |_ __ _| | | ___ ___ ";
echo " | | __ / _\` | '_ \` _ \ / _ \ | || '_ \/ __| __/ _\` | | |/ _ \ __|";
echo " | |_\ \ (_| | | | | | | __/ _| || | | \__ \ || (_| | | | __/ | ";
echo " \____/\__,_|_| |_| |_|\___| \___/_| |_|___/\__\__,_|_|_|\___|_| ";
echo " ";
echo " Written by CosmicScale"
echo | tee -a ${LOG_FILE}
lsblk -p -o MODEL,NAME,SIZE,LABEL,MOUNTPOINT | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
read -p "Choose your PS2 HDD from list above e.g. /dev/sdx): " DEVICE
# Validate input
if [[ $DEVICE =~ ^/dev/sd[a-z]$ ]]; then
echo
echo -e "Selected drive: \"${DEVICE}\"" | tee -a ${LOG_FILE}
break
fi
done
# 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 | tee -a ${INSTALL_LOG}
echo "Unmounting volumes associated with $DEVICE..."
for mount_point in $mounted_volumes; do
echo "Unmounting $mount_point..." | tee -a ${INSTALL_LOG}
if sudo umount "$mount_point"; then
echo "Successfully unmounted $mount_point." | tee -a ${INSTALL_LOG}
else
echo "Failed to unmount $mount_point. Please unmount manually." | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
done
echo "All volumes unmounted for $DEVICE."| tee -a ${INSTALL_LOG}
# Validate the GAMES_PATH
if [[ ! -d "$GAMES_PATH" ]]; then
echo
echo "Error: GAMES_PATH is not a valid directory: $GAMES_PATH" | tee -a ${LOG_FILE}
read -p "Press any key to exit..."
exit 1
fi
echo | tee -a ${LOG_FILE}
echo "GAMES_PATH is valid: $GAMES_PATH" | tee -a ${LOG_FILE}
# Check if the file exists
if [ -f "./venv/bin/activate" ]; then
echo "The Python virtual environment exists."
else
echo "Error: The Python virtual environment does not exist."
read -p "Press any key to exit..."
exit 1
fi
# Activate the virtual environment
source "./venv/bin/activate"
# Check if activation was successful
if [ $? -ne 0 ]; then
echo "Failed to activate the virtual environment" | tee -a ${INSTALL_LOG}
read -p "Press any key to exit..."
exit 1
fi
# Create games list of PS1 and PS2 games to be installed
echo | tee -a ${LOG_FILE}
echo "Creating PS1 games list..."| tee -a ${LOG_FILE}
python3 ./helper/list-builder-ps1.py ${GAMES_PATH} | tee -a ${LOG_FILE}
echo "Creating PS2 games list..."| tee -a ${LOG_FILE}
python3 ./helper/list-builder-ps2.py ${GAMES_PATH} | tee -a ${LOG_FILE}
# Deactivate the virtual environment
deactivate
# Create master list combining PS1 and PS2 games to a single list
if [[ ! -f "${GAMES_PATH}/ps1.list" && ! -f "${GAMES_PATH}/ps2.list" ]]; then
echo "No games found to install."| tee -a "${LOG_FILE}"
read -p "Press any key to exit..."
exit 1
elif [[ ! -f "${GAMES_PATH}/ps1.list" ]]; then
cat "${GAMES_PATH}/ps2.list" > "${GAMES_PATH}/master.list" 2>> "${LOG_FILE}"
else
cat "${GAMES_PATH}/ps1.list" > "${GAMES_PATH}/master.list" 2>> "${LOG_FILE}"
cat "${GAMES_PATH}/ps2.list" >> "${GAMES_PATH}/master.list" 2>> "${LOG_FILE}"
fi
echo | tee -a ${LOG_FILE}
echo "Games list successfully created"| tee -a ${LOG_FILE}
# Count the number of games to be installed
count=$(grep -c '^[^[:space:]]' ${ALL_GAMES})
echo "Number of games to install: $count" | tee -a ${LOG_FILE}
# Count the number of 'OPL Launcher' partitions available
partition_count=$(sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc $DEVICE | grep -o 'PP\.[0-9]\+' | grep -v '^$' | wc -l)
echo "Number of PP partitions: $partition_count" | tee -a ${LOG_FILE}
# Check if the count exceeds the partition count
if [ "$count" -gt "$partition_count" ]; then
echo
echo "Error: Number of games ($count) exceeds the available partitions ($partition_count)." | tee -a "${LOG_FILE}"
read -p "Press any key to exit..."
exit 1
fi
# Get the list of partition names
partitions=$(sudo ${TOOLKIT_PATH}/helper/HDL\ Dump.elf toc $DEVICE | grep -o 'PP\.[0-9]*')
missing_partitions=()
# Check for each partition from PP.001 to PP.<partition_count> and identify any missing partitions
for i in $(seq -f "%03g" 1 "$partition_count"); do
partition_name="PP.$i"
if ! echo "$partitions" | grep -q "$partition_name"; then
missing_partitions+=("$partition_name")
fi
done
if [ ${#missing_partitions[@]} -eq 0 ]; then
echo "All partitions (PP.001 to PP.${partition_count}) are present." | tee -a ${LOG_FILE}
else
echo "Missing partitions:" | tee -a ${LOG_FILE}
for partition in "${missing_partitions[@]}"; do
echo "$partition" | tee -a ${LOG_FILE}
read -p "Press any key to exit..."
exit 1
done
fi
# Set the highest starting partition based on available PP partitions
START_PARTITION_NUMBER=$(echo "$partitions" | grep -o 'PP\.[0-9]*' | sort -V | tail -n 1 | sed 's/PP\.//')
START_PARTITION_NUMBER=$((START_PARTITION_NUMBER))
echo | tee -a ${LOG_FILE}
echo "Ready to install games. Press any key to continue..."
read -n 1 -s
echo | tee -a ${LOG_FILE}
echo "Preparing to sync PS1 games..." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
# Step 1: Create matching .ELF files for .VCD files
echo "Creating matching .ELF files for .VCDs..." | tee -a ${LOG_FILE}
for vcd_file in "$POPS_FOLDER"/*.VCD; do
if [ -f "$vcd_file" ]; then
# Extract the base name (without extension) from the .VCD file
base_name=$(basename "$vcd_file" .VCD)
# Define the corresponding .ELF file name
elf_file="$POPS_FOLDER/$base_name.ELF"
# Copy and rename POPSTARTER.ELF to match the .VCD file
if [ ! -f "$elf_file" ]; then
echo "Creating $elf_file..." | tee -a ${LOG_FILE}
cp "$POPSTARTER_DIR" "$elf_file"
fi
fi
done
echo "Matching .ELF files created successfully." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
# Step 2: Delete .ELF files without matching .VCD files
echo "Removing orphan .ELF files..." | tee -a ${LOG_FILE}
for elf_file in "$POPS_FOLDER"/*.ELF; do
if [ -f "$elf_file" ]; then
# Extract the base name (without extension) from the .ELF file
base_name=$(basename "$elf_file" .ELF)
# Check if a corresponding .VCD file exists
vcd_file="$POPS_FOLDER/$base_name.VCD"
if [ ! -f "$vcd_file" ]; then
echo "Deleting orphan $elf_file..." | tee -a ${LOG_FILE}
rm "$elf_file"
fi
fi
done
echo "Orphan .ELF files removed successfully." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
# Generate the local file list directly in a variable
local_files=$(ls -1 $POPS_FOLDER | sort)
# 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
ps2_files=$(echo -e "$COMMANDS" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" 2>/dev/null | grep -iE "\.vcd$|\.elf$" | sort)
# Compute differences and store them in variables
files_only_in_local=$(comm -23 <(echo "$local_files") <(echo "$ps2_files"))
files_only_in_ps2=$(comm -13 <(echo "$local_files") <(echo "$ps2_files"))
echo "Files to delete:" | tee -a ${LOG_FILE}
echo "$files_only_in_ps2" | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
echo "Files to copy:" | tee -a ${LOG_FILE}
echo "$files_only_in_local" | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
# Syncing PS1 games
cd $POPS_FOLDER
combined_commands="device ${DEVICE}\n"
combined_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
combined_commands+="rm \"$file\"\n"
done <<< "$files_only_in_ps2"
else
echo "No files to delete." | tee -a ${LOG_FILE}
fi
echo | tee -a ${LOG_FILE}
# Add put commands for files_only_in_local
if [ -n "$files_only_in_local" ]; then
while IFS= read -r file; do
combined_commands+="put \"$file\"\n"
done <<< "$files_only_in_local"
else
echo "No files to copy." | tee -a ${LOG_FILE}
fi
combined_commands+="umount\n"
combined_commands+="exit"
# Execute the combined commands with PFS Shell
echo "Syncing PS1 games to HDD..." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
echo -e "$combined_commands" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${LOG_FILE} 2>&1
echo | tee -a ${LOG_FILE}
echo "PS1 games synced sucessfully." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
cd ${TOOLKIT_PATH}
# Syncing PS2 games
echo "Mounting OPL partition" | tee -a ${LOG_FILE}
mkdir ${TOOLKIT_PATH}/OPL 2>> "${LOG_FILE}"
sudo mount ${DEVICE}3 ${TOOLKIT_PATH}/OPL
echo | tee -a ${LOG_FILE}
echo "Syncing PS2 games..." | tee -a ${LOG_FILE}
sudo rsync -r --progress --ignore-existing --delete ${GAMES_PATH}/CD/ ${TOOLKIT_PATH}/OPL/CD/ | tee -a ${LOG_FILE}
sudo rsync -r --progress --ignore-existing --delete ${GAMES_PATH}/DVD/ ${TOOLKIT_PATH}/OPL/DVD/ | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
echo "PS2 games sucessfully synced" | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
echo "Unmounting OPL partition..." | tee -a ${LOG_FILE}
sudo umount ${TOOLKIT_PATH}/OPL
echo | tee -a ${LOG_FILE}
mkdir -p "${ARTWORK_DIR}/tmp" 2>> "${LOG_FILE}"
echo | tee -a ${LOG_FILE}
echo "Downloading artwork..." | tee -a ${LOG_FILE}
# First loop: Run the art downloader script for each game_id if artwork doesn't already exist
while IFS='|' read -r game_title game_id publisher disc_type file_name; 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 $game_id already exists. Skipping download." | tee -a ${LOG_FILE}
else
# If the file doesn't exist, run the art downloader
echo "Running art downloader for game ID: $game_id" | tee -a ${LOG_FILE}
node ${TOOLKIT_PATH}/helper/art_downloader.js "$game_id" | tee -a ${LOG_FILE}
fi
done < "$ALL_GAMES"
echo | tee -a ${LOG_FILE}
echo "Converting artwork..." | tee -a ${LOG_FILE}
# Define input directory
input_dir="${ARTWORK_DIR}/tmp"
# Check if the directory contains any files
if compgen -G "${input_dir}/*" > /dev/null; then
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}/${base_name}.png"
# Convert each file to .png with resizing and 8-bit depth
convert "$file" -resize 256x256 -depth 8 -dither FloydSteinberg -colors 256 "$output" | tee -a ${LOG_FILE}
done
rm "${input_dir}"/*
else
echo "No files to process in ${input_dir}" | tee -a ${LOG_FILE}
fi
echo | tee -a ${LOG_FILE}
echo "Creating game assets..." | tee -a ${LOG_FILE}
# Read the file line by line
while IFS='|' read -r game_title game_id publisher disc_type file_name; do
# Create a sub-folder named after the game_id
game_dir="$ICONS_DIR/$game_id"
mkdir -p "$game_dir" | tee -a ${LOG_FILE}
# Generate the launcher.cfg file
launcher_cfg_filename="$game_dir/launcher.cfg"
cat > "$launcher_cfg_filename" <<EOL
file_name=$file_name
title_id=$game_id
disc_type=$disc_type
EOL
echo "Created info.sys: $launcher_cfg_filename" | tee -a ${LOG_FILE}
# Generate the info.sys file
info_sys_filename="$game_dir/info.sys"
cat > "$info_sys_filename" <<EOL
title = $game_title
title_id = $game_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 = 1
copyright_imgcount = 1
genre =
parental_lock = 1
effective_date = 0
expire_date = 0
violence_flag = 0
content_type = 255
content_subtype = 0
EOL
echo "Created info.sys: $info_sys_filename" | tee -a ${LOG_FILE}
# Copy the matching .png file and rename it to jkt_001.png
png_file="${TOOLKIT_PATH}/icons/art/${game_id}.png"
if [[ -f "$png_file" ]]; then
cp "$png_file" "$game_dir/jkt_001.png"
echo "Artwork found for $game_title" | tee -a ${LOG_FILE}
else
if [[ "$disc_type" == "POPS" ]]; then
cp "${TOOLKIT_PATH}/icons/art/ps1.png" "$game_dir/jkt_001.png"
echo "Artwork not found for $game_title. Using default PS1 image" | tee -a ${LOG_FILE}
echo "$game_id $game_title" >> ${MISSING_ART}
else
cp "${TOOLKIT_PATH}/icons/art/ps2.png" "$game_dir/jkt_001.png"
echo "Artwork not found for $game_title. Using default PS2 image" | tee -a ${LOG_FILE}
echo "$game_id $game_title" >> ${MISSING_ART}
fi
fi
done < "$ALL_GAMES"
echo | tee -a ${LOG_FILE}
echo "All .cfg, info.sys, and .png files have been created in their respective sub-folders." | tee -a ${LOG_FILE}
echo | tee -a ${LOG_FILE}
echo "Installing game assets..." | tee -a ${LOG_FILE}
cd ${ICONS_DIR}
# Build the mount/copy/unmount commands for all partitions
COMMANDS="device ${DEVICE}\n"
i=0
while IFS='|' read -r game_title game_id publisher disc_type file_name; do
# Calculate the partition label for the current iteration, starting from the highest partition and counting down
PARTITION_LABEL=$(printf "PP.%03d" "$((START_PARTITION_NUMBER - i))")
COMMANDS+="mount ${PARTITION_LABEL}\n"
COMMANDS+="cd ..\n"
COMMANDS+="rm OPL-Launcher-BDM.KELF\n"
COMMANDS+="put OPL-Launcher-BDM.KELF\n"
# Navigate into the sub-directory named after the gameid
COMMANDS+="lcd ./${game_id}\n"
COMMANDS+="rm 'launcher.cfg'\n"
COMMANDS+="put 'launcher.cfg'\n"
COMMANDS+="cd res\n"
COMMANDS+="rm info.sys\n"
COMMANDS+="put info.sys\n"
COMMANDS+="rm jkt_001.png\n"
COMMANDS+="put jkt_001.png\n"
COMMANDS+="umount\n"
COMMANDS+="lcd ..\n"
# Increment the loop counter
((i++))
done < "$ALL_GAMES"
# Process remaining partitions after the games
for ((j = START_PARTITION_NUMBER - i; j >= 1; j--)); do
PARTITION_LABEL=$(printf "PP.%03d" "$j")
COMMANDS+="mount ${PARTITION_LABEL}\n"
COMMANDS+="cd ..\n"
COMMANDS+="rm OPL-Launcher-BDM.KELF\n"
COMMANDS+="put OPL-Launcher-BDM.KELF\n"
COMMANDS+="rm 'launcher.cfg'\n"
COMMANDS+="cd res\n"
COMMANDS+="rm info.sys\n"
COMMANDS+="put info.sys\n"
COMMANDS+="rm jkt_001.png\n"
COMMANDS+="umount\n"
done
COMMANDS+="exit"
# Pipe all commands to PFS Shell for mounting, copying, and unmounting
echo -e "$COMMANDS" | sudo "${TOOLKIT_PATH}/helper/PFS Shell.elf" >> ${LOG_FILE} 2>&1
echo | tee -a ${LOG_FILE}
echo "Cleaning up..." | tee -a ${LOG_FILE}
# Loop through all items in the target directory
for item in "$ICONS_DIR"/*; do
# Check if the item is a directory and not the 'art' folder
if [ -d "$item" ] && [ "$(basename "$item")" != "art" ]; then
rm -rf "$item" >> ${LOG_FILE} 2>&1
fi
done
echo | tee -a ${LOG_FILE}
echo "Game installer script complete" | tee -a ${LOG_FILE}
echo
read -p "Press any key to continue..."

104
README.md
View File

@ -1,29 +1,110 @@
# PlayStation Broadband Navigator (PSBBN) Definitive English Patch
# PSBBN Definitive English Patch
This is the definitive English patch for Sony's PlayStation Broadband Navigator (PSBBN) software for the PlayStation 2 (PS2) video game console.
This is the definitive English patch of Sony's "PlayStation Broadband Navigator" (PSBBN) software for the "PlayStation 2" (PS2) video game console.
You can find out more about the PSBBN software on Wikipedia [here](https://en.wikipedia.org/wiki/PlayStation_Broadband_Navigator).
You can find out more about the PSBBN software on [Wikipedia](https://en.wikipedia.org/wiki/PlayStation_Broadband_Navigator).
## Patch Features
- A full English translation of the stock Japanese BB Navigator version 0.32
- All binaries, XML files, textures, and pictures have been translated*
- Compatible with any fat model PS2 console regardless of region**
- DNAS authorization checks bypassed to enable online connectivity
- Access working mirrors for the online game channels from Sony, Hudson, EA, Konami, Capcom, Namco, and KOEI. Hosted courtesy of vitas155 at psbbn.ru
- English translations of the online game channels from Sony, Hudson, EA, Konami, Capcom, Namco, and KOEI. Hosted courtesy of vitas155 at [psbbn.ru](https://psbbn.ru/)
- "Audio Player" feature re-added to the Music Channel from an earlier release of PSBBN, allowing compatibility with NetMD MiniDisc Recorders
- Associated manual pages and troubleshooting regarding the "Audio Player" feature translated and re-added to the user guide
- Japanese qwerty on-screen keyboard replaced with US English on-screen keyboard
Video demonstrating how PSBBN can be used in 2024. **Note**: Additional software and setup is required to achieve everything shown in this video. Click to watch:
## New to version 2.0
- Large HDD support. No longer limited to 128 GB, now with support for drives up to 128 PB
- Supports 700 games, all launchable from the Game Channel
- Set a custom size for your music partition. Original limit of 5 GB allowed the storage of around 7 albums. Now the partition can be up to 97 GB for around 150 albums
- exFAT partition for easy install of PS2 games
- wLaunchELF is pre-installed
- PS2 Linux is pre-installed. Just hold any button on the controller at startup to boot into Linux
- Bandai and SCEI online channels have been added to the Game Channel
- Some minor fixes to the English translation
[![IMAGE ALT TEXT HERE](https://github.com/user-attachments/assets/298c8c0b-5726-4485-840d-9d567498fd95)](https://www.youtube.com/watch?v=kR1MVcAkW5M)
Video demonstrating how PSBBN can be used in 2024:
[![PSBBN in 2024](https://github.com/user-attachments/assets/298c8c0b-5726-4485-840d-9d567498fd95)](https://www.youtube.com/watch?v=kR1MVcAkW5M)
## New installation scripts
These scripts are essential for unlocking all the new features exclusive to version 2.0. They require a Linux environment to run. If Linux is not installed on your PC, you can use a bootable USB drive or a virtual machine. Only Debian-based distributions are supported, with Linux Mint being the recommended choice. You will require a HDD/SSD for your PS2 that is larger than 128 GB, ideally 500 GB or larger. I highly recommend a SSD for better performance. The HDD/SSD can be connected to your PC internally or via USB.
### Setup script:
`01-Setup.sh` installs all the necessary dependencies for the other scripts and must be run first.
### PSBBN installer script:
`02-PSBBN-Installer.sh` fully automates the installation of PSBBN
- Downloads the latest version of the `PSBBN Definitive English patch` from archive.org and installs it
- Asks how many 'OPL Launcher' partitions you'd like to create (for up to 700 games!)
- Asks how large youd like the music partition
- Creates a __.POPS partition for PS1 games with the remaining space up to 128 GB
- Installs a custom build of OPL with exFAT and Auto Launch support for BDM devices
- Installs POPStarter
- Runs APA-Jail and creates an exFAT partition with all remaining disk space
### Game installer script:
`03-Game-Installer.sh` fully automates the installation of PS1 and PS2 games. In the `games` folder on your computer, simply put your PS2 ISO or ZSO files in the `CD`/`DVD` folders, and your PS1 VCD files in the `POPS` folder.
The script will:
- Syncs all games in those folders with your PS2's drive
- Create all game assets
- Download artwork
- Install [OPL Launcher BDM](https://github.com/CosmicScale/OPL-Launcher-BDM) into every game partition, making games bootable from the Game Channel
To add or delete games, simply add or remove them from the `games` folder on your computer, then run the script again to sync. All games are kept in alphabetical order and grouped by series in the Game Channel on PSBBN.
By default the `games` folder is located in the same directory you installed the scrips to. If you need to change the location of the `games` folder, edit `03-Game-Installer.sh` and modify the `GAMES_PATH` variable.
### Notes:
- PSBBN requires a Fat PS2 console with expansion bay and an official Sony Network Adapter
- I would highly recommend using a **Kaico IDE to SATA Upgrade Kit** and a SATA SSD such as the **Kingston A400 960G**. The improved random access speed over a HDD really makes a big difference to the responsiveness of the PSBBN interface.
- Delete any existing OPL config files you may have from your memory cards
- Remove any attached BDM (USB, iLink, MX4ISO) devices from your PS2 console before launching games from the Game Channel
- Games in the Game Channel listed as "Coming soon..." will launch OPL if selected
- The `root` password for Linux is `password`. There is also a `ps2` user account with the password set as `password`
### Notes on APA-Jail:
APA-Jail, created and developed by [Berion](https://www.psx-place.com/resources/authors/berion.1431/), enables the PS2's APA partitions to coexist with an exFAT partition. This setup allows PSBBN to access the first 128 GB of the HDD/SSD directly. The remaining space on the drive is formatted as an exFAT partition, which can be accessed directly on a PC and by a custom version of [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader) on PS2. PS2 games in the ISO and ZSO format are stored on the exFAT partition.
![APA-Jail](https://user-images.githubusercontent.com/33134408/131233166-9527f892-40bb-4a83-a528-6b733087cf33.png)
An application called [OPL Launcher BDM](https://github.com/CosmicScale/OPL-Launcher-BDM) resides on the APA partitions, along with a custom build of [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader).
[OPL Launcher BDM](https://github.com/CosmicScale/OPL-Launcher-BDM) directs [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader) to launch specific PS2 games.
### Warning: Deleting or creating partitions on your PS2 drive will cause drive corruption.
---
## Credits
- PSBBN Definitive English Patch by [CosmicScale](https://github.com/CosmicScale)
- Online channels re-translated by [CosmicScale](https://github.com/CosmicScale)
- Online channels resurrected, maintained and hosted by vitas155 at [psbbn.ru](https://psbbn.ru/)
- `01-Setup.sh`, `02-PSBBN-Installer.sh`, `03-Game-Installer.sh`, `art_downloader.js` written by [CosmicScale](https://github.com/CosmicScale)
- Contains code from `list_builder.py` from [XEB+ neutrino Launcher Plugin](https://github.com/MegaBitmap/UDPBD-for-XEBP) by [MegaBitmap](https://github.com/MegaBitmap), modified by [CosmicScale](https://github.com/CosmicScale)
- Contains data from `TitlesDB_PS1_English.txt` and `TitlesDB_PS2_English.txt` from the [Title Database Scrapper](https://github.com/GDX-X/Title-Database-Scrapper), modified by [CosmicScale](https://github.com/CosmicScale)
- [OPL Launcher BDM](https://github.com/CosmicScale/OPL-Launcher-BDM) written by [CosmicScale](https://github.com/CosmicScale)
- Custom build of [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader) with BDM contributions from [KrahJohlito](https://github.com/KrahJohlito)
- Uses APA-Jail code from the [PS2 HDD Decryption Helper](https://www.psx-place.com/resources/ps2-hdd-decryption-helper.1507/) by [Berion](https://www.psx-place.com/resources/authors/berion.1431/)
- `APA Partition Header Checksumer` by Pinky from the [PS2 HDD Decryption Helper](https://www.psx-place.com/resources/ps2-hdd-decryption-helper.1507/) project. Linux port by Bucanero
- `ziso.py` from [Open PS2 Loader](https://github.com/ps2homebrew/Open-PS2-Loader) written by Virtuous Flame
- [PFS Shell](https://github.com/ps2homebrew/pfsshell)
- [HDL Dump](https://github.com/ps2homebrew/hdl-dump)
---
---
<details>
<summary><font size="4"><b>Legacy versions of the PSBBN Definitive English Patch</b></font></summary>
## Version History
### v1.2 - 4th September 2024
- Fixed a bug on the Photo Channel that could potentially prevented the Digital Camera feature from being launched.
- Fixed a bug on the Photo Channel that could potentially prevent the Digital Camera feature from being launched.
- Fixed formatting issues with a number of error messages where text was too long to fit on the screen.
- Various small adjustments and corrections to the translation throughout.
@ -106,7 +187,7 @@ Remove your Free McBoot Memory Card. Power the console on and enjoy PSBBN in ful
There are a number of ways this can be achieved. On a Japanese PlayStation 2 console with an **official PSBBN installation disc**, or with **Sony Utility Discs Compilation 3**.
To install via **Sony Utility Discs Compilation 3** you will need a way to boot backup discs on your console, be that a mod chip or a swap disc. If your are lucky enough to have a **SCPH-500xx** series console you can use the **MechaPwn** softmod.
To install via **Sony Utility Discs Compilation 3** you will need a way to boot backup discs on your console, be that a mod chip or a swap disc. If you are lucky enough to have a **SCPH-500xx** series console you can use the **MechaPwn** softmod.
### Installing with Sony Utility Discs Compilation 3
@ -158,5 +239,8 @@ Before installing the English patch, you **must** power off your console to stan
---
\* Instances in feega where some Japanese text could not be translated because it is hard coded, most likely in an encrypted file. Atok software has not been translated. You might have to manually change the title of your "Favorite" folders if they were created before running this patch.
\** **PS2 HDD RAW Image Install** - not compatible with early model Japanese PS2 consoles that have an external HDD due to space limitations. **Patch an existing PSBBN install** - Kloader might have compatibility issues with early model Japanese PS2 consoles.
</details>
\* Instances in feega where some Japanese text could not be translated because it is hard coded in an encrypted file. Atok software has not been translated. You might have to manually change the title of your "Favorite" folders if they were created before you **Patch an existing PSBBN install**.
\** PSBBN Definitive English Patch 2.0 and older versions of the **PS2 HDD RAW Image Install** - not compatible with early model Japanese PS2 consoles that have an external HDD due to space limitations. **Patch an existing PSBBN install** - Kloader might have compatibility issues with early model Japanese PS2 consoles.

BIN
assets/IGR_BG.TM2 Normal file

Binary file not shown.

BIN
assets/IGR_NO.TM2 Normal file

Binary file not shown.

BIN
assets/IGR_YES.TM2 Normal file

Binary file not shown.

BIN
assets/OPNPS2LD.ELF Normal file

Binary file not shown.

BIN
assets/POPSTARTER.ELF Normal file

Binary file not shown.

18
assets/icon.sys Normal file
View File

@ -0,0 +1,18 @@
PS2X
title0=OPL Launcher BDM
title1=
bgcola=0
bgcol0=0,0,0
bgcol1=0,0,0
bgcol2=0,0,0
bgcol3=0,0,0
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=

BIN
assets/list.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

4
assets/system.cnf Normal file
View File

@ -0,0 +1,4 @@
BOOT2 = pfs:/OPL-Launcher-BDM.KELF
VER = 1.01
VMODE = NTSC
HDDUNITPOWER = NICHDD

22584
helper/ArtDB.csv Normal file

File diff suppressed because it is too large Load Diff

BIN
helper/HDL Dump.elf Executable file

Binary file not shown.

BIN
helper/PFS Shell.elf Executable file

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

100
helper/art_downloader.js Normal file
View File

@ -0,0 +1,100 @@
const fs = require('fs');
const https = require('https');
const readline = require('readline');
const path = require('path');
const { execSync } = require('child_process');
// Check if Puppeteer is installed, and install it if not
try {
require.resolve('puppeteer');
} catch (e) {
console.log("Puppeteer not found. Installing...");
try {
execSync('npm install puppeteer', { stdio: 'inherit' });
console.log("Puppeteer installed successfully.");
} catch (installError) {
console.error("Failed to install Puppeteer. Ensure you have npm installed and try again.");
process.exit(1);
}
}
const puppeteer = require('puppeteer'); // Import Puppeteer after ensuring it's installed
(async () => {
// Get the game ID from the command-line arguments
const gameId = process.argv[2];
if (!gameId) {
console.error("Usage: node script.js <gameid>");
process.exit(1); // Exit if no game ID is provided
}
const csvFilePath = './helper/ArtDB.csv';
const outputDir = './icons/art/tmp';
// Ensure the output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Function to search the CSV file for the game ID
const findUrlForGameId = async (gameId) => {
const fileStream = fs.createReadStream(csvFilePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
for await (const line of rl) {
const [id, urlPart] = line.split('|');
if (id === gameId) {
return `https://www.ign.com/games/${urlPart}`; // Construct the full URL
}
}
return null; // Return null if no match is found
};
const url = await findUrlForGameId(gameId);
if (!url) {
console.error(`Game ID "${gameId}" not found in ArtDB.csv`);
process.exit(1);
}
const browser = await puppeteer.launch();
const page = await browser.newPage();
try {
console.log(`Navigating to: ${url}`);
await page.goto(url, { waitUntil: 'networkidle2' });
// Find the first image with src starting with 'https://assets-prd.ignimgs.com'
const imgUrl = await page.evaluate(() => {
const img = document.querySelector('img[src^="https://assets-prd.ignimgs.com"]'); // Look for images with this src prefix
if (img) {
return img.src.split('?')[0]; // Remove query parameters
}
return null;
});
if (imgUrl) {
// Get the file extension from the URL
const fileExtension = path.extname(imgUrl).split('?')[0]; // Ensures query strings don't interfere
// Save the image in the specified directory with the correct file extension
const fileName = path.join(outputDir, `${gameId}${fileExtension}`);
console.log(`Downloading image from: ${imgUrl}`);
console.log(`Saving as: ${fileName}`);
const file = fs.createWriteStream(fileName);
https.get(imgUrl, (response) => response.pipe(file));
} else {
console.log("No image found with the specified source.");
}
} catch (error) {
console.error(`Failed to fetch the page: ${error.message}`);
}
await browser.close();
})();

236
helper/list-builder-ps1.py Normal file
View File

@ -0,0 +1,236 @@
import os.path
import sys
import subprocess
import math
import unicodedata
from natsort import natsorted
done = "Error: No games found."
total = 0
count = 0
pattern_1 = [b'\x01', b'\x0D']
pattern_2 = [b'\x3B', b'\x31']
# Function to count VCD files in the POPS folder
def count_vcd(folder):
global total
for image in os.listdir(game_path + folder):
if image.lower().endswith(".vcd"):
total += 1
# Function to process VCD files in the POPS folder
def process_vcd(folder):
global total
global count
global done
gameid_file_path = "./helper/TitlesDB_PS1_English.csv"
# Read TitlesDB_PS1_English.csv and create a dictionary of title IDs to game names
game_names = {}
if os.path.isfile(gameid_file_path):
with open(gameid_file_path, 'r') as gameid_file:
for line in gameid_file:
parts = line.strip().split('|') # Split title ID and game name
if len(parts) >= 3:
game_names[parts[0]] = (parts[1], parts[2])
# Prepare a list to hold all game list entries
game_list_entries = []
for image in os.listdir(game_path + folder):
if image.lower().endswith(".vcd"):
print(math.floor((count * 100) / total), '% complete')
print('Processing', image)
index = 0
string = ""
with open(game_path + folder + "/" + image, "rb") as file:
while (byte := file.read(1)):
if len(string) < 4:
if index == 2:
string += byte.decode('utf-8', errors='ignore')
elif byte == pattern_1[index]:
index += 1
else:
string = ""
index = 0
elif len(string) == 4:
index = 0
if byte == b'\x5F':
string += byte.decode('utf-8', errors='ignore')
else:
string = ""
elif len(string) < 8:
string += byte.decode('utf-8', errors='ignore')
elif len(string) == 8:
if byte == b'\x2E':
string += byte.decode('utf-8', errors='ignore')
else:
string = ""
elif len(string) < 11:
string += byte.decode('utf-8', errors='ignore')
elif len(string) == 11:
if byte == pattern_2[index]:
index += 1
if index == 2:
break
else:
string = ""
index = 0
count += 1
# If no title ID is found, set it to the first 11 characters of the filename
if len(string) != 11:
string = os.path.splitext(image)[0][:11]
print(f'No title ID found. Defaulting to first 11 chars of filename: {string}')
# Determine game name and publisher
entry = game_names.get(string)
if entry:
# If we found a match in the CSV
game_name = entry[0] if entry[0] else None # If game name is empty, set to None
publisher = entry[1] if len(entry) > 1 and entry[1] else ""
if not game_name: # If game name is None (i.e., found in CSV but empty)
print(f"Game ID '{string}' found in CSV, but title is missing. Using filename logic.")
file_name_without_ext = os.path.splitext(image)[0]
if len(file_name_without_ext) >= 12 and file_name_without_ext[4] == '_' and file_name_without_ext[8] == '.' and file_name_without_ext[11] == '.':
game_name = file_name_without_ext[12:] # Fallback to part after the game ID
else:
game_name = file_name_without_ext # Use the filename as-is
publisher = "" # Publisher will remain empty in this case
print(f"Match found: ID='{string}' -> Game='{game_name}', Publisher='{publisher}'")
else:
# If no match found in CSV, use filename logic for game name
print(f"No match found for ID='{string}'")
file_name_without_ext = os.path.splitext(image)[0]
if len(file_name_without_ext) >= 12 and file_name_without_ext[4] == '_' and file_name_without_ext[8] == '.' and file_name_without_ext[11] == '.':
game_name = file_name_without_ext[12:]
else:
game_name = file_name_without_ext
publisher = ""
print(f"Default game name from filename: '{game_name}'")
# Format entry with game name, game ID, publisher, and image info
folder_image = f"{folder.replace('/', '', 1)}|{image}"
game_list_entry = f"{game_name}|{string}|{publisher}|{folder_image}"
game_list_entries.append(game_list_entry)
if game_list_entries:
with open(os.path.join(game_path, 'ps1.list'), "a") as output:
for entry in game_list_entries:
output.write(f"{entry}\n")
done = "Done!"
# Function to normalize text by removing diacritical marks and converting to ASCII
def normalize_text(text):
"""
Normalize text by removing diacritical marks and converting to ASCII.
"""
return ''.join(
c for c in unicodedata.normalize('NFD', text)
if unicodedata.category(c) != 'Mn'
)
# Main function to sort the games list
def sort_games_list(game_path):
games_list_path = os.path.join(game_path, 'ps1.list')
# Read the ps1.list into a list of lines
with open(games_list_path, 'r') as file:
lines = file.readlines()
# Sort the lines by the first field dynamically
def sort_key(line):
# Split the line into fields
fields = line.strip().split('|')
# Extract the game title (first field) and game_id (second field, if present)
first_field = fields[0].strip()
game_id = fields[1].strip() if len(fields) > 1 else ""
# Check for colon and truncate at the first colon, if exists
if ':' in first_field:
first_field = first_field.split(':')[0].strip()
# Remove leading "The" or "the" for sorting purposes
if first_field.lower().startswith('the '):
first_field = first_field[4:].strip()
# Normalize the title
normalized_title = normalize_text(first_field)
# Check for special cases like Roman numeral endings
replacements = {
' II': ' 2',
' III': ' 3',
' IV': ' 4',
' V': ' 5',
' VI': ' 6',
' VII': ' 7',
' VIII': ' 8',
' IX': ' 9',
' X': ' 10',
' XI': ' 11',
' XII': ' 12',
' XIII': ' 13',
' XIV': ' 14',
' XV': ' 15',
' XVI': ' 16',
' XVII': ' 17',
' XVIII': ' 18',
' XIX': ' 19',
' XX': ' 20'
}
for roman, digit in replacements.items():
if normalized_title.endswith(roman):
normalized_title = normalized_title.replace(roman, digit)
break
final_key = normalized_title.lower()
return (final_key, game_id)
# Sort the lines by the dynamic key using natsorted
sorted_lines = natsorted(lines, key=sort_key)
# Write the sorted lines back to ps1.list
with open(games_list_path, 'w') as file:
file.writelines(sorted_lines)
def main(arg1):
if arg1:
global game_path
global current_dir
game_path = arg1
current_dir = os.getcwd()
# Remove any existing game list file
ps1_list_path = os.path.join(game_path, 'ps1.list')
if os.path.isfile(ps1_list_path):
os.remove(ps1_list_path)
# Count and process files in the DVD and CD folders
if os.path.isdir(game_path + '/POPS'):
count_vcd('/POPS')
if total == 0: # No VCD files found
print("No PS1 games found in the POPS folder.")
sys.exit(1)
process_vcd('/POPS')
else:
print('POPS folder not found at ' + game_path)
sys.exit(1)
# Sort the games list after processing
sort_games_list(game_path)
print(done)
print('')
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: python3 list_builder-ps1.py <path/to/games>')
sys.exit(1)
main(sys.argv[1])

300
helper/list-builder-ps2.py Normal file
View File

@ -0,0 +1,300 @@
import sys
import math
import os.path
import subprocess
import unicodedata
from natsort import natsorted
done = "Error: No games found."
total = 0
count = 0
pattern_1 = [b'\x01', b'\x0D']
pattern_2 = [b'\x3B', b'\x31']
# Function to count game files in the given folder
def count_files(folder, extensions):
global total
for image in os.listdir(game_path + folder):
if any(image.lower().endswith(ext) for ext in extensions):
total += 1
# Function to process game files in the given folder
def process_files(folder, extensions):
global total
global count
global done
gameid_file_path = "./helper/TitlesDB_PS2_English.csv"
# Read TitlesDB_PS2_English.csv and create a dictionary of title IDs to game names
game_names = {}
if os.path.isfile(gameid_file_path):
with open(gameid_file_path, 'r') as gameid_file:
for line in gameid_file:
parts = line.strip().split('|') # Split title ID and game name
if len(parts) == 3:
game_names[parts[0]] = (parts[1], parts[2])
# Prepare a list to hold all game list entries
game_list_entries = []
for image in os.listdir(game_path + folder):
if any(image.lower().endswith(ext) for ext in extensions):
print(math.floor((count * 100) / total), '% complete')
print('Processing', image)
index = 0
string = ""
original_image = image # Store the original filename (e.g., `.zso` or `.iso`)
converted_iso = False
# Check the filename condition for all files
file_name_without_ext = os.path.splitext(image)[0]
if len(file_name_without_ext) >= 9 and file_name_without_ext[4] == '_' and file_name_without_ext[8] == '.':
# Filename meets the condition, directly set the game ID
string = file_name_without_ext[:11]
print(f"Filename meets condition. Game ID set directly from filename: {string}")
# If the file has a .zso extension and no ID was set, convert to .iso
if image.lower().endswith('.zso') and not string:
zso_path = os.path.join(game_path + folder, image)
iso_path = os.path.join(game_path + folder, os.path.splitext(image)[0] + '.iso')
print(f"Converting {image} from .zso to .iso...")
venv_activate = os.path.join('venv', 'bin', 'activate')
command = f"source {venv_activate} && python3 ./helper/ziso.py -c 0 '{zso_path}' '{iso_path}'"
subprocess.run(command, shell=True, check=True, executable='/bin/bash')
# Update image to the new .iso path for processing
image = os.path.basename(iso_path)
converted_iso = True # Mark the .iso file as being converted from .zso
# Extract the game ID from the file content if not set by the filename
if not string: # Only process if the game ID is not set from the filename
with open(game_path + folder + "/" + image, "rb") as file:
while (byte := file.read(1)):
if len(string) < 4:
if index == 2:
string += byte.decode('utf-8', errors='ignore')
elif byte == pattern_1[index]:
index += 1
else:
string = ""
index = 0
elif len(string) == 4:
index = 0
if byte == b'\x5F':
string += byte.decode('utf-8', errors='ignore')
else:
string = ""
elif len(string) < 8:
string += byte.decode('utf-8', errors='ignore')
elif len(string) == 8:
if byte == b'\x2E':
string += byte.decode('utf-8', errors='ignore')
else:
string = ""
elif len(string) < 11:
string += byte.decode('utf-8', errors='ignore')
elif len(string) == 11:
if byte == pattern_2[index]:
index += 1
if index == 2:
break
else:
string = ""
index = 0
count += 1
# Fallback if no title ID is found
if len(string) != 11:
string = os.path.splitext(original_image)[0][:11]
print(f'No title ID found. Defaulting to first 11 chars of filename: {string}')
# Rename the original `.zso` file to begin with the `gameid`
if converted_iso:
new_filename = f"{string}.{original_image}"
new_zso_path = os.path.join(game_path + folder, new_filename)
os.rename(zso_path, new_zso_path)
print(f"Renamed {original_image} to {new_filename}")
original_image = new_filename # Update the original image reference
# Determine game name and publisher
entry = game_names.get(string)
if entry:
game_name, publisher = entry
else:
game_name = os.path.splitext(original_image)[0]
publisher = ""
# Format entry with game name, game ID, publisher, and updated original image info
folder_image = f"{folder.replace('/', '', 1)}|{original_image}"
game_list_entry = f"{game_name}|{string}|{publisher}|{folder_image}"
game_list_entries.append(game_list_entry)
# If the file was converted from .zso to .iso, delete the .iso file
if converted_iso:
os.remove(game_path + folder + "/" + image)
print(f"Deleted the temporary ISO file: {image}")
# Write all entries to the ps2.list file
if game_list_entries:
with open(os.path.join(game_path, 'ps2.list'), "a") as output:
for entry in game_list_entries:
output.write(f"{entry}\n")
done = "Done!"
# Function to normalize text by removing diacritical marks and converting to ASCII
def normalize_text(text):
"""
Normalize text by removing diacritical marks and converting to ASCII.
"""
return ''.join(
c for c in unicodedata.normalize('NFD', text)
if unicodedata.category(c) != 'Mn'
)
# Main function to sort the games list
def sort_games_list(game_path):
games_list_path = os.path.join(game_path, 'ps2.list')
# Read the ps2.list into a list of lines
with open(games_list_path, 'r') as file:
lines = file.readlines()
# Sort the lines by the first field dynamically
def sort_key(line):
# Split the line into fields
fields = line.strip().split('|')
# Extract the game title (first field) and game_id (second field, if present)
first_field = fields[0].strip()
game_id = fields[1].strip() if len(fields) > 1 else ""
# Special condition for 'Jak and Daxter: The Precursor Legacy'
if first_field.lower() == "jak and daxter: the precursor legacy":
return ("jak", game_id)
# Special condition for 'Ratchet: Deadlocked'
if first_field.lower() == "ratchet: deadlocked":
return ("ratchet & clank", game_id)
# Special condition for 'Ratchet: Deadlocked'
if first_field.lower() == "secret agent clank":
return ("ratchet & clank", game_id)
# Special condition for 'Sly Cooper and the Thievius Raccoonus'
if first_field.lower().startswith("sly cooper and the thievius raccoonus"):
return ("sly", game_id)
# Special condition for 'Zone of the Enders: The 2nd Runner'
if first_field.lower().startswith("zone of the enders: the 2nd runner"):
return ("zone of the enders 2", game_id)
# Special condition for 'Grand Theft Auto III'
if first_field.lower().startswith("grand theft auto iii"):
return ("grand theft auto", game_id)
# Special condition for 'The Document of Metal Gear Solid 2'
if first_field.lower().startswith("the document of metal gear solid 2"):
return ("metal gear solid 2", game_id)
# Special condition for 'Forbidden Siren'
if first_field.lower().startswith("forbidden siren 2"):
return ("siren 2", game_id)
# Special condition for 'We Love Katamari'
if first_field.lower().startswith("we love katamari"):
return ("katamari damacy 2", game_id)
# Check for colon and truncate at the first colon, if exists
if ':' in first_field:
first_field = first_field.split(':')[0].strip()
# Remove leading "The" or "the" for sorting purposes
if first_field.lower().startswith('the '):
first_field = first_field[4:].strip()
# Normalize the title
normalized_title = normalize_text(first_field)
# Check for special cases like Roman numeral endings
replacements = {
' II': ' 2',
' III': ' 3',
' IV': ' 4',
' V': ' 5',
' VI': ' 6',
' VII': ' 7',
' VIII': ' 8',
' IX': ' 9',
' X': ' 10',
' XI': ' 11',
' XII': ' 12',
' XIII': ' 13',
' XIV': ' 14',
' XV': ' 15',
' XVI': ' 16',
' XVII': ' 17',
' XVIII': ' 18',
' XIX': ' 19',
' XX': ' 20'
}
for roman, digit in replacements.items():
if normalized_title.endswith(roman):
normalized_title = normalized_title.replace(roman, digit)
break
final_key = normalized_title.lower()
return (final_key, game_id)
# Sort the lines by the dynamic key using natsorted
sorted_lines = natsorted(lines, key=sort_key)
# Write the sorted lines back to ps2.list
with open(games_list_path, 'w') as file:
file.writelines(sorted_lines)
def main(arg1):
global game_path
global current_dir
game_path = arg1
current_dir = os.getcwd()
# Remove any existing game list file
ps2_list_path = os.path.join(game_path, 'ps2.list')
if os.path.isfile(ps2_list_path):
os.remove(ps2_list_path)
# Count and process files in the DVD and CD folders
for folder, extensions in [('/DVD', ['.iso', '.zso']), ('/CD', ['.iso', '.zso'])]:
if os.path.isdir(game_path + folder):
count_files(folder, extensions)
else:
print(f'{folder} not found at ' + game_path)
sys.exit(1)
# Check if no games were found
if total == 0:
print("No PS2 games found in the CD or DVD folder.")
sys.exit(1)
# Process the files now that we know there are games
for folder, extensions in [('/DVD', ['.iso', '.zso']), ('/CD', ['.iso', '.zso'])]:
if os.path.isdir(game_path + folder):
process_files(folder, extensions)
# Sort the games list after processing
sort_games_list(game_path)
print(done)
print('')
if __name__ == '__main__':
if len(sys.argv) != 2:
print('Usage: list_builder-ps2.py <path/to/games>')
sys.exit(1)
main(sys.argv[1])

424
helper/ziso.py Normal file
View File

@ -0,0 +1,424 @@
#!/usr/bin/env python3
# Copyright (c) 2011 by Virtuous Flame
# Based BOOSTER 1.01 CSO Compressor
# Adapted for codestation's ZSO format
#
# GNU General Public Licence (GPL)
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 59 Temple
# Place, Suite 330, Boston, MA 02111-1307 USA
#
__author__ = "Virtuous Flame"
__license__ = "GPL"
__version__ = "2.0"
import sys
import os
import lz4.block
from struct import pack, unpack
from multiprocessing import Pool
from getopt import gnu_getopt, GetoptError
ZISO_MAGIC = 0x4F53495A
DEFAULT_ALIGN = 0
DEFAULT_BLOCK_SIZE = 0x800
COMPRESS_THREHOLD = 95
DEFAULT_PADDING = br'X'
MP = False
MP_NR = 1024 * 16
def hexdump(data):
for i in data:
print("0x%02X" % ((ord(i))))
print("")
def lz4_compress(plain, level=9):
mode = "high_compression" if level > 1 else "default"
return lz4.block.compress(plain, mode=mode, compression=level, store_size=False)
def lz4_compress_mp(i):
plain = i[0]
level = i[1]
mode = "high_compression" if level > 1 else "default"
return lz4.block.compress(plain, mode=mode, compression=level, store_size=False)
def lz4_decompress(compressed, block_size):
decompressed = None
while True:
try:
decompressed = lz4.block.decompress(
compressed, uncompressed_size=block_size)
break
except lz4.block.LZ4BlockError:
compressed = compressed[:-1]
return decompressed
def usage():
print("Usage: ziso [-c level] [-m] [-t percent] [-h] infile outfile")
print(" -c level: 1-12 compress ISO to ZSO, 1 for standard compression, >1 for high compression")
print(" 0 decompress ZSO to ISO")
print(" -b size: 2048-8192, specify block size (2048 by default)")
print(" -m Use multiprocessing acceleration for compressing")
print(" -t percent Compression Threshold (1-100)")
print(" -a align Padding alignment 0=small/slow 6=fast/large")
print(" -p pad Padding byte")
print(" -h this help")
def open_input_output(fname_in, fname_out):
try:
fin = open(fname_in, "rb")
except IOError:
print("Can't open %s" % (fname_in))
sys.exit(-1)
try:
fout = open(fname_out, "wb")
except IOError:
print("Can't create %s" % (fname_out))
sys.exit(-1)
return fin, fout
def seek_and_read(fin, offset, size):
fin.seek(offset)
return fin.read(size)
def read_zso_header(fin):
# ZSO header has 0x18 bytes
data = seek_and_read(fin, 0, 0x18)
magic, header_size, total_bytes, block_size, ver, align = unpack(
'IIQIbbxx', data)
return magic, header_size, total_bytes, block_size, ver, align
def generate_zso_header(magic, header_size, total_bytes, block_size, ver, align):
data = pack('IIQIbbxx', magic, header_size,
total_bytes, block_size, ver, align)
return data
def show_zso_info(fname_in, fname_out, total_bytes, block_size, total_block, ver, align):
print("Decompress '%s' to '%s'" % (fname_in, fname_out))
print("Total File Size %ld bytes" % (total_bytes))
print("block size %d bytes" % (block_size))
print("total blocks %d blocks" % (total_block))
print("index align %d" % (align))
print("version %d" % (ver))
def decompress_zso(fname_in, fname_out):
fin, fout = open_input_output(fname_in, fname_out)
magic, header_size, total_bytes, block_size, ver, align = read_zso_header(
fin)
if magic != ZISO_MAGIC or block_size == 0 or total_bytes == 0 or header_size != 24 or ver > 1:
print("ziso file format error")
return -1
total_block = total_bytes // block_size
index_buf = []
for _ in range(total_block + 1):
index_buf.append(unpack('I', fin.read(4))[0])
show_zso_info(fname_in, fname_out, total_bytes,
block_size, total_block, ver, align)
block = 0
percent_period = total_block/100
percent_cnt = 0
while block < total_block:
percent_cnt += 1
if percent_cnt >= percent_period and percent_period != 0:
percent_cnt = 0
print("decompress %d%%\r" %
(block / percent_period), file=sys.stderr, end='\r')
index = index_buf[block]
plain = index & 0x80000000
index &= 0x7fffffff
read_pos = index << (align)
if plain:
read_size = block_size
else:
index2 = index_buf[block+1] & 0x7fffffff
# Have to read more bytes if align was set
read_size = (index2-index) << (align)
if block == total_block - 1:
read_size = total_bytes - read_pos
zso_data = seek_and_read(fin, read_pos, read_size)
if plain:
dec_data = zso_data
else:
try:
dec_data = lz4_decompress(zso_data, block_size)
except Exception as e:
print("%d block: 0x%08X %d %s" %
(block, read_pos, read_size, e))
sys.exit(-1)
if (len(dec_data) != block_size):
print("%d block: 0x%08X %d" %
(block, read_pos, read_size))
sys.exit(-1)
fout.write(dec_data)
block += 1
fin.close()
fout.close()
print("ziso decompress completed")
def show_comp_info(fname_in, fname_out, total_bytes, block_size, ver, align, level):
print("Compress '%s' to '%s'" % (fname_in, fname_out))
print("Total File Size %ld bytes" % (total_bytes))
print("block size %d bytes" % (block_size))
print("index align %d" % (1 << align))
print("compress level %d" % (level))
print("version %d" % (ver))
if MP:
print("multiprocessing %s" % (MP))
def set_align(fout, write_pos, align):
if write_pos % (1 << align):
align_len = (1 << align) - write_pos % (1 << align)
fout.write(DEFAULT_PADDING * align_len)
write_pos += align_len
return write_pos
def compress_zso(fname_in, fname_out, level, bsize):
fin, fout = open_input_output(fname_in, fname_out)
fin.seek(0, os.SEEK_END)
total_bytes = fin.tell()
fin.seek(0)
magic, header_size, block_size, ver, align = ZISO_MAGIC, 0x18, bsize, 1, DEFAULT_ALIGN
# We have to use alignment on any ZSO files which > 2GB, for MSB bit of index as the plain indicator
# If we don't then the index can be larger than 2GB, which its plain indicator was improperly set
align = total_bytes // 2 ** 31
header = generate_zso_header(
magic, header_size, total_bytes, block_size, ver, align)
fout.write(header)
total_block = total_bytes // block_size
index_buf = [0 for i in range(total_block + 1)]
fout.write(b"\x00\x00\x00\x00" * len(index_buf))
show_comp_info(fname_in, fname_out, total_bytes, block_size, ver, align, level)
write_pos = fout.tell()
percent_period = total_block/100
percent_cnt = 0
if MP:
pool = Pool()
block = 0
while block < total_block:
if MP:
percent_cnt += min(total_block - block, MP_NR)
else:
percent_cnt += 1
if percent_cnt >= percent_period and percent_period != 0:
percent_cnt = 0
if block == 0:
print("compress %3d%% avarage rate %3d%%\r" % (
block / percent_period, 0), file=sys.stderr, end='\r')
else:
print("compress %3d%% avarage rate %3d%%\r" % (
block / percent_period, 100*write_pos/(block*block_size)), file=sys.stderr, end='\r')
if MP:
iso_data = [(fin.read(block_size), level)
for i in range(min(total_block - block, MP_NR))]
zso_data_all = pool.map_async(
lz4_compress_mp, iso_data).get(9999999)
for i, zso_data in enumerate(zso_data_all):
write_pos = set_align(fout, write_pos, align)
index_buf[block] = write_pos >> align
if 100 * len(zso_data) / len(iso_data[i][0]) >= min(COMPRESS_THREHOLD, 100):
zso_data = iso_data[i][0]
index_buf[block] |= 0x80000000 # Mark as plain
elif index_buf[block] & 0x80000000:
print(
"Align error, you have to increase align by 1 or OPL won't be able to read offset above 2 ** 31 bytes")
sys.exit(1)
fout.write(zso_data)
write_pos += len(zso_data)
block += 1
else:
iso_data = fin.read(block_size)
try:
zso_data = lz4_compress(iso_data, level)
except Exception as e:
print("%d block: %s" % (block, e))
sys.exit(-1)
write_pos = set_align(fout, write_pos, align)
index_buf[block] = write_pos >> align
if 100 * len(zso_data) / len(iso_data) >= COMPRESS_THREHOLD:
zso_data = iso_data
index_buf[block] |= 0x80000000 # Mark as plain
elif index_buf[block] & 0x80000000:
print(
"Align error, you have to increase align by 1 or CFW won't be able to read offset above 2 ** 31 bytes")
sys.exit(1)
fout.write(zso_data)
write_pos += len(zso_data)
block += 1
# Last position (total size)
index_buf[block] = write_pos >> align
# Update index block
fout.seek(len(header))
for i in index_buf:
idx = pack('I', i)
fout.write(idx)
print("ziso compress completed , total size = %8d bytes , rate %d%%" %
(write_pos, (write_pos*100/total_bytes)))
fin.close()
fout.close()
def parse_args():
global MP, COMPRESS_THREHOLD, DEFAULT_PADDING, DEFAULT_ALIGN
if len(sys.argv) < 2:
usage()
sys.exit(-1)
try:
optlist, args = gnu_getopt(sys.argv, "c:b:mt:a:p:h")
except GetoptError as err:
print(str(err))
usage()
sys.exit(-1)
level = None
bsize = DEFAULT_BLOCK_SIZE
for o, a in optlist:
if o == '-c':
level = int(a)
elif o == '-b':
bsize = int(a)
elif o == '-m':
MP = True
elif o == '-t':
COMPRESS_THREHOLD = min(int(a), 100)
elif o == '-a':
DEFAULT_ALIGN = int(a)
elif o == '-p':
DEFAULT_PADDING = bytes(a[0], encoding='utf8')
elif o == '-h':
usage()
sys.exit(0)
try:
fname_in, fname_out = args[1:3]
except ValueError as err:
print("You have to specify input/output filename: %s", err)
sys.exit(-1)
if bsize%2048 != 0:
print("Error, invalid block size. Must be multiple of 2048.")
sys.exit(-1)
return level, bsize, fname_in, fname_out
def load_sector_table(sector_table_fn, total_block, default_level=9):
# In future we will support NC
sectors = [default_level for i in range(total_block)]
with open(sector_table_fn) as f:
for line in f:
line = line.strip()
a = line.split(":")
if len(a) < 2:
raise ValueError("Invalid line founded: %s" % (line))
if -1 == a[0].find("-"):
try:
sector, level = int(a[0]), int(a[1])
except ValueError:
raise ValueError("Invalid line founded: %s" % (line))
if level < 1 or level > 9:
raise ValueError("Invalid line founded: %s" % (line))
sectors[sector] = level
else:
b = a[0].split("-")
try:
start, end, level = int(b[0]), int(b[1]), int(a[1])
except ValueError:
raise ValueError("Invalid line founded: %s" % (line))
i = start
while i < end:
sectors[i] = level
i += 1
return sectors
def main():
print("ziso-python %s by %s" % (__version__, __author__))
level, bsize, fname_in, fname_out = parse_args()
if level == 0:
decompress_zso(fname_in, fname_out)
else:
compress_zso(fname_in, fname_out, level, bsize)
PROFILE = False
if __name__ == "__main__":
if PROFILE:
import cProfile
cProfile.run("main()")
else:
main()

BIN
icons/OPL-Launcher-BDM.KELF Normal file

Binary file not shown.

BIN
icons/art/PBPX_955.02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/PBPX_955.03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/PBPX_955.16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
icons/art/PCPX_961.64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
icons/art/SCES_500.34.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
icons/art/SCES_502.94.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/SCES_503.00.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
icons/art/SCES_507.60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
icons/art/SCES_511.59.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
icons/art/SCES_538.51.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

BIN
icons/art/SCKA_200.95.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
icons/art/SCPS_100.87.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
icons/art/SCPS_150.09.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/SCPS_550.07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/SCPS_720.01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

BIN
icons/art/SCUS_941.03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
icons/art/SCUS_941.83.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
icons/art/SCUS_941.94.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
icons/art/SCUS_944.23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
icons/art/SCUS_944.48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
icons/art/SCUS_944.49.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
icons/art/SCUS_971.02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
icons/art/SCUS_971.05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
icons/art/SCUS_971.11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
icons/art/SCUS_971.15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
icons/art/SCUS_971.24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
icons/art/SCUS_971.25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SCUS_971.40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
icons/art/SCUS_971.42.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SCUS_971.67.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
icons/art/SCUS_971.98.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
icons/art/SCUS_971.99.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
icons/art/SCUS_972.10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
icons/art/SCUS_972.65.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

BIN
icons/art/SCUS_972.68.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
icons/art/SCUS_973.16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
icons/art/SCUS_973.28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
icons/art/SCUS_973.30.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
icons/art/SCUS_973.53.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SCUS_973.55.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
icons/art/SCUS_973.99.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
icons/art/SCUS_974.02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
icons/art/SCUS_974.08.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
icons/art/SCUS_974.64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
icons/art/SCUS_974.65.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
icons/art/SCUS_974.72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

BIN
icons/art/SCUS_974.81.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
icons/art/SCUS_975.01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
icons/art/SCUS_975.02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
icons/art/SCUS_975.12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
icons/art/SCUS_976.15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
icons/art/SCUS_976.23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
icons/art/SLES_502.47.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
icons/art/SLES_507.69.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
icons/art/SLES_511.13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
icons/art/SLES_517.05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
icons/art/SLES_520.96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
icons/art/SLES_525.05.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
icons/art/SLES_527.19.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
icons/art/SLES_530.38.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SLES_530.73.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
icons/art/SLES_535.40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
icons/art/SLES_541.86.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
icons/art/SLKA_250.07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

BIN
icons/art/SLKA_252.65.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SLPM_610.30.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
icons/art/SLPM_651.24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
icons/art/SLPM_651.40.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
icons/art/SLPM_654.70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
icons/art/SLPM_657.44.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
icons/art/SLPM_658.80.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
icons/art/SLPM_660.17.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

BIN
icons/art/SLPM_661.60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
icons/art/SLPM_668.53.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
icons/art/SLPM_669.26.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
icons/art/SLPM_675.07.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
icons/art/SLPM_742.42.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Some files were not shown because too many files have changed in this diff Show More