Fans de Launchbox os invoco!

17, 8, 9, 10, 11
manumedia escribió:
SirAzraelGrotesque escribió:Muchas gracias. Sí, eso casi me sirve. ¡Por poco! Lástima que parece que no se puede seleccionar otro núcleo de Retroarch, que es justo lo que quería hacer. O no he sabido cómo hacerlo. Me deja cambiar por ejemplo a MAME externo, pero yo quiero cambiar del núcleo de FinalBurn Neo al de MAME.

Ah vale, leí emuladores y no pensé en núcleos de RetroArch, yo en RetroArch tendría que investigar ya que sólo lo he usado en la Xbox, también puede que haya algún parámetro en Launchbox que permita cargar la rom en RetroArch con el Core indicado. Si veo algo lo comento aquí.


Perfecto. A ver si averiguamos cómo se puede hacer, en caso de que se pueda. Muchas gracias.
Hace años que tengo comprada la licencia de por vida y nunca supe configurarlo correctamente.

No me interesan demasiadas plataformas, solo arcade, y 8 - 16 bits ya que queria meterlo en una recreativa.

¿alguien podria compartir su recopilacion?

Tengo una cuenta de one drive donde se podrian subir 4 tb, si alguien me lo puede compartir.

Es que soy malisimo para las configuraciones y no soy capaz de hacer nada :(
Ya he averiguado cómo hacer lo de lanzar juegos específicos con un núcleo distinto de Retroarch. Es prácticamente como tú me indicaste, @manumedia sólo que antes de seleccionar el emu, habría que crear una segunda instancia de Retroarch, donde podremos asignar un núcleo diferente para el sistema. En mi caso, el MAME. De esta manera, lanzo todos por defecto mediante FinalBurn y los que no funcionan en este emulador, pero sí en MAME, los voy cambiando manualmente a esa segunda instancia de Retroarch con el núcleo de MAME.
SirAzraelGrotesque escribió:Ya he averiguado cómo hacer lo de lanzar juegos específicos con un núcleo distinto de Retroarch. Es prácticamente como tú me indicaste, @manumedia sólo que antes de seleccionar el emu, habría que crear una segunda instancia de Retroarch, donde podremos asignar un núcleo diferente para el sistema. En mi caso, el MAME. De esta manera, lanzo todos por defecto mediante FinalBurn y los que no funcionan en este emulador, pero sí en MAME, los voy cambiando manualmente a esa segunda instancia de Retroarch con el núcleo de MAME.

Me alegro compañero, pues ya tenemos un dato más del uso de RetroArch en Launchbox. A disfrutar de esos arcades [beer]
Hola, para lo que tu dices de poner x core en un juego expecifico hay una forma mas facil y sin crear ninguna nueva instancia de retroarch, usas el siguiente commando en la caseta de lineas de comando extra o algo asi en el juego que quieras:
-L "cores\("Nombre del Core a usar").dll"

Ejemplo:
-L "cores\parallel_n64_libretro.dll"

y ya puedes usar ese core specifico para el juego que vas a ejecutar desde launchbox.
Espero que te halla servido.

Ya que estoy voy a hacer una pregunta. no se que ha pasado pero antes me hice un bat para apagar el equipo en launchbox, pero no se porque aunque le ponga el comando de shutdown /s o incluso de los que cierran todo lo unico que hace el pc es reiniciarse. tengo windows 10, y me pasa esto desde hace unas semanas o as, no se si es la actualizacion de windows o si es del launchbox.

Un saludo.
@Pidgey Muchas gracias. Lo probaré.
Sin Problema @SirAzraelGrotesque , dejo aqui una imagen de donde se debe añadir el comando para mas facilidad, ya que no pude ponerla en el comentario anterior.

Adjuntos

Pidgey escribió:Sin Problema @SirAzraelGrotesque , dejo aqui una imagen de donde se debe añadir el comando para mas facilidad, ya que no pude ponerla en el comentario anterior.


Fantástico. Gracias de nuevo. Es tedioso ir haciéndolo uno a uno, pero como tampoco tengo prisa... Según vaya queriendo jugar a juegos que no vayan con Final Burn Neo y sí con Mame, los iré cambiando y listo. :)

Antes era muy de querer colecciones completas listas antes de ponerme a jugar a nada. Ahora sin embargo prefiero ir jugando y configurando/preparando las cosas sobre la marcha.
El programa parece que ha mejorado considerablemente en cuanto a agilidad de uso con la última actualización beta, ¿no os parece? Todo se nota como más liviano. Y le hacía buena falta, porque antes era pesadillo.
Ha mejorado bastante en terminos generales,y tiene sus cositas como integracion de logros,Hi Scores,lobby para netplay e.t.c. aunque la verdad,me mata que este frontend a pesar de requerir una licencia de pago, no tenga algunas cosas simples a estas alturas,como por ejemplo la posibilidad de sobreexponer en los temas de BigBox las tipicas animaciones .flv de control de siguiente sistema,o siguiente juego,que encuentras por ejemplo en hyperspin en el directorio special.
Bueno,es que Ni siquiera en el port del tema Unified esta disponible.

Aunque pueda ser algo meramente estético,y existan formas de conseguirlo como introducir videothemes grabados a partir de este u otro frontend y usarlos en launchbox,da corage tener que recurrir a cosas asi,después de pedir un pago sea anual o vitalicia y ver frontend como attract mode gratuitos,capaces de hacer cosas así sin tanto mareo.
Saludos!
He añadido el emulador Xenia (en su versión Canary) a mi Launchbox para emular juegos de Xbox 360. Este emulador ha dado un buen salto de calidad desde la actualización de Diciembre pero sigue teniendo una interfaz muy parca y la configuración sigue brillando por su ausencia en el menú. Para configurar este emulador he seguido un tutorial donde te explica que parámetros cambiar en el archivo "xenia-canary.config.toml".
Me he creado una carpeta con todas las configuraciones que me interesan a diferentes resoluciones y cada una con su versión a 30fps o 60fps y alguna función muy específica para que se vean bien determinados juegos.
El problema es que Xenia no te deja guardar configuración por juego, hay que sustituir el archivo manualmente cada vez que cambies de un juego a otro y recordar que configuración iba bien con cada juego.
Tengo entendido que hay una manera para indicar a Launchbox la ruta hacia estas configuraciones y así poder cargar cada juego ya de manera óptima sin tener que estar copiando y pegando la configuración cada vez.
¿Alguien me puede ayudar con esto?
Desarchivando para nuevas consultas.

PD- y disculpa la tardanza [angelito]
@salvor70 Gracias.

Buenas. A ver si alguien me puede echar una mano con Launchbox y con el addon BezelLauncher. Lo intenté configurar hace unos meses y con el emulador BigPEmu por ejemplo me funciona bien. El problema es con Duckstation, que no consigo hacerlo funcionar. La imagen del emu se me queda por debajo, se oyen los juegos, pero se ven en negro. ¿alguien que lo tenga funcionando me puede decir cómo tiene las opciones en BezelLauncher y también cómo tiene configurado el vídeo en Duckstation, porfa?

Muchas gracias.

P.D.: Traigo yo mismo la solución, por si a alguien más le pasa o le llega a ocurrir en el futuro.

1º Es primordial desactivar la "Pantalla completa exclusiva" en las opciones gráficas de Duckstation.
2º En las opciones de BezelLauncher para PS1, dejad únicamente activadas las que recomienda el propio programa para el emulador. Esto es: g
3º En las opciones de Launchbox referentes al emulador, en el apartado de "Pantalla de inicio", no activar bajo ningún la opción de "Ocultación agresiva de la ventana de inicio".

Con estos tres pasos debería funcionar y cargar los bezels cuando ejecutéis un juego a través de Duckstation, que en mi caso ha sido el sistema más conflictivo a la hora de poner en marcha este plugin de Launchbox.
He creado un script para ver las imagenes, videos y manuales que tengo en launchbox de forma facil, para ver que falta y que tengo que buscar, por que se me hacia algo pesado en el propio launchbox

Lo primero que tengo que decir es que no tengo NI IDEA de programar en Phyton, todo esta hecho pidiendole a la IA Deepseek, lo que iba queriendo y ella lo generaba

La ventana principal del programa es esta:
Imagen


Ahi al darle a Configuracion se abre esta ventana
Imagen


En la que podemos configurar donde esta Launchbox, los filtros que queremos ver, los juegos por pagina mostrados, asi como el tamaño de las celdas en modo texto "Solo SI/NO" o con Imagenes.
Tambien esta lo de "Ruta Alternativa, que es lo que uso yo, por que tengo los videos y manuales en el mismo HDD que las roms, por que es lo que normalmente mas ocupa y liberar un poco el SSD.
Ademas de eso hay para ponerle colores a las celdas
"Color Media encontrada con Title" Se refiere al nombre que pone por defecto LaunchBox (Ejem. "Super Mario Bros. 3-01.png") (Solo busca 1 imagen no si hay mas de 1, por eso lo de buscar con -01)
"Color Media encontrada con RomName" es con el nombre del rom a ejecutar ("Super Mario Bros 3 (USA).zip")
"Color Media encontrada con Ambos" es como su nombre indica si encuetra ambos. Este es mas que nada por si tengo archivos repetidos
"Color Media NO encontrda" este sigue la misma regla y solo hay 1 variante.
No solo se ve con color si no que en el primer caso se ve con el texto "(Si Title)", en el segundo "(Si Rom)" y en el tercero "(Si TItle/Rom)". O directamente NO, si no encuentra nada

Por ultimo, esta lo de cache local que creara copias en jpg sin transparencias que se sustituyen por el "Color fondo imagenes" que pogamos asi como la resolucion para que carge mas rapido, sin marcarlo tambien creara imagenes pero seran temporales y es mas lento.

Una vez configurado y darle a cargar plataformas saldran en el desplegable todas las que tenemos, y se mostrara por defecto la primera.
Imagen


Una vez aqui si pulsamos en "Ocultar todo SI" escondera de los filtros actuales todos los que tengan en todas las categorias SI, dejando solo en los que nos falte algun media
Imagen


Al darle a "Modo Imagenes" ya no saldra texto y se veran las imagenes a la resolucion que pusimos en Configuracion, la primera vez puede tardar, pero una vez creadas deberia de ser mas rapido, de todos modos abajo del todo donde estan las paginas hay dos botones uno para cargar todas las paginas a la vez, aunque mas lento puede ser util y otro para regenerarlas si cambiamos la resolucion de las mismas. (Videos y Manuales, no tienen previsualizacion)
Imagen


Y eso es todo, tambien tiene para ordenar las columnas para poner los SI o los No arriba, o ordenar por nombre del juego alfabeticamente, lo suyo seria algo similar con un pluguin en el propio LaunchBox, pero como digo, no tengo NI IDEA de programar y DeepSeek, me recomendo hacerlo en Python y contento con el resultado, lo pongo por si a alguien mas le vale.

Si alguien entiende Python y lee el codigo que no se asuste, yo iba pegando cosas que me tiraba la IA donde pillaba si funcionaba me valia ^ ^. Si alguien tiene alguna mejora y la comparte genial y si a alguien le falta algo imprescindible tan facil como pegar todo el codigo en DeepSeek y decirlo "Quiero añadir un boton para exportar en TXT" y facil te ayuda a ponerlo por ejemplo

Saludos :P

Y aqui estan los dos PY
LaunchBox - Media Manager.py
import os
import shutil
import xml.etree.ElementTree as ET
import tkinter as tk
from tkinter import messagebox, filedialog
from PIL import Image, ImageTk
import subprocess
from tkinter import ttk


class LaunchBoxManager:
    def __init__(self, root):
        self.root = root
        self.root.title("LaunchBox Media Manager")
        self.root.geometry("800x600")
        self.platforms = {}
        self.launchbox_path = None
        self.filters = ["Box - Front", "Clear Logo"]
        self.show_images = False
        self.image_references = {}
        self.last_sorted_column = None
        self.videos_enabled = False
        self.manual_enabled = False
        self.sort_ascending = True
        self.cell_size_text = (100, 50)
        self.cell_size_image = (150, 150)
        self.columns = 0
        self.image_cache_ratio = "150x150"
        self.alternative_path = None
        self.max_games_per_page_imagemode = 100
        self.make_cache = True
        self.current_page = 0
        self.total_pages = 0

        max_retries = 2
        retries = 0

        while retries < max_retries:
            if self.load_launchbox_path():
                break
            else:
                print("Error: No se pudo cargar la configuración de LaunchBox. Reintentando...")
                retries += 1

        if retries == max_retries:
            print("Error: No se pudo cargar la configuración de LaunchBox después de varios intentos.")
            self.root.destroy()

        self.setup_ui()

    def setup_ui(self):
        # Frame superior (para la selección de plataformas)
        self.top_frame = tk.Frame(self.root)
        self.top_frame.pack(fill="x", padx=10, pady=10)

        # Etiqueta y Combobox para seleccionar la plataforma
        self.platform_label = tk.Label(self.top_frame, text="Plataforma:")
        self.platform_label.pack(side="left", padx=(0, 5))

        self.platform_combobox = tk.StringVar()
        self.platform_menu = ttk.Combobox(self.top_frame, textvariable=self.platform_combobox, state="readonly")
        self.platform_menu.pack(side="left", fill="x", expand=True, padx=(0, 10))
        self.platform_combobox.trace("w", self.on_platform_select)

        # Botón para cargar plataformas
        self.load_button = tk.Button(self.top_frame, text="Cargar Plataformas", command=self.load_platforms)
        self.load_button.pack(side="left")

        # Frame para los Checkbuttons (Modo Imágenes y Ocultar todo SI)
        self.checkbutton_frame = tk.Frame(self.top_frame)
        self.checkbutton_frame.pack(side="left", padx=(10, 0))

        # Checkbutton para el modo imágenes
        self.switch_var = tk.BooleanVar()
        self.switch = tk.Checkbutton(self.checkbutton_frame, text="Modo Imágenes", variable=self.switch_var,
                                     command=self.toggle_view_mode)
        self.switch.pack(anchor="w")  # Alineado a la izquierda dentro del Frame

        # Checkbutton para ocultar juegos con "SI" en todas las celdas
        self.hide_all_si_var = tk.BooleanVar(value=False)  # Desactivado por defecto
        self.hide_all_si_check = tk.Checkbutton(self.checkbutton_frame, text="Ocultar todo SI",
                                                variable=self.hide_all_si_var,
                                                command=self.toggle_hide_all_si)
        self.hide_all_si_check.pack(anchor="w")  # Alineado a la izquierda dentro del Frame

        # Botón para abrir la configuración (a la derecha)
        self.settings_button = tk.Button(self.top_frame, text="Configuración", command=self.open_settings)
        self.settings_button.pack(side="right", padx=(10, 0))

        # Frame para los filtros y la tabla
        self.filter_frame = tk.LabelFrame(self.root, text="Estado de imágenes")
        self.filter_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # Canvas y scrollbars para la tabla
        self.header_canvas = tk.Canvas(self.filter_frame, borderwidth=1, relief="solid", height=self.cell_size_text[1])
        self.header_canvas.pack(fill="x", side="top")

        self.header_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
                                               command=lambda *args: self.sync_scroll(*args))
        self.header_scrollbar_x.pack(side="top", fill="x")
        self.header_canvas.config(xscrollcommand=self.header_scrollbar_x.set)

        self.canvas_frame = tk.Frame(self.filter_frame)
        self.canvas_frame.pack(fill="both", expand=True)

        self.canvas = tk.Canvas(self.canvas_frame, borderwidth=1, relief="solid")
        self.canvas.pack(side="left", fill="both", expand=True)

        self.canvas_scrollbar_y = tk.Scrollbar(self.canvas_frame, orient="vertical", command=self.canvas.yview)
        self.canvas_scrollbar_y.pack(side="right", fill="y")
        self.canvas.config(yscrollcommand=self.canvas_scrollbar_y.set)

        self.canvas_scrollbar_x = tk.Scrollbar(self.filter_frame, orient="horizontal",
                                               command=lambda *args: self.sync_scroll(*args))
        self.canvas_scrollbar_x.pack(side="bottom", fill="x")
        self.canvas.config(xscrollcommand=self.canvas_scrollbar_x.set)

        self.canvas.bind("<Configure>", lambda e: self.sync_scroll())

        # Vincular el evento de la rueda del ratón al Canvas
        self.canvas.bind("<MouseWheel>", self.on_mouse_wheel_cells)

        # Frame para la paginación y botones adicionales
        self.pagination_frame = tk.Frame(self.root)
        self.pagination_frame.pack(fill="x", padx=10, pady=10)

        self.center_frame = tk.Frame(self.pagination_frame)
        self.center_frame.pack()

        # Botón "Anterior"
        self.prev_button = tk.Button(self.center_frame, text="Anterior", command=self.prev_page)
        self.prev_button.pack(side="left")

        # Etiqueta de la página actual
        self.page_label = tk.Label(self.center_frame, text="Página 1 de 1")
        self.page_label.pack(side="left", padx=10)

        # Botón "Siguiente"
        self.next_button = tk.Button(self.center_frame, text="Siguiente", command=self.next_page)
        self.next_button.pack(side="left")

        # Botón "Generar todo Cache"
        self.generate_all_cache_button = tk.Button(self.center_frame, text="Generar todo Cache",
                                                   command=self.generate_all_cache)
        self.generate_all_cache_button.pack(side="left", padx=(10, 0))

        # Botón "Regenerar caché"
        self.regenerate_cache_button = tk.Button(self.center_frame, text="Regenerar caché",
                                                 command=self.regenerate_cache)
        self.regenerate_cache_button.pack(side="left", padx=(10, 0))

        # Ocultar o mostrar los botones de caché según make_cache
        self.update_cache_buttons_visibility()

    def on_mouse_wheel_cells(self, event):
        menu_state = str(self.platform_menu.cget("state"))  # Guardarlo como string inmutable

        if menu_state == "readonly":
            delta = -event.delta
            if delta > 0:
                self.canvas.yview_scroll(1, "units")
            else:
                self.canvas.yview_scroll(-1, "units")

    def on_mouse_wheel_platforms(self, event):
        """
        Maneja el desplazamiento vertical con la rueda del ratón para la lista de plataformas.
        """
        menu_state = self.platform_menu.cget("state")

        if menu_state == "normal":  # Solo desplazar si el menú está desplegado
            current_index = self.platform_menu.current()
            if event.delta > 0:
                new_index = max(0, current_index - 1)  # Desplazar hacia arriba
            else:
                new_index = min(len(self.platform_menu["values"]) - 1, current_index + 1)  # Desplazar hacia abajo
            self.platform_menu.current(new_index)
            self.platform_combobox.set(self.platform_menu["values"][new_index])
            self.on_platform_select()

    def sync_scroll(self, *args):
        """
        Sincroniza el desplazamiento horizontal entre el header_canvas y el canvas principal.
        No afecta el desplazamiento vertical.
        """
        if len(args) == 2 and args[0] == "moveto":
            fraction = float(args[1])
            self.header_canvas.xview_moveto(fraction)
            self.canvas.xview_moveto(fraction)
        elif len(args) == 2 and args[0] == "scroll":
            amount, units = args[1].split()
            self.header_canvas.xview_scroll(amount, units)
            self.canvas.xview_scroll(amount, units)
        else:
            fraction = self.canvas.xview()[0]
            self.header_canvas.xview_moveto(fraction)

    def toggle_hide_all_si(self):
        # Si el Checkbutton está activado, ocultamos los juegos con "SI" en todas las celdas
        if self.hide_all_si_var.get():
            # Ordenar los juegos alfabéticamente antes de aplicar el filtro
            selected_platform = self.platform_combobox.get()
            if not selected_platform:
                return

            xml_path = self.platforms[selected_platform]
            tree = ET.parse(xml_path)
            root = tree.getroot()

            games = root.findall("Game")
            game_dict = {}

            for game in games:
                title_element = game.find("Title")
                title = title_element.text if title_element is not None else None

                app_path_element = game.find("ApplicationPath")
                if app_path_element is not None and app_path_element.text:
                    app_filename = os.path.basename(app_path_element.text)
                    app_name = os.path.splitext(app_filename)[0]
                else:
                    app_name = None

                if title:
                    game_dict[title] = [title]
                    if app_name and app_name != title:
                        game_dict[title].append(app_name)

            # Ordenar los juegos alfabéticamente (de la A a la Z)
            sorted_game_dict = dict(
                sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))

            # Aplicar el filtro "Ocultar todo SI" sobre la lista ordenada
            self.hide_all_si(sorted_game_dict)
        else:
            # Si está desactivado, mostramos todos los juegos nuevamente
            self.on_platform_select()

    def update_cache_buttons_visibility(self):
        """Actualiza la visibilidad de los botones de caché según el valor de make_cache."""
        if self.make_cache:
            self.generate_all_cache_button.pack(side="left", padx=(10, 0))
            self.regenerate_cache_button.pack(side="left", padx=(10, 0))
        else:
            self.generate_all_cache_button.pack_forget()
            self.regenerate_cache_button.pack_forget()

    def load_platforms(self):
        self.platform_combobox.set("")  # Limpiar la selección actual
        self.platforms.clear()  # Limpiar el diccionario de plataformas

        # Construir la ruta a la carpeta de plataformas
        platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
        if not os.path.exists(platforms_path):
            messagebox.showerror("Error", f"No se encontró la carpeta de plataformas en: {platforms_path}")
            return

        # Obtener los nombres de las plataformas
        platform_names = []
        for filename in os.listdir(platforms_path):
            if filename.endswith(".xml"):
                platform_name = filename[:-4]  # Eliminar la extensión .xml
                self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
                platform_names.append(platform_name)

        if not platform_names:
            messagebox.showerror("Error", "No se encontraron archivos XML en la carpeta de plataformas.")
            return

        # Asignar los valores al Combobox
        self.platform_menu["values"] = platform_names
        self.platform_menu.current(0)  # Seleccionar la primera plataforma por defecto

        # Forzar la actualización de la interfaz
        self.platform_menu.update_idletasks()

        # Cargar los datos de la plataforma seleccionada
        self.on_platform_select()

    def hide_all_si(self, game_dict=None):
        if game_dict is None:
            selected_platform = self.platform_combobox.get()
            if not selected_platform:
                return

            xml_path = self.platforms[selected_platform]
            tree = ET.parse(xml_path)
            root = tree.getroot()

            games = root.findall("Game")
            game_dict = {}

            for game in games:
                title_element = game.find("Title")
                title = title_element.text if title_element is not None else None

                app_path_element = game.find("ApplicationPath")
                if app_path_element is not None and app_path_element.text:
                    app_filename = os.path.basename(app_path_element.text)
                    app_name = os.path.splitext(app_filename)[0]
                else:
                    app_name = None

                if title:
                    game_dict[title] = [title]
                    if app_name and app_name != title:
                        game_dict[title].append(app_name)

        # Filtrar juegos que tienen "SI" en todas las celdas
        filtered_game_dict = self.filter_all_si(game_dict)

        # Actualizar la tabla con los juegos filtrados
        selected_platform = self.platform_combobox.get()
        self.load_filter_file(selected_platform, filtered_game_dict)

    def on_platform_select(self, *args):
        selected_platform = self.platform_combobox.get()
        if not selected_platform:
            return

        try:
            xml_path = self.platforms[selected_platform]
            tree = ET.parse(xml_path)
            root = tree.getroot()

            if root.tag != "LaunchBox":
                messagebox.showerror("Error", f"El archivo XML no tiene la estructura esperada. Elemento raíz: {root.tag}")
                return

            games = root.findall("Game")
            game_dict = {}

            for game in games:
                title_element = game.find("Title")
                title = title_element.text if title_element is not None else None

                app_path_element = game.find("ApplicationPath")
                if app_path_element is not None and app_path_element.text:
                    app_filename = os.path.basename(app_path_element.text)
                    app_name = os.path.splitext(app_filename)[0]
                else:
                    app_name = None

                if title:
                    game_dict[title] = [title]
                    if app_name and app_name != title:
                        game_dict[title].append(app_name)

            sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
            self.load_filter_file(selected_platform, sorted_game_dict)
        except Exception as e:
            messagebox.showerror("Error", f"No se pudo leer el archivo XML: {e}")

    def generate_all_cache(self):
        selected_platform = self.platform_combobox.get()
        if not selected_platform:
            return

        self.progress_label = tk.Label(self.root, text="Generando Caché...")
        self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
        self.root.update()

        original_page = self.current_page

        for page in range(self.total_pages):
            self.current_page = page
            self.on_platform_select()
            self.progress_label.config(text=f"Generando página {page + 1} de {self.total_pages}")
            self.root.update()

        self.current_page = original_page
        self.on_platform_select()
        self.progress_label.destroy()

    def open_settings(self):
        current_dir = os.path.dirname(os.path.abspath(__file__))
        settings_path = os.path.join(current_dir, "Settings.py")
        subprocess.run(["python", settings_path], check=True)

        # Volver a cargar la configuración después de guardar los cambios
        self.load_launchbox_path()

        # Actualizar la visibilidad de los botones de caché
        self.update_cache_buttons_visibility()

        # Actualizar otros elementos de la interfaz si es necesario
        self.on_platform_select()  # Esto actualiza la tabla con los nuevos valores

    def load_launchbox_path(self):
        config_file = os.path.join(os.path.dirname(__file__), "config.txt").replace("\\", "/")

        if not os.path.exists(config_file):
            with open(config_file, "w") as file:
                file.write("path=\n")
                file.write("filters=\n")
                file.write("image_cache_ratio=150x150\n")
                file.write("alternative_path=\n")
                file.write("image_cell=150x150\n")
                file.write("text_cell=150x50\n")
                file.write("max_games_per_page_imagemode=100\n")
                file.write("color_media_yes_title=#0073E6\n")
                file.write("color_media_yes_rom=#00E673\n")
                file.write("color_media_both=#E67300\n")
                file.write("color_media_no=#E60073\n")
                file.write("color_no_trans=#C0C0C0\n")
                file.write("make_cache=true\n")
            print("Archivo config.txt creado con la estructura básica.")
            return False

        with open(config_file, "r") as file:
            config_lines = file.readlines()
            for line in config_lines:
                if line.startswith("path="):
                    self.launchbox_path = line.strip().split('=')[1]
                if line.startswith("filters="):
                    filters_line = line.strip().split('=')[1]
                    self.filters = [f.strip().strip('"') for f in filters_line.split(',')]
                if line.startswith("image_cache_ratio="):
                    self.image_cache_ratio = line.strip().split('=')[1]
                if line.startswith("alternative_path="):
                    self.alternative_path = line.strip().split('=')[1]
                if line.startswith("image_cell="):
                    try:
                        width, height = line.strip().split('=')[1].split('x')
                        self.cell_size_image = (int(width), int(height))
                    except ValueError:
                        print(
                            "Error: El valor de image_cell no tiene el formato correcto. Usando valores predeterminados (200x200).")
                        self.cell_size_image = (200, 200)
                if line.startswith("text_cell="):
                    try:
                        width, height = line.strip().split('=')[1].split('x')
                        self.cell_size_text = (int(width), int(height))
                    except ValueError:
                        print(
                            "Error: El valor de text_cell no tiene el formato correcto. Usando valores predeterminados (100x50).")
                        self.cell_size_text = (100, 50)
                if line.startswith("max_games_per_page_imagemode="):
                    try:
                        self.max_games_per_page_imagemode = int(line.strip().split('=')[1])
                    except ValueError:
                        print(
                            "Error: El valor de max_games_per_page_imagemode no es un número válido. Usando valor predeterminado (20).")
                        self.max_games_per_page_imagemode = 20
                if line.startswith("color_media_yes_title="):
                    self.color_media_yes_title = line.strip().split('=')[1]
                if line.startswith("color_media_yes_rom="):
                    self.color_media_yes_rom = line.strip().split('=')[1]
                if line.startswith("color_media_both="):
                    self.color_media_both = line.strip().split('=')[1]
                if line.startswith("color_media_no="):
                    self.color_media_no = line.strip().split('=')[1]
                if line.startswith("color_no_trans="):
                    self.color_no_trans = line.strip().split('=')[1]
                if line.startswith("color_media_both="):
                    self.color_media_both = line.strip().split('=')[1]
                if line.startswith("make_cache="):  # Nuevo parámetro
                    self.make_cache = line.strip().split('=')[1].lower() == "true"

        return True

    def load_platforms(self):
        self.platform_combobox.set("")  # Limpiar la selección actual
        self.platforms.clear()  # Limpiar el diccionario de plataformas

        # Construir la ruta a la carpeta de plataformas
        platforms_path = os.path.join(self.launchbox_path, "Data", "Platforms").replace("\\", "/")
        if not os.path.exists(platforms_path):
            messagebox.showerror("Error", f"No se encontró la carpeta de plataformas en: {platforms_path}")
            return

        # Obtener los nombres de las plataformas
        platform_names = []
        for filename in os.listdir(platforms_path):
            if filename.endswith(".xml"):
                platform_name = filename[:-4]  # Eliminar la extensión .xml
                self.platforms[platform_name] = os.path.join(platforms_path, filename).replace("\\", "/")
                platform_names.append(platform_name)


        if not platform_names:
            messagebox.showerror("Error", "No se encontraron archivos XML en la carpeta de plataformas.")
            return

        # Asignar los valores al Combobox
        self.platform_menu["values"] = platform_names
        self.platform_menu.current(0)  # Seleccionar la primera plataforma por defecto

        # Forzar la actualización de la interfaz
        self.platform_menu.update_idletasks()

        # Cargar los datos de la plataforma seleccionada
        self.on_platform_select()

    def on_platform_select(self, *args):
        selected_platform = self.platform_combobox.get()
        if not selected_platform:
            return

        try:
            xml_path = self.platforms[selected_platform]
            tree = ET.parse(xml_path)
            root = tree.getroot()

            if root.tag != "LaunchBox":
                messagebox.showerror("Error", f"El archivo XML no tiene la estructura esperada. Elemento raíz: {root.tag}")
                return

            games = root.findall("Game")
            game_dict = {}

            for game in games:
                title_element = game.find("Title")
                title = title_element.text if title_element is not None else None

                app_path_element = game.find("ApplicationPath")
                if app_path_element is not None and app_path_element.text:
                    app_filename = os.path.basename(app_path_element.text)
                    app_name = os.path.splitext(app_filename)[0]
                else:
                    app_name = None

                if title:
                    game_dict[title] = [title]
                    if app_name and app_name != title:
                        game_dict[title].append(app_name)

            sorted_game_dict = dict(sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
            self.load_filter_file(selected_platform, sorted_game_dict)
        except Exception as e:
            messagebox.showerror("Error", f"No se pudo leer el archivo XML: {e}")

    def load_filter_file(self, platform, game_dict):
        filters_to_use = self.filters[:]
        if self.videos_enabled:
            filters_to_use.append("Videos")
        if self.manual_enabled:
            filters_to_use.append("Manual")

        self.columns = len(filters_to_use) + 1  # Definir self.columns
        self.total_pages = (len(game_dict) + self.max_games_per_page_imagemode - 1) // self.max_games_per_page_imagemode
        self.update_pagination_controls()

        start_index = self.current_page * self.max_games_per_page_imagemode
        end_index = start_index + self.max_games_per_page_imagemode
        games_to_show = list(game_dict.items())[start_index:end_index]

        self.canvas.delete("all")
        self.image_references = {}
        self.draw_table(games_to_show, filters_to_use)

        total_width = sum(
            self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
        total_height = self.rows * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
        self.canvas.config(scrollregion=(0, 0, total_width, total_height))

    def prev_page(self):
        if self.current_page > 0:
            self.current_page -= 1
            self.update_pagination_controls()
            self.on_platform_select()

    def next_page(self):
        if self.current_page < self.total_pages - 1:
            self.current_page += 1
            self.update_pagination_controls()
            self.on_platform_select()

    def update_pagination_controls(self):
        self.page_label.config(text=f"Página {self.current_page + 1} de {self.total_pages}")
        self.prev_button.config(state="normal" if self.current_page > 0 else "disabled")
        self.next_button.config(state="normal" if self.current_page < self.total_pages - 1 else "disabled")

    def toggle_view_mode(self):
        self.show_images = self.switch_var.get()

        if self.show_images:
            self.header_canvas.config(height=self.cell_size_image[1])
        else:
            self.header_canvas.config(height=self.cell_size_text[1])

        self.on_platform_select()

    def search_image_in_folder(self, folder, search_names, extensions):
        """
        Busca imágenes en una carpeta y sus subcarpetas de manera recursiva.
        Retorna la ruta de la imagen si la encuentra, o None si no la encuentra.
        """
        for root, dirs, files in os.walk(folder):
            for file in files:
                # Verificar si el archivo coincide con alguno de los nombres y extensiones
                for search_name in search_names:
                    for ext in extensions:
                        if file.startswith(search_name) and file.endswith(ext):
                            return os.path.join(root, file), search_name
        return None, None

    def draw_table(self, games_to_show, filters):
        self.rows = len(games_to_show)

        if self.show_images:
            cell_width, cell_height = self.cell_size_image
        else:
            cell_width, cell_height = self.cell_size_text

        total_width = sum(cell_width for _ in range(self.columns))

        self.header_canvas.config(width=total_width, height=cell_height)
        self.header_canvas.delete("all")
        for col, filter_name in enumerate(["Título"] + filters):
            self.draw_cell(0, col, filter_name, header=True, canvas=self.header_canvas)
            self.header_canvas.tag_bind(f"header_{col}", "<Button-1>", lambda event, col=col: self.on_header_click(col))

        for row, (title, search_names) in enumerate(games_to_show, start=0):
            self.draw_cell(row, 0, title)
            for col, filter_name in enumerate(filters, start=1):
                if filter_name == "Videos":
                    # Definir las rutas principales y alternativas
                    main_folder_path = os.path.join(self.launchbox_path, "Videos",
                                                    self.platform_combobox.get()).replace("\\", "/")
                    alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
                                                           "Videos").replace("\\",
                                                                             "/") if self.alternative_path else None

                    file_found = False
                    match_type = None
                    file_path = None

                    # Buscar en la ruta alternativa primero (si existe)
                    if alternative_folder_path and os.path.exists(alternative_folder_path):
                        # Buscar <Title>-01.mp4
                        file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
                                                                                                               "/")
                        if os.path.isfile(file_path):
                            file_found = True
                            match_type = 'title'
                        else:
                            # Buscar <ApplicationPath>.mp4 (sin -01)
                            if len(search_names) > 1:
                                file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'
                            else:
                                # Si <Title> y <ApplicationPath> son iguales, buscar <Title>.mp4 (sin -01)
                                file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.mp4").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'

                    # Si no se encontró en la ruta alternativa, buscar en la ruta principal
                    if not file_found and os.path.exists(main_folder_path):
                        # Buscar <Title>-01.mp4
                        file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
                        if os.path.isfile(file_path):
                            file_found = True
                            match_type = 'title'
                        else:
                            # Buscar <ApplicationPath>.mp4 (sin -01)
                            if len(search_names) > 1:
                                file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'
                            else:
                                # Si <Title> y <ApplicationPath> son iguales, buscar <Title>.mp4 (sin -01)
                                file_path = os.path.join(main_folder_path, f"{search_names[0]}.mp4").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'

                    # Mostrar el resultado en la tabla
                    if self.show_images:
                        if file_found:
                            self.draw_cell(row, col, "SI",
                                           cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
                        else:
                            self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
                    else:
                        cell_value = "SI (Title)" if match_type == 'title' else "SI (Rom)" if match_type == 'rom' else "NO"
                        cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
                        self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)

                elif filter_name == "Manual":
                    # Definir las rutas principales y alternativas
                    main_folder_path = os.path.join(self.launchbox_path, "Manuals",
                                                    self.platform_combobox.get()).replace("\\", "/")
                    alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
                                                           "Manuals").replace("\\",
                                                                              "/") if self.alternative_path else None

                    file_found = False
                    match_type = None
                    file_path = None

                    # Buscar en la ruta alternativa primero (si existe)
                    if alternative_folder_path and os.path.exists(alternative_folder_path):
                        # Buscar <Title>-01.pdf
                        file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
                                                                                                               "/")
                        if os.path.isfile(file_path):
                            file_found = True
                            match_type = 'title'
                        else:
                            # Buscar <ApplicationPath>.pdf (sin -01)
                            if len(search_names) > 1:
                                file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'
                            else:
                                # Si <Title> y <ApplicationPath> son iguales, buscar <Title>.pdf (sin -01)
                                file_path = os.path.join(alternative_folder_path, f"{search_names[0]}.pdf").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'

                    # Si no se encontró en la ruta alternativa, buscar en la ruta principal
                    if not file_found and os.path.exists(main_folder_path):
                        # Buscar <Title>-01.pdf
                        file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
                        if os.path.isfile(file_path):
                            file_found = True
                            match_type = 'title'
                        else:
                            # Buscar <ApplicationPath>.pdf (sin -01)
                            if len(search_names) > 1:
                                file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'
                            else:
                                # Si <Title> y <ApplicationPath> son iguales, buscar <Title>.pdf (sin -01)
                                file_path = os.path.join(main_folder_path, f"{search_names[0]}.pdf").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True
                                    match_type = 'rom'

                    # Mostrar el resultado en la tabla
                    if self.show_images:
                        if file_found:
                            self.draw_cell(row, col, "SI",
                                           cell_color=self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom)
                        else:
                            self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
                    else:
                        cell_value = "SI (Title)" if match_type == 'title' else "SI (Rom)" if match_type == 'rom' else "NO"
                        cell_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom if match_type == 'rom' else self.color_media_no
                        self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)

                else:
                    image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
                                                filter_name).replace("\\", "/")
                    image_found = False
                    image_path = None
                    match_type = None
                    title_found = False
                    rom_found = False
                    title_path = None
                    rom_path = None

                    if os.path.exists(image_folder):
                        # Buscar <Title>-01.ext de forma recursiva
                        for ext in ["png", "jpg"]:
                            for root, dirs, files in os.walk(image_folder):
                                for file in files:
                                    if file.startswith(f"{search_names[0]}-01") and file.endswith(ext):
                                        title_path = os.path.join(root, file)
                                        title_found = True
                                        break
                                if title_found:
                                    break
                            if title_found:
                                break

                        # Buscar <ApplicationPath>.ext de forma no recursiva
                        if len(search_names) > 1:
                            for ext in ["png", "jpg"]:
                                rom_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
                                if os.path.isfile(rom_path):
                                    rom_found = True
                                    break
                        else:
                            # Si <Title> y <ApplicationPath> son iguales, buscar <Title>.ext (sin -01)
                            for ext in ["png", "jpg"]:
                                rom_path = os.path.join(image_folder, f"{search_names[0]}.{ext}").replace("\\", "/")
                                if os.path.isfile(rom_path):
                                    rom_found = True
                                    break

                    # Determinar el tipo de coincidencia
                    if title_found and rom_found:
                        match_type = 'title/rom'
                        image_path = title_path  # Usar la imagen de Title para mostrar
                    elif title_found:
                        match_type = 'title'
                        image_path = title_path
                    elif rom_found:
                        match_type = 'rom'
                        image_path = rom_path

                    if self.show_images:
                        if image_path:
                            self.draw_image_cell(row, col, image_path, match_type)
                        else:
                            self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
                    else:
                        if match_type == 'title/rom':
                            cell_value = "SI (Title/Rom)"
                            cell_color = self.color_media_both
                        elif match_type == 'title':
                            cell_value = "SI (Title)"
                            cell_color = self.color_media_yes_title
                        elif match_type == 'rom':
                            cell_value = "SI (Rom)"
                            cell_color = self.color_media_yes_rom
                        else:
                            cell_value = "NO"
                            cell_color = self.color_media_no
                        self.draw_cell(row, col, cell_value, cell_color=cell_color, is_text=True)

        total_width = sum(
            self.cell_size_image[0] if self.show_images else self.cell_size_text[0] for _ in range(self.columns))
        total_height = len(games_to_show) * (self.cell_size_image[1] if self.show_images else self.cell_size_text[1])
        self.canvas.config(scrollregion=(0, 0, total_width, total_height))
        self.header_canvas.config(scrollregion=(0, 0, total_width, cell_height))

        # Forzar el foco al Canvas para que reciba eventos de la rueda del ratón
        self.canvas.focus_set()

    def on_header_click(self, col):
        # Llamar al método para ordenar los datos
        self.sort_by_column(col)

    def draw_cell(self, row, col, value, header=False, cell_color=None, is_text=False, canvas=None):
        if canvas is None:
            canvas = self.canvas

        if header:
            if self.show_images:
                cell_width, cell_height = self.cell_size_image
            else:
                cell_width, cell_height = self.cell_size_text
        elif is_text or not self.show_images:
            cell_width, cell_height = self.cell_size_text
        else:
            cell_width, cell_height = self.cell_size_image

        x1 = col * cell_width
        y1 = row * cell_height
        x2 = x1 + cell_width
        y2 = y1 + cell_height

        if value == "SI (Title)":
            fill_color = self.color_media_yes_title
        elif value == "SI (Rom)":
            fill_color = self.color_media_yes_rom
        elif value == "NO":
            fill_color = self.color_media_no
        else:
            fill_color = cell_color or "white"

        # Dibujar la celda
        canvas.create_rectangle(x1, y1, x2, y2, fill=fill_color, outline="black",
                                tags=f"header_{col}" if header else f"cell_{row}_{col}")

        font_style = ("Arial", 10, "bold") if header else ("Arial", 10)
        wrapped_text = self.wrap_text(value, cell_width - 10)
        text_y = y1 + (cell_height - len(wrapped_text) * 12) / 2

        for line in wrapped_text:
            canvas.create_text(x1 + 5, text_y, anchor="w", text=line, font=font_style,
                               tags=f"header_{col}" if header else f"cell_{row}_{col}")
            text_y += 12

    def wrap_text(self, text, max_width):
        import textwrap
        return textwrap.wrap(text, width=max_width // 7)

    def draw_image_cell(self, row, col, image_path, match_type):
        try:
            cell_width, cell_height = self.cell_size_image

            if self.make_cache:
                # Cargar desde la caché
                cache_folder = os.path.join(os.path.dirname(__file__), "cache", self.platform_combobox.get(),
                                            self.filters[col - 1])
                os.makedirs(cache_folder, exist_ok=True)

                cache_image_path = os.path.join(cache_folder,
                                                os.path.basename(image_path).replace(".png", ".jpg").replace(".PNG",
                                                                                                             ".jpg"))

                width, height = map(int, self.image_cache_ratio.split("x"))

                if not os.path.exists(cache_image_path):
                    try:
                        file_size = os.path.getsize(image_path)
                        if file_size > 100 * 1024 * 1024:
                            raise ValueError(
                                f"El archivo {image_path} es demasiado grande ({file_size / (1024 * 1024):.2f} MB)")

                        with Image.open(image_path) as image:
                            if image.mode in ("P", "1", "L", "LA"):
                                image = image.convert("RGBA")

                            if image.mode == "RGBA":
                                background = Image.new("RGB", image.size, self.color_no_trans)
                                background.paste(image, mask=image.split()[-1])
                                image = background

                            image.thumbnail((width, height))
                            image.save(cache_image_path, "JPEG")

                    except Exception as e:
                        print(f"Error al procesar la imagen {image_path}: {e}")
                        with open("imagenes_con_error.txt", "a") as error_file:
                            error_file.write(f"{image_path}\n")
                        self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
                        return

                with Image.open(cache_image_path) as image:
                    tk_image = ImageTk.PhotoImage(image)
                    self.image_references[(row, col)] = tk_image

            else:
                # Cargar directamente desde la fuente
                with Image.open(image_path) as image:
                    if image.mode in ("P", "1", "L", "LA"):
                        image = image.convert("RGBA")

                    if image.mode == "RGBA":
                        background = Image.new("RGB", image.size, self.color_no_trans)
                        background.paste(image, mask=image.split()[-1])
                        image = background

                    image.thumbnail((cell_width, cell_height))
                    tk_image = ImageTk.PhotoImage(image)
                    self.image_references[(row, col)] = tk_image

            x1 = col * self.cell_size_image[0]
            y1 = row * self.cell_size_image[1]

            bg_color = self.color_media_yes_title if match_type == 'title' else self.color_media_yes_rom
            self.canvas.create_rectangle(x1, y1, x1 + cell_width, y1 + cell_height, fill=bg_color, outline="black")
            self.canvas.create_image(x1 + cell_width // 2, y1 + cell_height // 2, anchor="center", image=tk_image)
        except Exception as e:
            print(f"Error inesperado al procesar la imagen {image_path}: {e}")
            self.draw_cell(row, col, "NO", cell_color=self.color_media_no)
            with open("imagenes_con_error.txt", "a") as error_file:
                error_file.write(f"{image_path}\n")

    def regenerate_cache(self):
        selected_platform = self.platform_combobox.get()
        if not selected_platform:
            return

        self.progress_label = tk.Label(self.root, text="Generando Caché...")
        self.progress_label.place(relx=0.5, rely=0.5, anchor="center")
        self.root.update()

        current_platform = self.platform_combobox.get()
        cache_folder = os.path.join(os.path.dirname(__file__), "cache", current_platform)
        if os.path.exists(cache_folder):
            shutil.rmtree(cache_folder)

        self.load_platforms()
        self.platform_combobox.set(current_platform)
        self.on_platform_select()
        self.progress_label.destroy()

    def natural_sort_key(self, text):
        import re
        return [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', text)]

    def sort_by_column(self, col):
        selected_platform = self.platform_combobox.get()
        if not selected_platform:
            return

        xml_path = self.platforms[selected_platform]
        tree = ET.parse(xml_path)
        root = tree.getroot()

        games = root.findall("Game")
        game_dict = {}

        for game in games:
            title_element = game.find("Title")
            title = title_element.text if title_element is not None else None

            app_path_element = game.find("ApplicationPath")
            if app_path_element is not None and app_path_element.text:
                app_filename = os.path.basename(app_path_element.text)
                app_name = os.path.splitext(app_filename)[0]
            else:
                app_name = None

            if title:
                game_dict[title] = [title]
                if app_name and app_name != title:
                    game_dict[title].append(app_name)

        # Ordenar los juegos
        if col == 0:
            # Ordenar por título
            sorted_game_dict = dict(
                sorted(game_dict.items(), key=lambda item: (self.natural_sort_key(item[0]), item[0])))
        else:
            # Ordenar por la columna seleccionada
            filter_name = self.filters[col - 1]
            sorted_game_dict = dict(
                sorted(game_dict.items(), key=lambda item: self.get_filter_value(item[1], filter_name)))

        # Alternar entre orden ascendente y descendente
        if self.last_sorted_column == col:
            self.sort_ascending = not self.sort_ascending
            if not self.sort_ascending:
                sorted_game_dict = dict(reversed(list(sorted_game_dict.items())))
        else:
            self.sort_ascending = True

        self.last_sorted_column = col

        # Si el filtro "Ocultar todo SI" está activado, aplicar el filtro después de ordenar
        if self.hide_all_si_var.get():
            sorted_game_dict = self.filter_all_si(sorted_game_dict)

        # Actualizar la interfaz gráfica con los juegos ordenados (y filtrados si es necesario)
        self.load_filter_file(selected_platform, sorted_game_dict)

    def filter_all_si(self, game_dict):
        """
        Filtra los juegos que tienen "SI" en todas las celdas.
        """
        filtered_game_dict = {}
        for title, search_names in game_dict.items():
            all_si = True
            for filter_name in self.filters:
                if filter_name == "Videos":
                    # Lógica para videos
                    main_folder_path = os.path.join(self.launchbox_path, "Videos",
                                                    self.platform_combobox.get()).replace("\\", "/")
                    alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
                                                           "Videos").replace("\\",
                                                                             "/") if self.alternative_path else None

                    file_found = False
                    if alternative_folder_path and os.path.exists(alternative_folder_path):
                        file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.mp4").replace("\\",
                                                                                                               "/")
                        if os.path.isfile(file_path):
                            file_found = True
                        else:
                            if len(search_names) > 1:
                                file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.mp4").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True

                    if not file_found and os.path.exists(main_folder_path):
                        file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.mp4").replace("\\", "/")
                        if os.path.isfile(file_path):
                            file_found = True
                        else:
                            if len(search_names) > 1:
                                file_path = os.path.join(main_folder_path, f"{search_names[1]}.mp4").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True

                    if not file_found:
                        all_si = False
                        break

                elif filter_name == "Manual":
                    # Lógica para manuales
                    main_folder_path = os.path.join(self.launchbox_path, "Manuals",
                                                    self.platform_combobox.get()).replace("\\", "/")
                    alternative_folder_path = os.path.join(self.alternative_path, self.platform_combobox.get(),
                                                           "Manuals").replace("\\",
                                                                              "/") if self.alternative_path else None

                    file_found = False
                    if alternative_folder_path and os.path.exists(alternative_folder_path):
                        file_path = os.path.join(alternative_folder_path, f"{search_names[0]}-01.pdf").replace("\\",
                                                                                                               "/")
                        if os.path.isfile(file_path):
                            file_found = True
                        else:
                            if len(search_names) > 1:
                                file_path = os.path.join(alternative_folder_path, f"{search_names[1]}.pdf").replace(
                                    "\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True

                    if not file_found and os.path.exists(main_folder_path):
                        file_path = os.path.join(main_folder_path, f"{search_names[0]}-01.pdf").replace("\\", "/")
                        if os.path.isfile(file_path):
                            file_found = True
                        else:
                            if len(search_names) > 1:
                                file_path = os.path.join(main_folder_path, f"{search_names[1]}.pdf").replace("\\", "/")
                                if os.path.isfile(file_path):
                                    file_found = True

                    if not file_found:
                        all_si = False
                        break

                else:
                    # Lógica para imágenes
                    image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(),
                                                filter_name).replace("\\", "/")
                    image_found = False
                    if os.path.exists(image_folder):
                        for ext in ["png", "jpg"]:
                            image_path = os.path.join(image_folder, f"{search_names[0]}-01.{ext}").replace("\\", "/")
                            if os.path.isfile(image_path):
                                image_found = True
                                break
                            if len(search_names) > 1:
                                image_path = os.path.join(image_folder, f"{search_names[1]}.{ext}").replace("\\", "/")
                                if os.path.isfile(image_path):
                                    image_found = True
                                    break

                    if not image_found:
                        all_si = False
                        break

            if not all_si:
                filtered_game_dict[title] = search_names

        return filtered_game_dict

    def get_filter_value(self, search_names, filter_name):
        image_folder = os.path.join(self.launchbox_path, "Images", self.platform_combobox.get(), filter_name).replace(
            "\\", "/")
        if os.path.exists(image_folder):
            for search_name in search_names:
                for ext in ["png", "jpg"]:
                    image_path = os.path.join(image_folder, f"{search_name}.{ext}").replace("\\", "/")
                    if os.path.isfile(image_path):
                        return search_name
        return ""

if __name__ == "__main__":
    try:
        root = tk.Tk()
        app = LaunchBoxManager(root)
        root.mainloop()
    except MemoryError:
        print("Error de memoria grave. El programa debe cerrarse.")
    except Exception as e:
        print(f"Error inesperado: {e}")
        messagebox.showerror("Error", f"Se produjo un error inesperado: {e}")


Y el Settings.py (Al primero le puedes dar el nombre que quieras, pero este segundo si tiene que llamarse asi por que lo llama el otro)
import os
import tkinter as tk
from tkinter import filedialog, messagebox, colorchooser


class SettingsWindow:
    def __init__(self, root, config_file):
        self.root = root
        self.config_file = config_file
        self.root.title("Configuración")

        # Crear un Canvas y Scrollbars
        self.canvas = tk.Canvas(self.root)
        self.scrollbar_y = tk.Scrollbar(self.root, orient="vertical", command=self.canvas.yview)
        self.scrollbar_x = tk.Scrollbar(self.root, orient="horizontal", command=self.canvas.xview)

        # Configurar el Canvas para que sea desplazable
        self.scrollable_frame = tk.Frame(self.canvas)
        self.scrollable_frame.bind(
            "<Configure>",
            lambda e: self.canvas.configure(
                scrollregion=self.canvas.bbox("all")
            )
        )

        self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)

        # Configurar el evento de la rueda del ratón
        self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)

        # Colocar el Canvas y los Scrollbars en la ventana
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.scrollbar_y.grid(row=0, column=1, sticky="ns")
        self.scrollbar_x.grid(row=1, column=0, sticky="ew")

        # Configurar la expansión de la ventana
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)

        # Variables para almacenar los valores
        self.launchbox_path = tk.StringVar(value="C:/LaunchBox")
        self.alternative_path = tk.StringVar(value="D:/LaunchBox")
        self.image_cache_ratio_w = tk.StringVar(value="200")
        self.image_cache_ratio_h = tk.StringVar(value="200")
        self.text_cell_w = tk.StringVar(value="150")
        self.text_cell_h = tk.StringVar(value="50")
        self.image_cell_w = tk.StringVar(value="200")
        self.image_cell_h = tk.StringVar(value="200")
        self.max_games_per_page_imagemode = tk.StringVar(value="200")
        self.color_media_yes_title = tk.StringVar(value="#80FF00")
        self.color_media_yes_rom = tk.StringVar(value="#80AA00")
        self.color_media_both= tk.StringVar(value="#DBDBDB")
        self.color_media_no = tk.StringVar(value="#FF0000")
        self.color_no_trans = tk.StringVar(value="#DBDBDB")
        self.selected_filters = ["Box - Front", "Clear Logo"]
        self.make_cache = tk.BooleanVar()

        # Cargar los valores actuales del archivo config.txt
        self.load_config()

        # Interfaz gráfica
        self.setup_ui()

        # Ajustar tamaño de la ventana al contenido más lejano sin margen adicional
        self.root.update_idletasks()
        width = self.scrollable_frame.winfo_reqwidth()
        height = self.scrollable_frame.winfo_reqheight()
        self.root.geometry(f"{width}x{height}")

        # Vincular el evento de redimensionamiento de la ventana
        self.root.bind("<Configure>", self._on_window_resize)

    def _on_mousewheel(self, event):
        """Desplaza el Canvas con la rueda del ratón."""
        if event.delta:
            self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    def _on_window_resize(self, event):
        """Actualiza la región de desplazamiento cuando la ventana se redimensiona."""
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    def setup_ui(self):
        """Configura los elementos de la interfaz gráfica."""
        main_frame = tk.Frame(self.scrollable_frame)
        main_frame.pack(fill="both", expand=True, padx=10, pady=10)

        # Ruta de LaunchBox
        tk.Label(main_frame, text="Ruta de LaunchBox:").grid(row=0, column=0, sticky="w")
        self.path_entry = tk.Entry(main_frame, textvariable=self.launchbox_path, width=40)
        self.path_entry.grid(row=0, column=1, sticky="w")
        tk.Button(main_frame, text="Buscar", command=self.browse_launchbox_path).grid(row=0, column=2)

        # Filtros seleccionados
        tk.Label(main_frame, text="Filtros Seleccionados:").grid(row=1, column=0, sticky="w", pady=(10, 0))
        self.filters = [
            "Arcade - Marquee", "Banner", "Advertisement Flyer - Back", "Advertisement Flyer - Front", "Box - Front",
            "Box - Front - Reconstructed", "Fanart - Box - Front", "Box - 3D", "Box - Back",
            "Box - Back - Reconstructed", "Fanart - Box - Back", "Box - Spine", "Box - Full", "Clear Logo",
            "Cart - Front", "Disc", "Fanart - Cart - Front", "Fanart - Disc", "Cart - Back", "Fanart - Cart - Back",
            "Cart - 3D", "Fanart - Background", "Screenshot - Game Over", "Screenshot - Game Select",
            "Screenshot - Game Title", "Screenshot - Gameplay", "Screenshot - High Scores", "Epic Games Background",
            "Epic Games Poster", "Epic Games Screenshot", "GOG Poster", "GOG Screenshot", "Origin Background",
            "Origin Poster", "Origin Screenshot", "Steam Banner", "Steam Poster", "Steam Screenshot",
            "Uplay Background", "Uplay Thumbnail", "Arcade - Cabinet", "Arcade - Circuit Board",
            "Arcade - Control Panel", "Arcade - Controls Information", "Amazon Background", "Amazon Poster",
            "Amazon Screenshot", "Videos", "Manual"
        ]

        self.filter_vars = []
        num_columns = 3  # Número de columnas para los filtros
        max_filters_per_column = (len(self.filters) // num_columns) + (1 if len(self.filters) % num_columns != 0 else 0)
        for i, filter_name in enumerate(self.filters):
            var = tk.BooleanVar(value=(filter_name in self.selected_filters))
            row = 2 + (i % max_filters_per_column)
            column = i // max_filters_per_column
            tk.Checkbutton(main_frame, text=filter_name, variable=var).grid(row=row, column=column, sticky="w")
            self.filter_vars.append(var)

        # Ajustar el ancho del scrollable_frame al contenido más ancho
        self.scrollable_frame.update_idletasks()
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

        row_offset = 2 + max_filters_per_column

        # Añadir salto de línea
        tk.Label(main_frame, text="").grid(row=row_offset, column=0)
        row_offset += 1

        # Máximo de juegos listados por página
        tk.Label(main_frame, text="Máximo de juegos listados por página:").grid(row=row_offset, column=0, sticky="w")
        self.max_games_entry = tk.Entry(main_frame, textvariable=self.max_games_per_page_imagemode, width=5)
        self.max_games_entry.grid(row=row_offset, column=1, padx=(0, 0))
        row_offset += 1

        # Añadir salto de línea
        tk.Label(main_frame, text="").grid(row=row_offset, column=0)
        row_offset += 1

        # Tamaño de las celdas en modo Texto
        self.create_size_input(main_frame, "Tamaño de las Celdas en modo Texto", row_offset, self.text_cell_w,
                               self.text_cell_h)
        row_offset += 1

        # Tamaño de las celdas en modo Imagen
        self.create_size_input(main_frame, "Tamaño de las Celdas en modo Imagen", row_offset, self.image_cell_w,
                               self.image_cell_h)
        row_offset += 1

        # Añadir doble salto de línea
        tk.Label(main_frame, text="").grid(row=row_offset, column=0)
        tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
        row_offset += 2

        # Ruta alternativa a la Media (Video/Manuals)
        tk.Label(main_frame, text="Ruta alternativa a la Media (Videos/Manuals):").grid(row=row_offset, column=0, sticky="w")
        self.alt_path_entry = tk.Entry(main_frame, textvariable=self.alternative_path, width=40)
        self.alt_path_entry.grid(row=row_offset, column=1, sticky="w")
        tk.Button(main_frame, text="Browse", command=self.browse_alternative_path).grid(row=row_offset, column=2)
        # Solo para Videos y Manuals, con el formato [Platforma]/Video/[Nombre del juego].mp4 y [Platforma]/Manuals/[Nombe del juego].pdf
        tk.Label(main_frame,
                 text="Solo para Videos y Manuals, con el formato [Platforma]/Videos/[Nombre del juego].mp4 y [Platforma]/Manuals/[Nombe del juego].pdf").grid(
            row=row_offset + 1, column=0, columnspan=3, sticky="w")
        row_offset += 2

        # Añadir doble salto de línea
        tk.Label(main_frame, text="").grid(row=row_offset, column=0)
        tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
        row_offset += 2

        # Selectores de color
        # Color Media encontrada Title
        tk.Label(main_frame, text="Color Media encontrada con Title:").grid(row=row_offset, column=0, sticky="w")
        self.color_media_yes_title_display = tk.Label(main_frame, textvariable=self.color_media_yes_title, width=10,
                                                      bg=self.color_media_yes_title.get())
        self.color_media_yes_title_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
        self.color_media_yes_title_button = tk.Button(main_frame, text="Seleccionar color",
                                                      command=self.choose_color_media_yes_title)
        self.color_media_yes_title_button.grid(row=row_offset, column=1, sticky="e")
        row_offset += 1
        # Color Media encontrada Rom
        tk.Label(main_frame, text="Color Media encontrada con RomName:").grid(row=row_offset, column=0, sticky="w")
        self.color_media_yes_rom_display = tk.Label(main_frame, textvariable=self.color_media_yes_rom, width=10,
                                                    bg=self.color_media_yes_rom.get())
        self.color_media_yes_rom_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
        self.color_media_yes_rom_button = tk.Button(main_frame, text="Seleccionar color",
                                                    command=self.choose_color_media_yes_rom)
        self.color_media_yes_rom_button.grid(row=row_offset, column=1, sticky="e")
        row_offset += 1
        # Color Media encontrada Ambos
        tk.Label(main_frame, text="Color Media encontrada con ambos:").grid(row=row_offset, column=0, sticky="w")
        self.color_media_both_display = tk.Label(main_frame, textvariable=self.color_media_both, width=10,
                                                    bg=self.color_media_both.get())
        self.color_media_both_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 0))
        self.color_media_both_button = tk.Button(main_frame, text="Seleccionar color",
                                                    command=self.choose_color_media_both)
        self.color_media_both_button.grid(row=row_offset, column=1, sticky="e")
        row_offset += 1

        # Color Media NO encontrada
        tk.Label(main_frame, text="Color Media NO encontrada:").grid(row=row_offset, column=0, sticky="w")
        self.color_media_no_display = tk.Label(main_frame, textvariable=self.color_media_no, width=10,
                                               bg=self.color_media_no.get())
        self.color_media_no_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
        self.color_media_no_button = tk.Button(main_frame, text="Seleccionar color", command=self.choose_color_media_no)
        self.color_media_no_button.grid(row=row_offset, column=1, sticky="e")
        row_offset += 1

        # Color fondo imagenes
        tk.Label(main_frame, text="Color fondo imagenes:").grid(row=row_offset, column=0, sticky="w")
        self.color_no_trans_display = tk.Label(main_frame, textvariable=self.color_no_trans, width=10,
                                               bg=self.color_no_trans.get())
        self.color_no_trans_display.grid(row=row_offset, column=1, sticky="w", padx=(0, 30))
        self.color_no_trans_button = tk.Button(main_frame, text="Seleccionar color", command=self.choose_color_no_trans)
        self.color_no_trans_button.grid(row=row_offset, column=1, sticky="e")
        row_offset += 1

        # Añadir doble salto de línea
        tk.Label(main_frame, text="").grid(row=row_offset, column=0)
        tk.Label(main_frame, text="").grid(row=row_offset + 1, column=0)
        row_offset += 2

        # Opción de hacer caché
        tk.Checkbutton(main_frame, text="Hacer caché Local (Mejor rendimiento)", variable=self.make_cache, onvalue=True,
                       offvalue=False).grid(row=row_offset, column=0, sticky="w")
        row_offset += 1

        # Ratio de imágenes en caché
        self.create_size_input(main_frame, "Ratio de las imágenes en caché", row_offset, self.image_cache_ratio_w,
                               self.image_cache_ratio_h)
        row_offset += 1

        # Botón Guardar
        tk.Button(main_frame, text="Guardar", command=self.save_config).grid(row=row_offset, column=0, columnspan=3,
                                                                             pady=(20, 0))

    def create_size_input(self, frame, label_text, row, var_w, var_h):
        """Crea una fila con una etiqueta, dos entradas y una 'x' en el medio, con espaciado ajustado."""
        tk.Label(frame, text=label_text).grid(row=row, column=0, sticky="w")
        tk.Entry(frame, textvariable=var_w, width=5).grid(row=row, column=1, padx=(0, 0))  # Eliminar padding derecho
        tk.Label(frame, text="x").grid(row=row, column=1, padx=(32, 0))  # Eliminar padding
        tk.Entry(frame, textvariable=var_h, width=5).grid(row=row, column=1, padx=(80, 0))  # Eliminar padding izquierdo

    def browse_launchbox_path(self):
        folder_path = filedialog.askdirectory(title="Selecciona la carpeta de LaunchBox")
        if folder_path:
            self.launchbox_path.set(folder_path)

    def browse_alternative_path(self):
        folder_path = filedialog.askdirectory(title="Selecciona la carpeta alternativa de Media")
        if folder_path:
            self.alternative_path.set(folder_path)

    def choose_color_media_yes_title(self):
        color_code = colorchooser.askcolor(title="Selecciona un color")
        if color_code[1]:  # Verifica si se seleccionó un color (color_code[1] es el código hexadecimal)
            self.color_media_yes_title.set(color_code[1])
            self.color_media_yes_title_display.config(bg=color_code[1])

    def choose_color_media_yes_rom(self):
        color_code = colorchooser.askcolor(title="Selecciona un color")
        if color_code[1]:  # Verifica si se seleccionó un color
            self.color_media_yes_rom.set(color_code[1])
            self.color_media_yes_rom_display.config(bg=color_code[1])
           
    def choose_color_media_both(self):
        color_code = colorchooser.askcolor(title="Selecciona un color")
        if color_code[1]:  # Verifica si se seleccionó un color
            self.color_media_both.set(color_code[1])
            self.color_media_both_display.config(bg=color_code[1])

    def choose_color_media_no(self):
        color_code = colorchooser.askcolor(title="Selecciona un color")
        if color_code[1]:  # Verifica si se seleccionó un color
            self.color_media_no.set(color_code[1])
            self.color_media_no_display.config(bg=color_code[1])

    def choose_color_no_trans(self):
        color_code = colorchooser.askcolor(title="Selecciona un color")
        if color_code[1]:  # Verifica si se seleccionó un color
            self.color_no_trans.set(color_code[1])
            self.color_no_trans_display.config(bg=color_code[1])

    def load_config(self):
        """Carga los valores actuales del archivo config.txt."""
        if os.path.exists(self.config_file):
            with open(self.config_file, "r") as file:
                for line in file:
                    key, value = line.strip().split('=', 1)
                    value = value.strip('"')
                    if key == "path":
                        self.launchbox_path.set(value)
                    elif key == "filters":
                        self.selected_filters = value.split(',')
                    elif key == "image_cache_ratio":
                        try:
                            w, h = value.split('x')
                            self.image_cache_ratio_w.set(w)
                            self.image_cache_ratio_h.set(h)
                        except ValueError:
                            print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
                            # Aquí puedes definir un valor por defecto o manejar el error de otra forma
                            self.image_cache_ratio_w.set(0)
                            self.image_cache_ratio_h.set(0)
                    elif key == "text_cell":
                        try:
                            w, h = value.split('x')
                            self.text_cell_w.set(w)
                            self.text_cell_h.set(h)
                        except ValueError:
                            print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
                            # Aquí puedes definir un valor por defecto o manejar el error de otra forma
                            self.text_cell_w.set(0)
                            self.text_cell_h.set(0)
                    elif key == "image_cell":
                        try:
                            try:
                                w, h = value.split('x')
                                self.image_cell_w.set(w)
                                self.image_cell_h.set(h)
                            except ValueError:
                                print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
                                # Aquí puedes definir un valor por defecto o manejar el error de otra forma
                                self.image_cell_w.set(0)
                                self.image_cell_h.set(0)
                        except ValueError:
                            print(f"Error: el valor '{value}' no está en el formato 'ancho x alto'")
                            # Aquí puedes definir un valor por defecto o manejar el error de otra forma
                            self.image_cell_w.set(0)
                            self.image_cell_h.set(0)

                    elif key == "alternative_path":
                        self.alternative_path.set(value)
                    elif key == "max_games_per_page_imagemode":
                        self.max_games_per_page_imagemode.set(value)
                    elif key == "color_media_yes_title":
                        self.color_media_yes_title.set(value if value else "#80FF00")
                    elif key == "color_media_yes_rom":
                        self.color_media_yes_rom.set(value if value else "80AA00")
                    elif key == "color_media_both":
                        self.color_media_both.set(value if value else "#DBDBDB")
                    elif key == "color_media_no":
                        self.color_media_no.set(value if value else "#FF0000")
                    elif key == "color_media_yes_rom":
                        self.color_media_yes_rom.set(value if value else "80AA00")
                    elif key == "color_no_trans":
                        self.color_no_trans.set(value if value else "#DBDBDB")
                    elif key == "make_cache":
                        self.make_cache.set(value.lower() == "true")

    def save_config(self):
        """Guarda los valores seleccionados en el archivo config.txt."""
        selected_filters = [self.filters[i] for i, var in enumerate(self.filter_vars) if var.get()]
        with open(self.config_file, "w") as file:
            file.write(f"path={self.launchbox_path.get()}\n")
            file.write(f'filters="{",".join(selected_filters)}"\n')
            file.write(f"image_cache_ratio={self.image_cache_ratio_w.get()}x{self.image_cache_ratio_h.get()}\n")
            file.write(f"text_cell={self.text_cell_w.get()}x{self.text_cell_h.get()}\n")
            file.write(f"image_cell={self.image_cell_w.get()}x{self.image_cell_h.get()}\n")
            file.write(f"alternative_path={self.alternative_path.get()}\n")
            file.write(f"max_games_per_page_imagemode={self.max_games_per_page_imagemode.get()}\n")
            file.write(f"color_media_yes_title={self.color_media_yes_title.get()}\n")
            file.write(f"color_media_yes_rom={self.color_media_yes_rom.get()}\n")
            file.write(f"color_media_both={self.color_media_both.get()}\n")
            file.write(f"color_media_no={self.color_media_no.get()}\n")
            file.write(f"color_no_trans={self.color_no_trans.get()}\n")
            file.write(f"make_cache={self.make_cache.get()}\n")
        messagebox.showinfo("Guardar", "Configuración guardada correctamente.")
        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    config_file = os.path.join(os.path.dirname(__file__), "config.txt")
    app = SettingsWindow(root, config_file)
    root.mainloop()


Los adjunto tambien por si alguien quiere descargarlos y no crearlos el mismo pero vamos que es copiar al Notepad y decirle guardar como .py, si no quieres que salga la terminal se puede guardar como pyw

PD: Necesitas tener Python 3, que se puede descargar desde el sitio web oficial https://www.python.org/downloads/windows/ . También necesitas instalar Pillow ejecutando el siguiente comando en una terminal:
pip install pillow

Adjuntos

LaunchBox - Media Manager.rar (12.18 KB)

LaunchBox - Media Manager ya en .py
@Messiahs Impresionante que hayas logrado algo así sin saber programar, aunque sea con IA. O no sé si solamente es que no sabes programar en Python pero sí en otros lenguajes, no me ha quedado claro.
SirAzraelGrotesque escribió:@Messiahs Impresionante que hayas logrado algo así sin saber programar, aunque sea con IA. O no sé si solamente es que no sabes programar en Python pero sí en otros lenguajes, no me ha quedado claro.


Na no sé programar en ninguno, lo más que hice algún script con Autohotkey para algún juego online xD de pulsar teclas cada x tiempo y tal, pero con las IA está tirado, empizas poco a poco y vas mejorando, al principio way por qué el código es pequeño y te lo dan entero cuando empiezan a darte solo cachos y tienes que pegarlo viene el problema, hasta que me di cuenta lo sensible que era a los espacios el python no veas yo todo el rato que no que n iba y le pedía el código completo entonces funcionaba...

Habré tardado unas 8h o así aún siendo con la IA no lo hice en 10 minutos, pero me ponía a ratos y lo iba "ampliando".
Messiahs escribió:
SirAzraelGrotesque escribió:@Messiahs Impresionante que hayas logrado algo así sin saber programar, aunque sea con IA. O no sé si solamente es que no sabes programar en Python pero sí en otros lenguajes, no me ha quedado claro.


Na no sé programar en ninguno, lo más que hice algún script con Autohotkey para algún juego online xD de pulsar teclas cada x tiempo y tal, pero con las IA está tirado, empizas poco a poco y vas mejorando, al principio way por qué el código es pequeño y te lo dan entero cuando empiezan a darte solo cachos y tienes que pegarlo viene el problema, hasta que me di cuenta lo sensible que era a los espacios el python no veas yo todo el rato que no que n iba y le pedía el código completo entonces funcionaba...

Habré tardado unas 8h o así aún siendo con la IA no lo hice en 10 minutos, pero me ponía a ratos y lo iba "ampliando".


Enlazar distintas partes de un programa y hacerlas funcionar en conjunto no es tarea fácil. Más si cabe si usas funciones/procedimientos que reciban y devuelvan datos. Que no he mirado si es el caso, aunque deduzco que no, que todo el programa será en un bloque. Como sea, mi enhorabuena y te animo a que investigues un poco sobre programación, que es probable que te guste. ;)
Con pynstaller puedes crear el exe y así no debes instalar ninguna dependencia ;)
Gracias no lo sabía, pero mejor con el código para compartirlo, por si alguien se atreve a intentar comprender algo xD. Además de que siempre puedes arreglar fallos o ampliar algo.
De todas formas probaré a crear el exe por curiosidad a ver.
518 respuestas
17, 8, 9, 10, 11