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) # Remove special characters normalized_title = ''.join(c for c in normalized_title if c.isalnum() or c.isspace()) # Check for special cases like Roman numeral endings replacements = { ' I': ' 1', ' 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 ') sys.exit(1) main(sys.argv[1])