├── requirements.txt ├── app.ico ├── resources.rcc ├── about.py ├── app.pyw ├── _debug.py ├── CHANGELOG.md ├── _platforms.py ├── _constants.py ├── _updater.py ├── splashscreen.py ├── options.py ├── _settings.py ├── ui ├── ui_DownloadQueue.py ├── ui_DownloadPane.py ├── ui_About.py ├── ui_Options.py └── ui_MainWindow.py ├── README.md ├── download_queue.py ├── _tools.py └── mainwindow.py /requirements.txt: -------------------------------------------------------------------------------- 1 | py7zr 2 | PyQt6 3 | requests 4 | -------------------------------------------------------------------------------- /app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silverlays/NoIntro-Roms-Downloader/HEAD/app.ico -------------------------------------------------------------------------------- /resources.rcc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/silverlays/NoIntro-Roms-Downloader/HEAD/resources.rcc -------------------------------------------------------------------------------- /about.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import * 2 | from PyQt6.QtGui import * 3 | from PyQt6.QtWidgets import * 4 | 5 | # Ui 6 | from ui.ui_About import Ui_About as Ui 7 | 8 | 9 | 10 | class About(QDialog, Ui): 11 | def __init__(self, parent): 12 | super().__init__(parent) 13 | self.setupUi(self) 14 | self.setWindowFlag(Qt.WindowType.MSWindowsFixedSizeDialogHint, True) -------------------------------------------------------------------------------- /app.pyw: -------------------------------------------------------------------------------- 1 | import sys, os 2 | from PyQt6.QtCore import * 3 | from PyQt6.QtGui import * 4 | from PyQt6.QtWidgets import * 5 | 6 | # Helpers 7 | from _constants import * 8 | from _debug import * 9 | 10 | # Main class 11 | from splashscreen import SplashScreen 12 | from mainwindow import MainWindow 13 | 14 | 15 | os.environ["DEBUG"] = "4" # 0 = DISABLE 16 | # 1 = ERROR 17 | # 2 = WARNING 18 | # 3 = INFO 19 | # 4 = DEBUG 20 | 21 | 22 | if __name__ == "__main__": 23 | # Initialize PyQt 24 | app = QApplication(sys.argv) 25 | 26 | # Load theme and ressources 27 | QResource.registerResource(RESOURCES_FILE) 28 | 29 | # Show the splashscreen and do starting stuff 30 | splash = SplashScreen(app) 31 | splash.show() 32 | 33 | # Initialize main window 34 | mainWindow = MainWindow(splash.settings, splash.updater, splash.platforms) 35 | mainWindow.show() 36 | 37 | # Execute then shutdown 38 | sys.exit(app.exec()) 39 | -------------------------------------------------------------------------------- /_debug.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PyQt6.QtCore import qDebug 3 | from enum import Enum 4 | from typing import Literal 5 | 6 | 7 | class DebugType(Enum): 8 | TYPE_INFO = "INFO" 9 | TYPE_WARNING = "WARNING" 10 | TYPE_ERROR = "ERROR" 11 | TYPE_DEBUG = "DEBUG" 12 | 13 | class DebugHelper(): 14 | def print(debug_type: Literal[DebugType.TYPE_INFO, DebugType.TYPE_WARNING, DebugType.TYPE_ERROR], debug_message: str, debug_module: str = None): 15 | if os.environ['DEBUG'] == "0": return 16 | elif os.environ['DEBUG'] == "1" and (debug_type != DebugType.TYPE_ERROR): return 17 | elif os.environ['DEBUG'] == "2" and (debug_type != DebugType.TYPE_ERROR and debug_type != DebugType.TYPE_WARNING): return 18 | elif os.environ['DEBUG'] == "3" and (debug_type != DebugType.TYPE_ERROR and debug_type != DebugType.TYPE_WARNING and debug_type != DebugType.TYPE_INFO): return 19 | else: 20 | message = ''.join(f"[{debug_type.value}]") 21 | if debug_module: message += f"[{debug_module.upper()}]" 22 | message += f" {debug_message}" 23 | qDebug(message) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v2.0.0 RC1 4 | ### Qt, new engine, cross-platform and more 5 | * TBD 6 | 7 | ## v1.3 8 | ### About screen, tools & more 9 | * File checker tool (md5): This tool scan a directory for roms and compare file's hash. 10 | * Fullset Comparer: Compare your local folder with the NoIntro list to know what you miss (or failed?). 11 | * About screen added to access my social networks. 12 | * Can now handle hyperlink for the whole program. 13 | 14 | ## v1.2 15 | ### Full redesign & more 16 | * Fully redesigned interface. 17 | * A way less I/O network operations since I discovered a "JSON" manner to handle data scrapped from [Internet Archive](https://archive.org). 18 | * Changed games combo view with a listview to be able to download for than one file at once. 19 | * Added cursors. 20 | * Added file's details window. 21 | * Added Unzip (7z only) option. 22 | * Added region filters. 23 | 24 | ## v1.1 25 | ### New features 26 | * Added size info to ROMS. 27 | * Added more precise progress bars. 28 | * Added download progress status texts. 29 | * Updated UI. 30 | 31 | ## v1.0 32 | ### Initial release 33 | * Download a single game from NoIntro Internet Archive repo. -------------------------------------------------------------------------------- /_platforms.py: -------------------------------------------------------------------------------- 1 | import os, pickle 2 | 3 | # Helpers 4 | from _constants import * 5 | from _debug import * 6 | 7 | 8 | class PlatformsHelper(): 9 | _platformsCache = {} 10 | 11 | 12 | def __init__(self): 13 | if os.path.exists(PLATFORMS_CACHE_FILENAME): 14 | try: 15 | with open(PLATFORMS_CACHE_FILENAME, 'rb') as fp: self._platformsCache = pickle.load(fp) 16 | DebugHelper.print(DebugType.TYPE_INFO, f"<{PLATFORMS_CACHE_FILENAME}> sucessfully loaded!", "PLATFORMS") 17 | except Exception as e: DebugHelper.print(DebugType.TYPE_ERROR, f"Error: {list(e.args)}", "EXCEPTION") 18 | 19 | 20 | def platformsCount(self) -> int: 21 | return len(self._platformsCache) 22 | 23 | 24 | def getRomsCount(self, platform_name: str) -> int: 25 | return len(self._platformsCache[platform_name]) 26 | 27 | 28 | def getPlatformName(self, index: int) -> str: 29 | return list(self._platformsCache.keys())[index] 30 | 31 | 32 | def getRomName(self, platform_name: str, index: int) -> str: 33 | return list(self._platformsCache[platform_name].keys())[index] 34 | 35 | 36 | def getRom(self, platform_name: str, rom_name: str) -> dict: 37 | return self._platformsCache[platform_name][rom_name] 38 | -------------------------------------------------------------------------------- /_constants.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | VERSION_MAJOR = 2 4 | VERSION_MINOR = 0 5 | VERSION_REVISION = "0 RC1" 6 | 7 | RESOURCES_FILE = os.path.join(os.path.split(__file__)[0], "resources.rcc") 8 | SETTINGS_FILE = 'settings.dat' 9 | PLATFORMS_CACHE_FILENAME = "database_cache.dat" 10 | 11 | ARCHIVE_PLATFORMS_DATA = [ 12 | [ 'Nintendo - NES', '7z', 'nointro.nes-headered' ], 13 | [ 'Nintendo - SNES', '7z', 'nointro.snes' ], 14 | [ 'Nintendo - 64', '7z', 'nointro.n64' ], 15 | [ 'Nintendo - 64DD', '7z', 'nointro.n64dd' ], 16 | [ 'Nintendo - VirtualBoy', '7z', 'nointro.vb' ], 17 | [ 'Nintendo - GameBoy', '7z', 'nointro.gb' ], 18 | [ 'Nintendo - GameBoy Color', '7z', 'nointro.gbc' ], 19 | [ 'Nintendo - GameBoy Advance', '7z', 'nointro.gba' ], 20 | [ 'Sega - Master System / Mark III', '7z', 'nointro.ms-mkiii' ], 21 | [ 'Sega - Megadrive / Genesis', '7z', 'nointro.md' ], 22 | [ 'Sega - 32X', '7z', 'nointro.32x' ], 23 | [ 'Sega - Game Gear', '7z', 'nointro.gg' ], 24 | [ 'Atari 2600', '7z', 'nointro.atari-2600' ], 25 | [ 'Atari 5200', '7z', 'nointro.atari-5200' ], 26 | [ 'Atari 7800', '7z', 'nointro.atari-7800' ], 27 | [ 'NEC - PC Engine - TurboGrafx 16' , '7z', 'nointro.tg-16' ] 28 | # [ 'Sony - Playstation', 'zip', 'non-redump_sony_playstation' ], 29 | # [ 'Sony - Playstation', '7z', 'redump-sony-playstation-pal'], 30 | # [ 'Sony - Playstation 2', 'zip', 27, 'PS2_COLLECTION_PART$$' ], 31 | # [ 'Sony - Playstation 3', 'zip', 8, 'PS3_NOINTRO_EUR_$$' ], 32 | ] 33 | -------------------------------------------------------------------------------- /_updater.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | # Helpers 4 | from _constants import * 5 | from _debug import * 6 | 7 | 8 | class UpdaterHelper(): 9 | LASTEST_MAJOR = VERSION_MAJOR 10 | LASTEST_MINOR = VERSION_MINOR 11 | LASTEST_REVISION = VERSION_REVISION 12 | 13 | 14 | def __init__(self) -> None: 15 | try: 16 | self.last_release_version: str = requests.get('https://api.github.com/repos/silverlays/NoIntro-Roms-Downloader/releases').json()[0]['name'] 17 | self.last_release_version = self.last_release_version.removeprefix('v').split('.') 18 | 19 | self.LASTEST_MAJOR = int(self.last_release_version[0]) 20 | self.LASTEST_MINOR = int(self.last_release_version[1][0:1]) # TO BE MODIFIED WHEN v1.3a WILL BE GONE 21 | self.LASTEST_REVISION = 0 # THIS ONE TOO... 22 | except Exception as e: 23 | DebugHelper.print(DebugType.TYPE_ERROR, list(e.args), "EXCEPTION") 24 | 25 | 26 | def updateAvailable(self) -> bool: 27 | if VERSION_MAJOR < self.LASTEST_MAJOR: 28 | DebugHelper.print(DebugType.TYPE_INFO, "Update available!", "UPDATER") 29 | return True 30 | elif (VERSION_MINOR < self.LASTEST_MINOR) and (VERSION_MAJOR == self.LASTEST_MAJOR): 31 | DebugHelper.print(DebugType.TYPE_INFO, "Update available!", "UPDATER") 32 | return True 33 | else: 34 | DebugHelper.print(DebugType.TYPE_INFO, "You have the lastest version.", "UPDATER") 35 | return False 36 | 37 | 38 | def currentVersionString(self) -> str: 39 | return f"v{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_REVISION}" 40 | 41 | def lastestVersionString(self) -> str: 42 | return f"v{self.LASTEST_MAJOR}.{self.LASTEST_MINOR}.{self.LASTEST_REVISION}" -------------------------------------------------------------------------------- /splashscreen.py: -------------------------------------------------------------------------------- 1 | import os, time 2 | from PyQt6.QtCore import * 3 | from PyQt6.QtGui import * 4 | from PyQt6.QtWidgets import * 5 | 6 | # Helpers 7 | from _settings import * 8 | from _updater import * 9 | from _platforms import * 10 | from _tools import * 11 | from _debug import * 12 | 13 | 14 | 15 | class SplashScreen(QSplashScreen): 16 | settings: SettingsHelper 17 | updater: UpdaterHelper 18 | platforms: PlatformsHelper 19 | 20 | 21 | def __init__(self, app: QApplication): 22 | super().__init__(flags=Qt.WindowType.WindowStaysOnTopHint) 23 | 24 | self.app = app 25 | self.settings = SettingsHelper() 26 | self.updater = UpdaterHelper() 27 | 28 | self.setPixmap(QPixmap(':/splash.png')) 29 | 30 | 31 | def show(self): 32 | super().show() 33 | 34 | # Check cache validity and presence. If not, build it. 35 | if not os.path.exists(PLATFORMS_CACHE_FILENAME): 36 | DebugHelper.print(DebugType.TYPE_WARNING, f"<{PLATFORMS_CACHE_FILENAME}> not found. Download needed.", "SPLASHSCREEN") 37 | cache = CacheGenerator(self.app, self) 38 | cache.run() 39 | elif not Tools.isCacheValid(self.settings.get('cache_expiration')): 40 | DebugHelper.print(DebugType.TYPE_WARNING, f"<{PLATFORMS_CACHE_FILENAME}> is outdated. Download needed.", "SPLASHSCREEN") 41 | cache = CacheGenerator(self.app, self) 42 | cache.run() 43 | else: 44 | self.showMessage("Loading data, please wait...", 45 | color=Qt.GlobalColor.white, 46 | alignment=(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignCenter) 47 | ) 48 | time.sleep(2) 49 | self.platforms = PlatformsHelper() 50 | super().close() 51 | 52 | 53 | def mousePressEvent(self, a0: QMouseEvent) -> None: 54 | ''' 55 | Overrided to avoid splashscreen being hided 56 | ''' 57 | return 58 | -------------------------------------------------------------------------------- /options.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import * 2 | from PyQt6.QtGui import * 3 | from PyQt6.QtWidgets import * 4 | 5 | # Ui 6 | from ui.ui_Options import Ui_Dialog as Ui 7 | 8 | # Helpers 9 | from _settings import SettingsHelper 10 | 11 | 12 | class Options(QDialog, Ui): 13 | _settings: SettingsHelper 14 | 15 | def __init__(self, parent, settings: SettingsHelper): 16 | super().__init__(parent) 17 | self._settings = settings 18 | 19 | # Setup UI 20 | self.setupUi(self) 21 | self.setWindowFlag(Qt.WindowType.MSWindowsFixedSizeDialogHint, True) 22 | 23 | # Setup events 24 | self.pb_BrowsePath.clicked.connect(self._onBrowsePathClicked) 25 | self.accepted.connect(self._onAccept) 26 | 27 | 28 | def show(self) -> None: 29 | # Cache expiration 30 | cache_expiration = self._settings.get('cache_expiration') 31 | cache_expiration = str(cache_expiration) if cache_expiration != 0 else "" 32 | self.cb_cache_expiration.setCurrentText(cache_expiration) 33 | 34 | # Download path 35 | self.le_DownloadPath.setText(self._settings.get('download_path')) 36 | 37 | # Decompress after download 38 | self.cb_unzip.setChecked(self._settings.get('unzip')) 39 | 40 | # Check for updates at startup 41 | self.cb_checkupdates.setChecked(self._settings.get('check_updates')) 42 | return super().show() 43 | 44 | 45 | def _onBrowsePathClicked(self): 46 | new_path = QFileDialog.getExistingDirectory() 47 | if new_path != "": self.le_DownloadPath.setText(new_path) 48 | 49 | 50 | def _onAccept(self): 51 | import os 52 | 53 | # Cache expiration 54 | cache_expiration = self.cb_cache_expiration.currentText() 55 | cache_expiration = int(cache_expiration) if cache_expiration != "" else 0 56 | self._settings.update(['cache_expiration', cache_expiration]) 57 | 58 | # Download path 59 | if os.path.isdir(self.le_DownloadPath.text()): 60 | self._settings.update(['download_path', self.le_DownloadPath.text()]) 61 | 62 | # Decompress after download 63 | self._settings.update(['unzip', self.cb_unzip.isChecked()]) 64 | 65 | # Check for updates at startup 66 | self._settings.update(['check_updates', self.cb_checkupdates.isChecked()]) 67 | 68 | # Write settings 69 | self._settings.write() 70 | -------------------------------------------------------------------------------- /_settings.py: -------------------------------------------------------------------------------- 1 | import os, pickle 2 | from typing import Any, Tuple 3 | 4 | # Helper 5 | from _constants import * 6 | from _debug import * 7 | 8 | 9 | 10 | class SettingsHelper(): 11 | full_path = SETTINGS_FILE 12 | _settings = { 13 | "cache_expiration": 30, 14 | "check_updates": True, 15 | "download_path": os.getcwd(), 16 | "unzip": True, 17 | } 18 | 19 | 20 | def __init__(self): 21 | if os.path.exists(self.full_path): 22 | self._read() 23 | else: 24 | DebugHelper.print(DebugType.TYPE_WARNING, f"<{SETTINGS_FILE}> not found.", "SETTINGS") 25 | self.write() 26 | 27 | 28 | def get(self, option: str): 29 | for op in self._settings: 30 | if option == op: return self._settings[option] 31 | raise ValueError(f"Setting <{option}> not found.") 32 | 33 | 34 | def update(self, option: Tuple[str, Any]): 35 | key, value = option 36 | 37 | for op in self._settings: 38 | if op == key: 39 | self._settings[key] = value 40 | DebugHelper.print(DebugType.TYPE_DEBUG, f"'{key}' updated to {value}.", "SETTINGS") 41 | return 42 | raise ValueError(f"Setting <{key}> not found.") 43 | 44 | 45 | def write(self): 46 | with open(self.full_path, 'wb') as fp: 47 | pickle.dump(self._settings, fp) 48 | DebugHelper.print(DebugType.TYPE_INFO, f"<{SETTINGS_FILE}> wrote.", "SETTINGS") 49 | 50 | 51 | def _read(self): 52 | try: 53 | with open(self.full_path, 'rb') as fp: 54 | temp_settings: dict = pickle.load(fp) 55 | if len(temp_settings.keys()) != len(self._settings.keys()): self._fix(temp_settings) 56 | else: self._settings = temp_settings 57 | DebugHelper.print(DebugType.TYPE_INFO, f"<{SETTINGS_FILE}> loaded.", "SETTINGS") 58 | for option in self._settings: DebugHelper.print(DebugType.TYPE_DEBUG, f"'{option}': {str(self._settings[option])}") 59 | except EOFError: pass 60 | 61 | 62 | def _fix(self, old_settings: dict): 63 | DebugHelper.print(DebugType.TYPE_WARNING, f"Application and file mismatch. Trying to fix...", "SETTINGS") 64 | for key in old_settings: 65 | try: 66 | if self._settings[key]: self._settings[key] = old_settings[key] 67 | DebugHelper.print(DebugType.TYPE_WARNING, f"'{key}' recovered.", "SETTINGS") 68 | except KeyError: 69 | DebugHelper.print(DebugType.TYPE_ERROR, f"'{key}' cannot be recovered.", "SETTINGS") 70 | self.write() 71 | -------------------------------------------------------------------------------- /ui/ui_DownloadQueue.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'd:\Python Projects\NoIntro Roms Downloader\ui\DownloadQueue.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.0 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_DownloadQueue(object): 13 | def setupUi(self, DownloadQueue): 14 | DownloadQueue.setObjectName("DownloadQueue") 15 | DownloadQueue.resize(380, 459) 16 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(DownloadQueue) 17 | self.verticalLayout_2.setObjectName("verticalLayout_2") 18 | self.verticalLayout = QtWidgets.QVBoxLayout() 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.lwToDownload = QtWidgets.QListWidget(DownloadQueue) 21 | self.lwToDownload.setSelectionRectVisible(True) 22 | self.lwToDownload.setObjectName("lwToDownload") 23 | self.verticalLayout.addWidget(self.lwToDownload) 24 | self.verticalLayout_2.addLayout(self.verticalLayout) 25 | self.horizontalLayout = QtWidgets.QHBoxLayout() 26 | self.horizontalLayout.setContentsMargins(0, 0, -1, 0) 27 | self.horizontalLayout.setObjectName("horizontalLayout") 28 | self.pbDownload = QtWidgets.QPushButton(DownloadQueue) 29 | font = QtGui.QFont() 30 | font.setBold(True) 31 | self.pbDownload.setFont(font) 32 | self.pbDownload.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 33 | self.pbDownload.setAutoDefault(True) 34 | self.pbDownload.setObjectName("pbDownload") 35 | self.horizontalLayout.addWidget(self.pbDownload) 36 | self.pbDelete = QtWidgets.QPushButton(DownloadQueue) 37 | self.pbDelete.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 38 | self.pbDelete.setObjectName("pbDelete") 39 | self.horizontalLayout.addWidget(self.pbDelete) 40 | self.pbDeleteAll = QtWidgets.QPushButton(DownloadQueue) 41 | font = QtGui.QFont() 42 | font.setBold(True) 43 | font.setItalic(True) 44 | self.pbDeleteAll.setFont(font) 45 | self.pbDeleteAll.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 46 | self.pbDeleteAll.setObjectName("pbDeleteAll") 47 | self.horizontalLayout.addWidget(self.pbDeleteAll) 48 | self.verticalLayout_2.addLayout(self.horizontalLayout) 49 | 50 | self.retranslateUi(DownloadQueue) 51 | QtCore.QMetaObject.connectSlotsByName(DownloadQueue) 52 | 53 | def retranslateUi(self, DownloadQueue): 54 | _translate = QtCore.QCoreApplication.translate 55 | DownloadQueue.setWindowTitle(_translate("DownloadQueue", "Download queue...")) 56 | self.pbDownload.setText(_translate("DownloadQueue", "Download")) 57 | self.pbDelete.setText(_translate("DownloadQueue", "Delete")) 58 | self.pbDeleteAll.setText(_translate("DownloadQueue", "DELETE ALL /!\\")) 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NoIntro-Roms-Downloader 2 | ![NoIntro Roms Downloader UI](https://github.com/user-attachments/assets/db73e725-460e-4f35-96c6-7558477400a4) 3 | 4 |

My work is totally free, but if you want to support me anyway, you can do it here https://en.tipeee.com/silverlays. Thank you!🙏

5 | 6 | ## NEWS ## 7 | - **06/26/2023**: *Good news! I may have finally found a viable alternative from using the "archive.org" site. If all goes to plan, you'll have a TON of new systems available, and download speeds will be GREATLY improved. I'll share more with you soon!* 8 | - **02/12/2023**: *Time has finally come !! v2.0.0 RC1 is up **[HERE](https://github.com/silverlays/NoIntro-Roms-Downloader/releases/tag/v2.0-RC1)** (Carefully read the release description to know how to use it).* 9 | 10 | ## DESCRIPTION 11 | Download "datted" ROMs directly from the NoIntro libraries on [Internet Archive](https://archive.org). Lastest binaries are available [here](https://github.com/silverlays/NoIntro-Roms-Downloader/releases/latest) or you can just clone the repo and launch it under Windows and Linux. (see below) 12 | 13 | If you want to see changes since the beginning of this project, see [CHANGELOG.md](https://github.com/silverlays/NoIntro-Roms-Downloader/blob/master/CHANGELOG.md). 14 | 15 | ## Supported platforms on 02/13/23 16 | - [Atari 2600](https://archive.org/details/nointro.atari-2600) 17 | - [Atari 5200](https://archive.org/details/nointro.atari-5200) 18 | - [Atari 7800](https://archive.org/details/nointro.atari-7800) 19 | - [NEC - PC Engine - TurboGrafx 16](https://archive.org/details/nointro.tg-16) 20 | - [Nintendo - Nintendo 64](https://archive.org/details/nointro.n64) 21 | - [Nintendo - Nintendo 64DD](https://archive.org/details/nointro.n64dd) 22 | - [Nintendo - Nintendo Entertainment System (Headered)](https://archive.org/details/nointro.nes-headered) 23 | - [Nintendo - Nintendo Game Boy](https://archive.org/details/nointro.gb) 24 | - [Nintendo - Nintendo Game Boy Advance](https://archive.org/details/nointro.gba) 25 | - [Nintendo - Nintendo Game Boy Color](https://archive.org/details/nointro.gbc) 26 | - [Nintendo - Super Nintendo Entertainment System (Combined)](https://archive.org/details/nointro.snes) 27 | - [Nintendo - Virtual Boy](https://archive.org/details/nointro.vb) 28 | - [Sega - 32X](https://archive.org/details/nointro.32x) 29 | - [Sega - Game Gear](https://archive.org/details/nointro.gg) 30 | - [Sega - Master System - Mark III](https://archive.org/details/nointro.ms-mkiii) 31 | - [Sega - Mega Drive - Genesis](https://archive.org/details/nointro.md) 32 | 33 | *NB: Playstation 1, 2 and 3 are ready to use, but because of the size of files, multi-connections must be implemented before, sorry.* 34 | 35 | ## Launch without PyInstaller (Windows/Linux/MacOS) 36 | ### Step one 37 | ``` 38 | git clone https://github.com/silverlays/NoIntro-Roms-Downloader 39 | cd ./NoIntro-Roms-Downloader 40 | python3 -m venv .venv 41 | ``` 42 | 43 | ### Step two 44 | * For Windows: ```.venv\Scripts\activate.bat``` 45 | * For Linux/MacOS: ```source .venv/bin/activate``` 46 | 47 | ### Step three 48 | ``` 49 | pip install -r requirements.txt 50 | python3 app.pyw 51 | ``` 52 | 53 | ## Feedback 54 | If you found a bug (not listed on the status above), feel free to create an issue [here](https://github.com/silverlays/NoIntro-Roms-Downloader/issues). 55 | -------------------------------------------------------------------------------- /ui/ui_DownloadPane.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'd:\Python Projects\NoIntro Roms Downloader\ui\DownloadPane.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.0 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_DownloadPane(object): 13 | def setupUi(self, DownloadPane): 14 | DownloadPane.setObjectName("DownloadPane") 15 | DownloadPane.resize(851, 68) 16 | self.verticalLayout = QtWidgets.QVBoxLayout(DownloadPane) 17 | self.verticalLayout.setObjectName("verticalLayout") 18 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 19 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 20 | self.label_3 = QtWidgets.QLabel(DownloadPane) 21 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Preferred) 22 | sizePolicy.setHorizontalStretch(0) 23 | sizePolicy.setVerticalStretch(0) 24 | sizePolicy.setHeightForWidth(self.label_3.sizePolicy().hasHeightForWidth()) 25 | self.label_3.setSizePolicy(sizePolicy) 26 | self.label_3.setObjectName("label_3") 27 | self.horizontalLayout_2.addWidget(self.label_3) 28 | self.l_job = QtWidgets.QLabel(DownloadPane) 29 | self.l_job.setObjectName("l_job") 30 | self.horizontalLayout_2.addWidget(self.l_job) 31 | self.verticalLayout.addLayout(self.horizontalLayout_2) 32 | self.horizontalLayout = QtWidgets.QHBoxLayout() 33 | self.horizontalLayout.setObjectName("horizontalLayout") 34 | self.l_progress = QtWidgets.QLabel(DownloadPane) 35 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 36 | sizePolicy.setHorizontalStretch(0) 37 | sizePolicy.setVerticalStretch(0) 38 | sizePolicy.setHeightForWidth(self.l_progress.sizePolicy().hasHeightForWidth()) 39 | self.l_progress.setSizePolicy(sizePolicy) 40 | self.l_progress.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight|QtCore.Qt.AlignmentFlag.AlignTrailing|QtCore.Qt.AlignmentFlag.AlignVCenter) 41 | self.l_progress.setObjectName("l_progress") 42 | self.horizontalLayout.addWidget(self.l_progress) 43 | self.pb_progress = QtWidgets.QProgressBar(DownloadPane) 44 | self.pb_progress.setProperty("value", 0) 45 | self.pb_progress.setTextVisible(False) 46 | self.pb_progress.setObjectName("pb_progress") 47 | self.horizontalLayout.addWidget(self.pb_progress) 48 | self.l_speed = QtWidgets.QLabel(DownloadPane) 49 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Preferred) 50 | sizePolicy.setHorizontalStretch(0) 51 | sizePolicy.setVerticalStretch(0) 52 | sizePolicy.setHeightForWidth(self.l_speed.sizePolicy().hasHeightForWidth()) 53 | self.l_speed.setSizePolicy(sizePolicy) 54 | self.l_speed.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) 55 | self.l_speed.setObjectName("l_speed") 56 | self.horizontalLayout.addWidget(self.l_speed) 57 | self.verticalLayout.addLayout(self.horizontalLayout) 58 | 59 | self.retranslateUi(DownloadPane) 60 | QtCore.QMetaObject.connectSlotsByName(DownloadPane) 61 | 62 | def retranslateUi(self, DownloadPane): 63 | _translate = QtCore.QCoreApplication.translate 64 | DownloadPane.setWindowTitle(_translate("DownloadPane", "Form")) 65 | self.label_3.setText(_translate("DownloadPane", "Actual job :")) 66 | self.l_job.setText(_translate("DownloadPane", "N/A")) 67 | self.l_progress.setText(_translate("DownloadPane", "0/0")) 68 | self.l_speed.setText(_translate("DownloadPane", "NaN Kb/s")) 69 | -------------------------------------------------------------------------------- /download_queue.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import * 2 | from PyQt6.QtGui import * 3 | from PyQt6.QtWidgets import * 4 | 5 | # Helpers 6 | from _platforms import PlatformsHelper 7 | 8 | # Ui 9 | from ui.ui_DownloadQueue import Ui_DownloadQueue 10 | 11 | 12 | 13 | class CustomListItemWidget(QListWidgetItem): 14 | rom_platform = "" 15 | rom_name = "" 16 | rom_index = -1 17 | 18 | def __init__(self, platforms: PlatformsHelper, rom_platform: str, rom_index: int): 19 | super().__init__() 20 | self.rom_platform = rom_platform 21 | self.rom_name = platforms.getRomName(rom_platform, rom_index) 22 | self.rom_index = rom_index 23 | self.setText(f"[{self.rom_platform}] {self.rom_name}") 24 | 25 | 26 | 27 | class DownloadQueue(QDialog, Ui_DownloadQueue): 28 | """This class handle the Download Queue""" 29 | queue_dict = {} 30 | platforms: PlatformsHelper = None 31 | 32 | 33 | def __init__(self, parent: QMainWindow, platforms: PlatformsHelper): 34 | super().__init__(parent) 35 | 36 | # Setup UI 37 | self.setupUi(self) 38 | self.pbDownload.setEnabled(False) 39 | self.pbDelete.setEnabled(False) 40 | self.pbDeleteAll.setEnabled(False) 41 | 42 | # Setup events 43 | self.lwToDownload.selectionChanged = self._onSelectionChanged 44 | self.pbDownload.clicked.connect(lambda: self.downloadClickedEvent()) 45 | self.pbDelete.clicked.connect(self._onpbDeleteClicked) 46 | self.pbDeleteAll.clicked.connect(self._onpbDeleteAllClicked) 47 | 48 | # Setup variables 49 | self.platforms = platforms 50 | 51 | 52 | def show(self, *args) -> None: 53 | self._refreshList() 54 | return super().show() 55 | 56 | 57 | def add(self, platform_name: str, roms_indexes: list[int]) -> None: 58 | try: self.queue_dict[platform_name] += roms_indexes 59 | except KeyError: self.queue_dict[platform_name] = roms_indexes 60 | finally: self.queue_dict[platform_name] = sorted(self.queue_dict[platform_name]) 61 | 62 | 63 | def remove(self, platform_name: str, rom_index: int): 64 | self.queue_dict[platform_name].remove(rom_index) 65 | self._refreshList() 66 | 67 | 68 | def getTotalCount(self) -> int: 69 | count = 0 70 | for i in self.queue_dict.keys(): 71 | count += len(self.queue_dict[i]) 72 | return count 73 | 74 | 75 | def updatedListEvent(self): 76 | # Do nothing. Overrided by the parent 77 | pass 78 | 79 | 80 | def downloadClickedEvent(self): 81 | # Do nothing. Overrided by the parent 82 | pass 83 | 84 | 85 | def _refreshList(self): 86 | self.lwToDownload.clear() 87 | for platform in self.queue_dict.keys(): 88 | for rom_index in self.queue_dict[platform]: 89 | self.lwToDownload.addItem(CustomListItemWidget(self.platforms, platform, rom_index)) 90 | if self.lwToDownload.count() > 0: 91 | self.pbDownload.setEnabled(True) 92 | self.pbDeleteAll.setEnabled(True) 93 | else: 94 | self.pbDownload.setEnabled(False) 95 | self.pbDeleteAll.setEnabled(False) 96 | self.updatedListEvent() 97 | 98 | 99 | def _onSelectionChanged(self, selected: QItemSelection, deselect: QItemSelection): 100 | try: 101 | text = self.lwToDownload.selectedItems()[0].text() 102 | self.pbDelete.setEnabled(True) 103 | except: 104 | self.pbDelete.setEnabled(False) 105 | 106 | 107 | def _onpbDeleteClicked(self, checked: bool): 108 | liw: CustomListItemWidget = self.lwToDownload.selectedItems()[0] 109 | self.queue_dict[liw.rom_platform].remove(liw.rom_index) 110 | self._refreshList() 111 | 112 | 113 | def _onpbDeleteAllClicked(self, checked: bool): 114 | if QMessageBox.warning(self, "Warning!", "Are you sure you want to delete the WHOLE QUEUE LIST ?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) == QMessageBox.StandardButton.Yes: 115 | self.queue_dict.clear() 116 | self._refreshList() -------------------------------------------------------------------------------- /ui/ui_About.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'd:\Python Projects\NoIntro Roms Downloader (Ui version)\ui\About.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.0 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_About(object): 13 | def setupUi(self, About): 14 | About.setObjectName("About") 15 | About.resize(533, 242) 16 | About.setModal(True) 17 | self.gridLayout = QtWidgets.QGridLayout(About) 18 | self.gridLayout.setObjectName("gridLayout") 19 | self.author = QtWidgets.QLabel(About) 20 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Maximum) 21 | sizePolicy.setHorizontalStretch(0) 22 | sizePolicy.setVerticalStretch(0) 23 | sizePolicy.setHeightForWidth(self.author.sizePolicy().hasHeightForWidth()) 24 | self.author.setSizePolicy(sizePolicy) 25 | font = QtGui.QFont() 26 | font.setPointSize(11) 27 | font.setBold(True) 28 | self.author.setFont(font) 29 | self.author.setObjectName("author") 30 | self.gridLayout.addWidget(self.author, 1, 1, 1, 1) 31 | self.program_title = QtWidgets.QLabel(About) 32 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Maximum) 33 | sizePolicy.setHorizontalStretch(0) 34 | sizePolicy.setVerticalStretch(0) 35 | sizePolicy.setHeightForWidth(self.program_title.sizePolicy().hasHeightForWidth()) 36 | self.program_title.setSizePolicy(sizePolicy) 37 | font = QtGui.QFont() 38 | font.setPointSize(26) 39 | font.setBold(True) 40 | self.program_title.setFont(font) 41 | self.program_title.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) 42 | self.program_title.setObjectName("program_title") 43 | self.gridLayout.addWidget(self.program_title, 0, 1, 1, 1) 44 | self.logo = QtWidgets.QLabel(About) 45 | self.logo.setMinimumSize(QtCore.QSize(48, 48)) 46 | self.logo.setMaximumSize(QtCore.QSize(48, 48)) 47 | self.logo.setPixmap(QtGui.QPixmap(":/app.ico")) 48 | self.logo.setScaledContents(True) 49 | self.logo.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) 50 | self.logo.setObjectName("logo") 51 | self.gridLayout.addWidget(self.logo, 0, 0, 1, 1) 52 | self.description = QtWidgets.QLabel(About) 53 | self.description.setAlignment(QtCore.Qt.AlignmentFlag.AlignBottom|QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft) 54 | self.description.setOpenExternalLinks(True) 55 | self.description.setObjectName("description") 56 | self.gridLayout.addWidget(self.description, 2, 1, 1, 1) 57 | 58 | self.retranslateUi(About) 59 | QtCore.QMetaObject.connectSlotsByName(About) 60 | 61 | def retranslateUi(self, About): 62 | _translate = QtCore.QCoreApplication.translate 63 | About.setWindowTitle(_translate("About", "About...")) 64 | self.author.setText(_translate("About", "Made by Silv3r")) 65 | self.program_title.setText(_translate("About", "NoIntro Roms Downloader")) 66 | self.description.setText(_translate("About", "

This program was made to easy access console\'s roms directly from archive.org.
I will try to add more systems on time. You can access to the actual list on this page.

GitHub repository : github.com/silverlays/NoIntro-Roms-Downloader
GitHub wiki : github.com/silverlays/NoIntro-Roms-Downloader/wiki

Have fun :)

")) 67 | -------------------------------------------------------------------------------- /_tools.py: -------------------------------------------------------------------------------- 1 | # Qt 2 | from PyQt6.QtCore import * 3 | from PyQt6.QtGui import * 4 | from PyQt6.QtWidgets import * 5 | 6 | # Helpers 7 | from _constants import * 8 | from _platforms import PlatformsHelper 9 | from _settings import SettingsHelper 10 | from _debug import * 11 | 12 | 13 | class CacheGenerator(): 14 | class PlatformWorker(QObject): 15 | platform = [] 16 | finished = pyqtSignal(str) 17 | 18 | 19 | def __init__(self, platform: list, output_cache_json: dict): 20 | super().__init__(None) 21 | self.platform = platform 22 | self.output_cache_json = output_cache_json 23 | 24 | 25 | def run(self): 26 | if len(self.platform) == 3: # SINGLE PART 27 | self.id_name = self.platform[0] 28 | self.format = self.platform[1] 29 | self.parts = 1 30 | self.url = f"https://archive.org/details/{self.platform[2]}&output=json" 31 | self.output_cache_json[self.id_name] = {} 32 | DebugHelper.print(DebugType.TYPE_DEBUG, f"Processing <{self.id_name}>", "CACHE") 33 | self._ProcessPart(part_id=self.platform[2]) 34 | elif len(self.platform) == 4: # MULTI PART 35 | self.id_name = self.platform[0] 36 | self.format = self.platform[1] 37 | self.parts = self.platform[2] 38 | self.output_cache_json[self.id_name] = {} 39 | 40 | DebugHelper.print(DebugType.TYPE_DEBUG, f"Processing <{self.id_name}>", "CACHE") 41 | for i in range(1, self.parts+1): 42 | parts_id = str(self.platform[3]).replace('$$', str(i)) 43 | self.url = f"https://archive.org/details/{parts_id}&output=json" 44 | self._ProcessPart(part_id=parts_id, part_number=i) 45 | self.finished.emit(self.id_name) 46 | 47 | 48 | def _ProcessPart(self, part_id: str, part_number: int = 1): 49 | import requests, json 50 | try: 51 | content_request = requests.get(self.url).content 52 | content_json = json.loads(content_request) 53 | part_files = content_json['files'] 54 | for file in part_files: 55 | if str(file).find(self.format) != -1: 56 | output_file = { 57 | "source_id": part_id, 58 | "size": int(part_files[file]['size']), 59 | "md5": part_files[file]['md5'], 60 | "crc32": part_files[file]['crc32'], 61 | "sha1": part_files[file]['sha1'], 62 | "format": part_files[file]['format'], 63 | } 64 | self.output_cache_json[self.id_name][file[1:-(len(self.format)+1)]] = output_file 65 | except: pass 66 | 67 | 68 | app: QApplication = None 69 | parent: QSplashScreen = None 70 | output_cache_json = {} 71 | threads = [] 72 | workers = [] 73 | download_completed = 0 74 | 75 | 76 | def __init__(self, app: QApplication, parent: QSplashScreen) -> None: 77 | self.app = app 78 | self.parent = parent 79 | 80 | 81 | def run(self): 82 | import pickle 83 | 84 | # Create workers and run them in separate threads (for speed) 85 | [self.threads.append(QThread()) for _ in range(len(ARCHIVE_PLATFORMS_DATA))] 86 | 87 | for i in range(len(ARCHIVE_PLATFORMS_DATA)): 88 | self.workers.append(CacheGenerator.PlatformWorker(ARCHIVE_PLATFORMS_DATA[i], self.output_cache_json)) 89 | self.workers[i].moveToThread(self.threads[i]) 90 | self.threads[i].started.connect(self.workers[i].run) 91 | self.workers[i].finished.connect(self._updateMessage) 92 | self.workers[i].finished.connect(self.threads[i].quit) 93 | self.threads[i].finished.connect(self.threads[i].deleteLater) 94 | self.threads[i].start() 95 | 96 | # Wait until workers finished 97 | while self.download_completed != len(self.threads): self.app.processEvents() 98 | 99 | # Sort the data before writing 100 | temp_dict = sorted(self.output_cache_json) 101 | new_output_cache_json = {} 102 | for i in range(len(temp_dict)): 103 | temp_name = temp_dict[i] 104 | for j in range(len(self.output_cache_json)): 105 | output_name = list(self.output_cache_json)[j] 106 | if temp_name == output_name: 107 | new_output_cache_json[temp_name] = {} 108 | new_output_cache_json[temp_name] = self.output_cache_json[temp_name] 109 | self.output_cache_json = new_output_cache_json 110 | 111 | # And finally write to file 112 | with open("database_cache.dat", "wb") as fp: pickle.dump(self.output_cache_json, fp) 113 | 114 | 115 | def _updateMessage(self, platform_name: str): 116 | self.download_completed += 1 117 | self.parent.showMessage(f"({self.download_completed}/{len(self.threads)}) [{platform_name}] completed.", 118 | color=Qt.GlobalColor.white, 119 | alignment=(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignCenter) 120 | ) 121 | 122 | 123 | 124 | class RomDownload(): 125 | platform_name = "" 126 | rom_name = "" 127 | rom_url = "" 128 | rom_format = "" 129 | 130 | def __init__(self, settings: SettingsHelper, platforms: PlatformsHelper, platform: str, rom_index: int) -> None: 131 | import requests 132 | from urllib.parse import quote 133 | 134 | self.platform_name = platform 135 | self.rom_name = platforms.getRomName(platform, rom_index) 136 | self.rom_format = platforms.getRom(platform, self.rom_name)['format'] 137 | self.rom_url = f"https://archive.org/download/{platforms.getRom(platform, self.rom_name)['source_id']}/{quote(self.rom_name)}.{self.rom_format}" 138 | DebugHelper.print(DebugType.TYPE_INFO, f"Downloading [{self.platform_name}] {self.rom_name}", "downloader") 139 | with open(os.path.join(settings.get('download_path'), f"{self.rom_name}.{self.rom_format}"), "wb") as of: 140 | DebugHelper.print(DebugType.TYPE_DEBUG, f"Downloading from [{self.rom_url}]", "downloader") 141 | of.write(requests.get(self.rom_url).content) 142 | 143 | 144 | 145 | class Unzip(): 146 | def __init__(self, settings: SettingsHelper, filename: str) -> None: 147 | from py7zr import SevenZipFile 148 | path = settings.get('download_path') 149 | full_path = os.path.join(path, filename) 150 | DebugHelper.print(DebugType.TYPE_INFO, f"Unzipping [{full_path}]...", "unzip") 151 | SevenZipFile(full_path).extractall(path) 152 | os.remove(full_path) 153 | 154 | 155 | 156 | class Tools(): 157 | def convertSizeToReadable(size: int) -> str: 158 | if size < 1000: 159 | return '%i' % size + 'B' 160 | elif 1000 <= size < 1000000: 161 | return '%.1f' % float(size/1000) + ' KB' 162 | elif 1000000 <= size < 1000000000: 163 | return '%.1f' % float(size/1000000) + ' MB' 164 | elif 1000000000 <= size < 1000000000000: 165 | return '%.1f' % float(size/1000000000) + ' GB' 166 | elif 1000000000000 <= size: 167 | return '%.1f' % float(size/1000000000000) + ' TB' 168 | 169 | 170 | def isCacheValid(validity_days: int) -> bool: 171 | import os 172 | from datetime import datetime, timedelta 173 | 174 | cache_mdate = os.path.getmtime("database_cache.dat") 175 | cache_mdate = datetime.fromtimestamp(cache_mdate) 176 | today_date = datetime.today() 177 | expiration_date = cache_mdate + timedelta(days=validity_days) 178 | 179 | if expiration_date > today_date: return True 180 | else: return False 181 | -------------------------------------------------------------------------------- /ui/ui_Options.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'd:\Python Projects\NoIntro Roms Downloader\ui\Options.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.2 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_Dialog(object): 13 | def setupUi(self, Dialog): 14 | Dialog.setObjectName("Dialog") 15 | Dialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal) 16 | Dialog.resize(639, 234) 17 | icon = QtGui.QIcon() 18 | icon.addPixmap(QtGui.QPixmap(":/app.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) 19 | Dialog.setWindowIcon(icon) 20 | Dialog.setStyleSheet("") 21 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(Dialog) 22 | self.verticalLayout_2.setObjectName("verticalLayout_2") 23 | self.gbCacheAndDatabase = QtWidgets.QGroupBox(parent=Dialog) 24 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Preferred) 25 | sizePolicy.setHorizontalStretch(0) 26 | sizePolicy.setVerticalStretch(0) 27 | sizePolicy.setHeightForWidth(self.gbCacheAndDatabase.sizePolicy().hasHeightForWidth()) 28 | self.gbCacheAndDatabase.setSizePolicy(sizePolicy) 29 | self.gbCacheAndDatabase.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignTop) 30 | self.gbCacheAndDatabase.setObjectName("gbCacheAndDatabase") 31 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.gbCacheAndDatabase) 32 | self.horizontalLayout.setObjectName("horizontalLayout") 33 | self.label = QtWidgets.QLabel(parent=self.gbCacheAndDatabase) 34 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 35 | sizePolicy.setHorizontalStretch(0) 36 | sizePolicy.setVerticalStretch(0) 37 | sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth()) 38 | self.label.setSizePolicy(sizePolicy) 39 | self.label.setObjectName("label") 40 | self.horizontalLayout.addWidget(self.label) 41 | self.cb_cache_expiration = QtWidgets.QComboBox(parent=self.gbCacheAndDatabase) 42 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 43 | sizePolicy.setHorizontalStretch(0) 44 | sizePolicy.setVerticalStretch(0) 45 | sizePolicy.setHeightForWidth(self.cb_cache_expiration.sizePolicy().hasHeightForWidth()) 46 | self.cb_cache_expiration.setSizePolicy(sizePolicy) 47 | self.cb_cache_expiration.setObjectName("cb_cache_expiration") 48 | self.cb_cache_expiration.addItem("") 49 | self.cb_cache_expiration.addItem("") 50 | self.cb_cache_expiration.addItem("") 51 | self.cb_cache_expiration.addItem("") 52 | self.horizontalLayout.addWidget(self.cb_cache_expiration) 53 | self.label_2 = QtWidgets.QLabel(parent=self.gbCacheAndDatabase) 54 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum) 55 | sizePolicy.setHorizontalStretch(0) 56 | sizePolicy.setVerticalStretch(0) 57 | sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth()) 58 | self.label_2.setSizePolicy(sizePolicy) 59 | self.label_2.setAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignLeft|QtCore.Qt.AlignmentFlag.AlignVCenter) 60 | self.label_2.setWordWrap(True) 61 | self.label_2.setObjectName("label_2") 62 | self.horizontalLayout.addWidget(self.label_2) 63 | self.verticalLayout_2.addWidget(self.gbCacheAndDatabase) 64 | self.groupBox_3 = QtWidgets.QGroupBox(parent=Dialog) 65 | self.groupBox_3.setObjectName("groupBox_3") 66 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.groupBox_3) 67 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 68 | self.label_3 = QtWidgets.QLabel(parent=self.groupBox_3) 69 | self.label_3.setObjectName("label_3") 70 | self.horizontalLayout_2.addWidget(self.label_3) 71 | self.le_DownloadPath = QtWidgets.QLineEdit(parent=self.groupBox_3) 72 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.Minimum) 73 | sizePolicy.setHorizontalStretch(0) 74 | sizePolicy.setVerticalStretch(0) 75 | sizePolicy.setHeightForWidth(self.le_DownloadPath.sizePolicy().hasHeightForWidth()) 76 | self.le_DownloadPath.setSizePolicy(sizePolicy) 77 | self.le_DownloadPath.setObjectName("le_DownloadPath") 78 | self.horizontalLayout_2.addWidget(self.le_DownloadPath) 79 | self.pb_BrowsePath = QtWidgets.QPushButton(parent=self.groupBox_3) 80 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Minimum) 81 | sizePolicy.setHorizontalStretch(0) 82 | sizePolicy.setVerticalStretch(0) 83 | sizePolicy.setHeightForWidth(self.pb_BrowsePath.sizePolicy().hasHeightForWidth()) 84 | self.pb_BrowsePath.setSizePolicy(sizePolicy) 85 | self.pb_BrowsePath.setMaximumSize(QtCore.QSize(30, 16777215)) 86 | self.pb_BrowsePath.setObjectName("pb_BrowsePath") 87 | self.horizontalLayout_2.addWidget(self.pb_BrowsePath) 88 | self.verticalLayout_2.addWidget(self.groupBox_3) 89 | self.groupBox_2 = QtWidgets.QGroupBox(parent=Dialog) 90 | self.groupBox_2.setFlat(False) 91 | self.groupBox_2.setObjectName("groupBox_2") 92 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.groupBox_2) 93 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 94 | self.cb_unzip = QtWidgets.QCheckBox(parent=self.groupBox_2) 95 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 96 | sizePolicy.setHorizontalStretch(0) 97 | sizePolicy.setVerticalStretch(0) 98 | sizePolicy.setHeightForWidth(self.cb_unzip.sizePolicy().hasHeightForWidth()) 99 | self.cb_unzip.setSizePolicy(sizePolicy) 100 | self.cb_unzip.setObjectName("cb_unzip") 101 | self.horizontalLayout_3.addWidget(self.cb_unzip) 102 | self.cb_checkupdates = QtWidgets.QCheckBox(parent=self.groupBox_2) 103 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 104 | sizePolicy.setHorizontalStretch(0) 105 | sizePolicy.setVerticalStretch(0) 106 | sizePolicy.setHeightForWidth(self.cb_checkupdates.sizePolicy().hasHeightForWidth()) 107 | self.cb_checkupdates.setSizePolicy(sizePolicy) 108 | self.cb_checkupdates.setAutoFillBackground(True) 109 | self.cb_checkupdates.setObjectName("cb_checkupdates") 110 | self.horizontalLayout_3.addWidget(self.cb_checkupdates) 111 | self.verticalLayout_2.addWidget(self.groupBox_2) 112 | self.buttonBox = QtWidgets.QDialogButtonBox(parent=Dialog) 113 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) 114 | sizePolicy.setHorizontalStretch(0) 115 | sizePolicy.setVerticalStretch(0) 116 | sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth()) 117 | self.buttonBox.setSizePolicy(sizePolicy) 118 | self.buttonBox.setOrientation(QtCore.Qt.Orientation.Horizontal) 119 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.StandardButton.Cancel|QtWidgets.QDialogButtonBox.StandardButton.Ok) 120 | self.buttonBox.setObjectName("buttonBox") 121 | self.verticalLayout_2.addWidget(self.buttonBox, 0, QtCore.Qt.AlignmentFlag.AlignBottom) 122 | 123 | self.retranslateUi(Dialog) 124 | self.buttonBox.accepted.connect(Dialog.accept) # type: ignore 125 | self.buttonBox.rejected.connect(Dialog.reject) # type: ignore 126 | QtCore.QMetaObject.connectSlotsByName(Dialog) 127 | 128 | def retranslateUi(self, Dialog): 129 | _translate = QtCore.QCoreApplication.translate 130 | Dialog.setWindowTitle(_translate("Dialog", "Settings...")) 131 | self.gbCacheAndDatabase.setTitle(_translate("Dialog", "Cache && Database")) 132 | self.label.setText(_translate("Dialog", "Cache validity (in days)")) 133 | self.cb_cache_expiration.setPlaceholderText(_translate("Dialog", "")) 134 | self.cb_cache_expiration.setItemText(0, _translate("Dialog", "")) 135 | self.cb_cache_expiration.setItemText(1, _translate("Dialog", "30")) 136 | self.cb_cache_expiration.setItemText(2, _translate("Dialog", "60")) 137 | self.cb_cache_expiration.setItemText(3, _translate("Dialog", "90")) 138 | self.label_2.setText(_translate("Dialog", "

<none> will update the cache EVERY LAUNCH! (not recommanded)

")) 139 | self.groupBox_3.setTitle(_translate("Dialog", "GroupBox")) 140 | self.label_3.setText(_translate("Dialog", "Download path")) 141 | self.pb_BrowsePath.setText(_translate("Dialog", "...")) 142 | self.groupBox_2.setTitle(_translate("Dialog", "Miscellaneous")) 143 | self.cb_unzip.setText(_translate("Dialog", "Decompress after download")) 144 | self.cb_checkupdates.setText(_translate("Dialog", "Check updates at startup")) 145 | -------------------------------------------------------------------------------- /mainwindow.py: -------------------------------------------------------------------------------- 1 | from PyQt6.QtCore import * 2 | from PyQt6.QtGui import * 3 | from PyQt6.QtWidgets import * 4 | 5 | # Helpers 6 | from _settings import SettingsHelper 7 | from _updater import UpdaterHelper 8 | from _platforms import PlatformsHelper 9 | from _tools import Tools, RomDownload, Unzip 10 | from _debug import * 11 | 12 | # Ui 13 | from ui.ui_MainWindow import Ui_MainWindow as Ui 14 | from ui.ui_DownloadPane import Ui_DownloadPane 15 | 16 | # Dialogs 17 | from download_queue import DownloadQueue 18 | from options import Options 19 | from about import About 20 | 21 | 22 | 23 | class DownloadPane(QWidget, Ui_DownloadPane): 24 | """This class made the jonction between the «Download_Pane.ui» and the main window.""" 25 | def __init__(self, gb_parent: QGroupBox): 26 | super().__init__(gb_parent) 27 | self.setupUi(self) 28 | self.parent_gb_layout = QVBoxLayout() 29 | self.parent_gb_layout.addWidget(self) 30 | self.hide() 31 | 32 | 33 | 34 | class MainWindow(QMainWindow, Ui): 35 | """This is the main window class.""" 36 | def __init__(self, settings: SettingsHelper, updater: UpdaterHelper, platforms: PlatformsHelper): 37 | super().__init__() 38 | self.setupUi(self) 39 | 40 | # Setup local variables 41 | self.settings = settings 42 | self.updater = updater 43 | self.platforms = platforms 44 | self.optionsDialog = Options(self, settings) 45 | self.aboutDialog = About(self) 46 | self.download_pane = DownloadPane(self.gb_downloads) 47 | self.download_queue = DownloadQueue(self, self.platforms) 48 | 49 | # Setup form 50 | self.setWindowTitle(f"{self.windowTitle()} {self.updater.currentVersionString()}") 51 | self.tw_romsList.setColumnWidth(0, int(self.tw_romsList.width() / 2.42)) 52 | self.tw_romsList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) 53 | self.gb_downloads.setLayout(self.download_pane.parent_gb_layout) 54 | 55 | # Setup statusbar 56 | self.statusbar_update = QLabel() 57 | self.statusbar.addPermanentWidget(self.statusbar_update) 58 | self.statusbar_queue = QLabel() 59 | self.statusbar_queue.setMinimumWidth(150) 60 | self.statusbar_queue.mousePressEvent = self.download_queue.show 61 | self.statusbar.addWidget(self.statusbar_queue) 62 | 63 | # Setup download queue 64 | self.download_queue.updatedListEvent = self._updateStatusbarQueueText 65 | self.download_queue.downloadClickedEvent = self._launchRomsDownload 66 | 67 | # Setup menu events 68 | self.actionShowOptions.triggered.connect(lambda: self.optionsDialog.show()) 69 | self.actionExit.triggered.connect(lambda: self.close()) 70 | self.actionGet_help.triggered.connect(lambda: QDesktopServices.openUrl(QUrl('https://github.com/silverlays/NoIntro-Roms-Downloader/wiki/Getting-started'))) 71 | self.actionCheck_for_updates.triggered.connect(lambda: self._checkUpdates()) 72 | self.actionAbout.triggered.connect(self.aboutDialog.show) 73 | self.actionAbout_Qt.triggered.connect(lambda: QMessageBox.aboutQt(self, 'About Qt...')) 74 | 75 | # Setup other events 76 | self.lw_platforms.itemClicked.connect(self._onListwidgetSelectionChanged) 77 | self.tw_romsList.customContextMenuRequested.connect(self._onRomslistRightClick) 78 | self.le_filter.textChanged.connect(self._filterTableWidget) 79 | self.pb_eur.toggled.connect(self._filterTableWidget) 80 | self.pb_usa.toggled.connect(self._filterTableWidget) 81 | self.pb_jpn.toggled.connect(self._filterTableWidget) 82 | self.pb_all.toggled.connect(self._filterTableWidget) 83 | self.gb_downloads.clicked.connect(self._onDownloadPaneClicked) 84 | 85 | # Startup task(s) 86 | self._checkUpdates(at_launch=True) 87 | self._loadPlatformsList() 88 | 89 | 90 | def _checkUpdates(self, at_launch: bool = False): 91 | """Check for software update. 92 | 93 | Args: 94 | at_launch (bool, optional): 'True' if the application just started. Defaults to False. 95 | """ 96 | def Ask(): 97 | answer = QMessageBox.question(self, "Update available", f"An update is available!\n\nActual version: {self.updater.currentVersionString()}\nLastest version: {self.updater.lastestVersionString()}\n\nWould you like to update now ?") 98 | if answer == QMessageBox.StandardButton.Yes: 99 | QMessageBox.warning(self, 'Updating...', 'Not yet implemented, sorry.') 100 | ### 101 | ### TODO: IMPLEMENT SOFTWARE UPDATE 102 | ### 103 | else: 104 | self.statusbar_update.setText("New version available!") 105 | 106 | update_available = self.updater.updateAvailable() if self.settings.get('check_updates') else False 107 | 108 | if at_launch and self.settings.get('check_updates') and update_available: Ask() 109 | elif at_launch and self.settings.get('check_updates') and not update_available: self.statusbar_update.setText("You are up-to-date.") 110 | elif not at_launch and update_available: Ask() 111 | elif not at_launch and not update_available: 112 | QMessageBox.information(self, "Update", "You are up-to-date.") 113 | self.statusbar_update.setText("You are up-to-date.") 114 | 115 | 116 | def _loadPlatformsList(self): 117 | for i in range(self.platforms.platformsCount()): 118 | name = self.platforms.getPlatformName(i) 119 | item = QListWidgetItem(QIcon(':/app.ico'), name) 120 | self.lw_platforms.addItem(item) 121 | 122 | 123 | def _filterTableWidget(self): 124 | """Handle table filtering.""" 125 | # Made the RegEx 126 | keywords = self.le_filter.text() 127 | if self.pb_eur.isChecked(): keywords += " Europe" 128 | if self.pb_usa.isChecked(): keywords += " USA" 129 | if self.pb_jpn.isChecked(): keywords += " Japan" 130 | keywords = keywords.replace(' ', '.+') 131 | 132 | # Fill the table based on RegEx 133 | to_keep = [item for item in self.tw_romsList.findItems(keywords, Qt.MatchFlag.MatchRegularExpression)] 134 | for i in range(self.tw_romsList.rowCount()): 135 | self.tw_romsList.hideRow(i) if self.tw_romsList.item(i, 0) not in to_keep else self.tw_romsList.showRow(i) 136 | 137 | 138 | def _updateStatusbarQueueText(self): 139 | count = self.download_queue.getTotalCount() 140 | self.statusbar_queue.setText(f"{count} item(s) in queue") if count > 0 else self.statusbar_queue.setText("") 141 | 142 | 143 | def _addToQueue(self): 144 | self.download_queue.add(self.platforms.getPlatformName(self.lw_platforms.selectedIndexes()[0].row()), [row.row() for row in self.tw_romsList.selectedIndexes() if row.column() == 0]) 145 | self._updateStatusbarQueueText() 146 | 147 | 148 | def _downloadNowContextMenu(self): 149 | self._addToQueue() 150 | self._launchRomsDownload() 151 | 152 | 153 | def _launchRomsDownload(self): 154 | DebugHelper.print(DebugType.TYPE_INFO, "Download started...") 155 | self.gb_downloads.setChecked(True) 156 | self.download_pane.show() 157 | total_count = self.download_queue.getTotalCount() 158 | self.download_pane.pb_progress.setMaximum(total_count) 159 | self.download_pane.pb_progress.setValue(0) 160 | for platform in self.download_queue.queue_dict: 161 | for i in range(len(self.download_queue.queue_dict[platform])): 162 | rom_name = self.platforms.getRomName(platform, self.download_queue.queue_dict[platform][0]) 163 | rom_index = self.download_queue.queue_dict[platform][0] 164 | self.download_pane.l_job.setText(f"[{platform}] {rom_name}") 165 | self.download_pane.l_progress.setText(f"{i}/{total_count}") 166 | self.repaint() 167 | RomDownload(self.settings, self.platforms, platform, rom_index) 168 | if self.settings.get('unzip'): Unzip(self.settings, f"{rom_name}.{self.platforms.getRom(platform, rom_name)['format']}") 169 | self.download_queue.remove(platform, rom_index) 170 | self.download_pane.pb_progress.setValue(i+1) 171 | self.repaint() 172 | self.download_pane.l_job.setText("N/A") 173 | self.download_pane.l_progress.setText(f"{i+1}/{total_count}") 174 | 175 | 176 | def _onListwidgetSelectionChanged(self, item: QListWidgetItem): 177 | platform_name = item.text() 178 | 179 | # Clear the table 180 | self.tw_romsList.setRowCount(0) 181 | 182 | # Fill the table with new content 183 | for i in range(self.platforms.getRomsCount(platform_name)): 184 | rom_name = self.platforms.getRomName(platform_name, i) 185 | rom_data = self.platforms.getRom(platform_name, rom_name) 186 | 187 | # Creating Items for table's row 188 | rom_name_item = QTableWidgetItem(rom_name) 189 | rom_size_item = QTableWidgetItem(Tools.convertSizeToReadable(rom_data['size'])) 190 | rom_size_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 191 | rom_format_item = QTableWidgetItem(rom_data['format']) 192 | rom_format_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 193 | rom_md5_item = QTableWidgetItem(rom_data['md5']) 194 | rom_md5_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 195 | rom_crc32_item = QTableWidgetItem(rom_data['crc32'].upper()) 196 | rom_crc32_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 197 | rom_sha1_item = QTableWidgetItem(rom_data['sha1']) 198 | rom_sha1_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) 199 | 200 | self.tw_romsList.insertRow(i) 201 | self.tw_romsList.setItem(i, 0, rom_name_item) 202 | self.tw_romsList.setItem(i, 1, rom_size_item) 203 | self.tw_romsList.setItem(i, 2, rom_format_item) 204 | self.tw_romsList.setItem(i, 3, rom_md5_item) 205 | self.tw_romsList.setItem(i, 4, rom_crc32_item) 206 | self.tw_romsList.setItem(i, 5, rom_sha1_item) 207 | 208 | # Resize columns and sort 209 | self.tw_romsList.resizeColumnsToContents() 210 | #self.tw_romsList.sortByColumn(0, Qt.SortOrder.AscendingOrder) 211 | 212 | # Apply any previous filters 213 | self._filterTableWidget() 214 | 215 | 216 | def _onDownloadPaneClicked(self): 217 | if self.gb_downloads.isChecked(): 218 | self.download_pane.show() 219 | self.gb_downloads.setFixedHeight(100) 220 | else: 221 | self.download_pane.hide() 222 | self.gb_downloads.setFixedHeight(20) 223 | 224 | 225 | def _onRomslistRightClick(self, point: QPoint): 226 | menu = QMenu(self.tw_romsList) 227 | 228 | add_to_queue = QAction("Add to Queue") 229 | add_to_queue.triggered.connect(lambda: self._addToQueue()) 230 | 231 | download_now = QAction("Download Now") 232 | download_now.triggered.connect(lambda: self._downloadNowContextMenu()) 233 | 234 | menu.exec([add_to_queue, download_now], QCursor.pos(), parent=self.tw_romsList) 235 | -------------------------------------------------------------------------------- /ui/ui_MainWindow.py: -------------------------------------------------------------------------------- 1 | # Form implementation generated from reading ui file 'd:\Python Projects\NoIntro Roms Downloader (Ui version)\ui\MainWindow.ui' 2 | # 3 | # Created by: PyQt6 UI code generator 6.4.0 4 | # 5 | # WARNING: Any manual changes made to this file will be lost when pyuic6 is 6 | # run again. Do not edit this file unless you know what you are doing. 7 | 8 | 9 | from PyQt6 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_MainWindow(object): 13 | def setupUi(self, MainWindow): 14 | MainWindow.setObjectName("MainWindow") 15 | MainWindow.resize(1105, 600) 16 | icon = QtGui.QIcon() 17 | icon.addPixmap(QtGui.QPixmap(":/app.ico"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) 18 | MainWindow.setWindowIcon(icon) 19 | MainWindow.setStyleSheet("QListWidget::item { margin: 5px; }") 20 | self.centralwidget = QtWidgets.QWidget(MainWindow) 21 | self.centralwidget.setObjectName("centralwidget") 22 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget) 23 | self.horizontalLayout.setObjectName("horizontalLayout") 24 | self.scrollArea = QtWidgets.QScrollArea(self.centralwidget) 25 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Expanding) 26 | sizePolicy.setHorizontalStretch(0) 27 | sizePolicy.setVerticalStretch(0) 28 | sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) 29 | self.scrollArea.setSizePolicy(sizePolicy) 30 | self.scrollArea.setMinimumSize(QtCore.QSize(225, 0)) 31 | self.scrollArea.setWidgetResizable(True) 32 | self.scrollArea.setObjectName("scrollArea") 33 | self.scrollAreaWidgetContents = QtWidgets.QWidget() 34 | self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 258, 537)) 35 | self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") 36 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetContents) 37 | self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) 38 | self.verticalLayout_2.setSpacing(0) 39 | self.verticalLayout_2.setObjectName("verticalLayout_2") 40 | self.verticalLayout_left = QtWidgets.QVBoxLayout() 41 | self.verticalLayout_left.setObjectName("verticalLayout_left") 42 | self.lw_platforms = QtWidgets.QListWidget(self.scrollAreaWidgetContents) 43 | font = QtGui.QFont() 44 | font.setPointSize(10) 45 | font.setBold(True) 46 | self.lw_platforms.setFont(font) 47 | self.lw_platforms.setStyleSheet("padding: 5px;") 48 | self.lw_platforms.setIconSize(QtCore.QSize(32, 32)) 49 | self.lw_platforms.setObjectName("lw_platforms") 50 | self.verticalLayout_left.addWidget(self.lw_platforms) 51 | self.verticalLayout_2.addLayout(self.verticalLayout_left) 52 | self.scrollArea.setWidget(self.scrollAreaWidgetContents) 53 | self.horizontalLayout.addWidget(self.scrollArea) 54 | self.verticalLayout_right = QtWidgets.QVBoxLayout() 55 | self.verticalLayout_right.setObjectName("verticalLayout_right") 56 | self.horizontalLayout_filter = QtWidgets.QHBoxLayout() 57 | self.horizontalLayout_filter.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetMaximumSize) 58 | self.horizontalLayout_filter.setObjectName("horizontalLayout_filter") 59 | self.le_filter = QtWidgets.QLineEdit(self.centralwidget) 60 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Maximum) 61 | sizePolicy.setHorizontalStretch(0) 62 | sizePolicy.setVerticalStretch(0) 63 | sizePolicy.setHeightForWidth(self.le_filter.sizePolicy().hasHeightForWidth()) 64 | self.le_filter.setSizePolicy(sizePolicy) 65 | self.le_filter.setFrame(False) 66 | self.le_filter.setClearButtonEnabled(True) 67 | self.le_filter.setObjectName("le_filter") 68 | self.horizontalLayout_filter.addWidget(self.le_filter) 69 | self.gb_regions = QtWidgets.QGroupBox(self.centralwidget) 70 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Maximum, QtWidgets.QSizePolicy.Policy.Maximum) 71 | sizePolicy.setHorizontalStretch(0) 72 | sizePolicy.setVerticalStretch(0) 73 | sizePolicy.setHeightForWidth(self.gb_regions.sizePolicy().hasHeightForWidth()) 74 | self.gb_regions.setSizePolicy(sizePolicy) 75 | self.gb_regions.setFlat(True) 76 | self.gb_regions.setObjectName("gb_regions") 77 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.gb_regions) 78 | self.horizontalLayout_3.setContentsMargins(0, 5, 0, 0) 79 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 80 | self.pb_eur = QtWidgets.QPushButton(self.gb_regions) 81 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum) 82 | sizePolicy.setHorizontalStretch(0) 83 | sizePolicy.setVerticalStretch(0) 84 | sizePolicy.setHeightForWidth(self.pb_eur.sizePolicy().hasHeightForWidth()) 85 | self.pb_eur.setSizePolicy(sizePolicy) 86 | self.pb_eur.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 87 | self.pb_eur.setCheckable(True) 88 | self.pb_eur.setAutoExclusive(True) 89 | self.pb_eur.setFlat(True) 90 | self.pb_eur.setObjectName("pb_eur") 91 | self.horizontalLayout_3.addWidget(self.pb_eur) 92 | self.pb_usa = QtWidgets.QPushButton(self.gb_regions) 93 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum) 94 | sizePolicy.setHorizontalStretch(0) 95 | sizePolicy.setVerticalStretch(0) 96 | sizePolicy.setHeightForWidth(self.pb_usa.sizePolicy().hasHeightForWidth()) 97 | self.pb_usa.setSizePolicy(sizePolicy) 98 | self.pb_usa.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 99 | self.pb_usa.setCheckable(True) 100 | self.pb_usa.setAutoExclusive(True) 101 | self.pb_usa.setFlat(True) 102 | self.pb_usa.setObjectName("pb_usa") 103 | self.horizontalLayout_3.addWidget(self.pb_usa) 104 | self.pb_jpn = QtWidgets.QPushButton(self.gb_regions) 105 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum) 106 | sizePolicy.setHorizontalStretch(0) 107 | sizePolicy.setVerticalStretch(0) 108 | sizePolicy.setHeightForWidth(self.pb_jpn.sizePolicy().hasHeightForWidth()) 109 | self.pb_jpn.setSizePolicy(sizePolicy) 110 | self.pb_jpn.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 111 | self.pb_jpn.setCheckable(True) 112 | self.pb_jpn.setAutoExclusive(True) 113 | self.pb_jpn.setFlat(True) 114 | self.pb_jpn.setObjectName("pb_jpn") 115 | self.horizontalLayout_3.addWidget(self.pb_jpn) 116 | self.pb_all = QtWidgets.QPushButton(self.gb_regions) 117 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Maximum) 118 | sizePolicy.setHorizontalStretch(0) 119 | sizePolicy.setVerticalStretch(0) 120 | sizePolicy.setHeightForWidth(self.pb_all.sizePolicy().hasHeightForWidth()) 121 | self.pb_all.setSizePolicy(sizePolicy) 122 | self.pb_all.setCursor(QtGui.QCursor(QtCore.Qt.CursorShape.PointingHandCursor)) 123 | self.pb_all.setCheckable(True) 124 | self.pb_all.setChecked(True) 125 | self.pb_all.setAutoExclusive(True) 126 | self.pb_all.setFlat(True) 127 | self.pb_all.setObjectName("pb_all") 128 | self.horizontalLayout_3.addWidget(self.pb_all) 129 | self.horizontalLayout_filter.addWidget(self.gb_regions) 130 | self.verticalLayout_right.addLayout(self.horizontalLayout_filter) 131 | self.tw_romsList = QtWidgets.QTableWidget(self.centralwidget) 132 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.MinimumExpanding, QtWidgets.QSizePolicy.Policy.MinimumExpanding) 133 | sizePolicy.setHorizontalStretch(0) 134 | sizePolicy.setVerticalStretch(0) 135 | sizePolicy.setHeightForWidth(self.tw_romsList.sizePolicy().hasHeightForWidth()) 136 | self.tw_romsList.setSizePolicy(sizePolicy) 137 | self.tw_romsList.setMinimumSize(QtCore.QSize(800, 0)) 138 | self.tw_romsList.setMouseTracking(True) 139 | self.tw_romsList.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers) 140 | self.tw_romsList.setAlternatingRowColors(True) 141 | self.tw_romsList.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows) 142 | self.tw_romsList.setObjectName("tw_romsList") 143 | self.tw_romsList.setColumnCount(6) 144 | self.tw_romsList.setRowCount(0) 145 | item = QtWidgets.QTableWidgetItem() 146 | font = QtGui.QFont() 147 | font.setBold(True) 148 | item.setFont(font) 149 | self.tw_romsList.setHorizontalHeaderItem(0, item) 150 | item = QtWidgets.QTableWidgetItem() 151 | font = QtGui.QFont() 152 | font.setBold(True) 153 | item.setFont(font) 154 | self.tw_romsList.setHorizontalHeaderItem(1, item) 155 | item = QtWidgets.QTableWidgetItem() 156 | font = QtGui.QFont() 157 | font.setBold(True) 158 | item.setFont(font) 159 | self.tw_romsList.setHorizontalHeaderItem(2, item) 160 | item = QtWidgets.QTableWidgetItem() 161 | font = QtGui.QFont() 162 | font.setBold(True) 163 | item.setFont(font) 164 | self.tw_romsList.setHorizontalHeaderItem(3, item) 165 | item = QtWidgets.QTableWidgetItem() 166 | font = QtGui.QFont() 167 | font.setBold(True) 168 | item.setFont(font) 169 | self.tw_romsList.setHorizontalHeaderItem(4, item) 170 | item = QtWidgets.QTableWidgetItem() 171 | font = QtGui.QFont() 172 | font.setBold(True) 173 | item.setFont(font) 174 | self.tw_romsList.setHorizontalHeaderItem(5, item) 175 | self.tw_romsList.verticalHeader().setVisible(False) 176 | self.verticalLayout_right.addWidget(self.tw_romsList) 177 | self.gb_downloads = QtWidgets.QGroupBox(self.centralwidget) 178 | self.gb_downloads.setMinimumSize(QtCore.QSize(0, 20)) 179 | self.gb_downloads.setMaximumSize(QtCore.QSize(16777215, 100)) 180 | self.gb_downloads.setFlat(True) 181 | self.gb_downloads.setCheckable(True) 182 | self.gb_downloads.setChecked(False) 183 | self.gb_downloads.setObjectName("gb_downloads") 184 | self.verticalLayout_right.addWidget(self.gb_downloads) 185 | self.horizontalLayout.addLayout(self.verticalLayout_right) 186 | MainWindow.setCentralWidget(self.centralwidget) 187 | self.menubar = QtWidgets.QMenuBar(MainWindow) 188 | self.menubar.setGeometry(QtCore.QRect(0, 0, 1105, 22)) 189 | self.menubar.setObjectName("menubar") 190 | self.menuOptions = QtWidgets.QMenu(self.menubar) 191 | self.menuOptions.setObjectName("menuOptions") 192 | self.menuHelp = QtWidgets.QMenu(self.menubar) 193 | self.menuHelp.setObjectName("menuHelp") 194 | MainWindow.setMenuBar(self.menubar) 195 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 196 | self.statusbar.setObjectName("statusbar") 197 | MainWindow.setStatusBar(self.statusbar) 198 | self.actionShowOptions = QtGui.QAction(MainWindow) 199 | self.actionShowOptions.setObjectName("actionShowOptions") 200 | self.actionGet_help = QtGui.QAction(MainWindow) 201 | icon1 = QtGui.QIcon() 202 | icon1.addPixmap(QtGui.QPixmap("d:\\Python Projects\\NoIntro Roms Downloader (Ui version)\\ui\\../../.designer/backup"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) 203 | self.actionGet_help.setIcon(icon1) 204 | self.actionGet_help.setObjectName("actionGet_help") 205 | self.actionAbout = QtGui.QAction(MainWindow) 206 | self.actionAbout.setObjectName("actionAbout") 207 | self.actionAbout_Qt = QtGui.QAction(MainWindow) 208 | self.actionAbout_Qt.setObjectName("actionAbout_Qt") 209 | self.actionExit = QtGui.QAction(MainWindow) 210 | self.actionExit.setObjectName("actionExit") 211 | self.actionCheck_for_updates = QtGui.QAction(MainWindow) 212 | self.actionCheck_for_updates.setObjectName("actionCheck_for_updates") 213 | self.menuOptions.addAction(self.actionShowOptions) 214 | self.menuOptions.addSeparator() 215 | self.menuOptions.addAction(self.actionExit) 216 | self.menuHelp.addAction(self.actionGet_help) 217 | self.menuHelp.addSeparator() 218 | self.menuHelp.addAction(self.actionCheck_for_updates) 219 | self.menuHelp.addSeparator() 220 | self.menuHelp.addAction(self.actionAbout) 221 | self.menuHelp.addAction(self.actionAbout_Qt) 222 | self.menubar.addAction(self.menuOptions.menuAction()) 223 | self.menubar.addAction(self.menuHelp.menuAction()) 224 | 225 | self.retranslateUi(MainWindow) 226 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 227 | 228 | def retranslateUi(self, MainWindow): 229 | _translate = QtCore.QCoreApplication.translate 230 | MainWindow.setWindowTitle(_translate("MainWindow", "NoIntro Roms Downloader")) 231 | self.le_filter.setPlaceholderText(_translate("MainWindow", "Enter some keywords to filter the results")) 232 | self.gb_regions.setTitle(_translate("MainWindow", "Region shortcut")) 233 | self.pb_eur.setText(_translate("MainWindow", "EUR")) 234 | self.pb_usa.setText(_translate("MainWindow", "USA")) 235 | self.pb_jpn.setText(_translate("MainWindow", "JPN")) 236 | self.pb_all.setText(_translate("MainWindow", "All")) 237 | item = self.tw_romsList.horizontalHeaderItem(0) 238 | item.setText(_translate("MainWindow", "GAME")) 239 | item = self.tw_romsList.horizontalHeaderItem(1) 240 | item.setText(_translate("MainWindow", "SIZE")) 241 | item = self.tw_romsList.horizontalHeaderItem(2) 242 | item.setText(_translate("MainWindow", "FORMAT")) 243 | item = self.tw_romsList.horizontalHeaderItem(3) 244 | item.setText(_translate("MainWindow", "MD5")) 245 | item = self.tw_romsList.horizontalHeaderItem(4) 246 | item.setText(_translate("MainWindow", "CRC32")) 247 | item = self.tw_romsList.horizontalHeaderItem(5) 248 | item.setText(_translate("MainWindow", "SHA1")) 249 | self.gb_downloads.setTitle(_translate("MainWindow", "Downloads (click to expand/shrink)")) 250 | self.menuOptions.setTitle(_translate("MainWindow", "Options")) 251 | self.menuHelp.setTitle(_translate("MainWindow", "Help")) 252 | self.actionShowOptions.setText(_translate("MainWindow", "Settings")) 253 | self.actionShowOptions.setToolTip(_translate("MainWindow", "Settings")) 254 | self.actionShowOptions.setShortcut(_translate("MainWindow", "F2")) 255 | self.actionGet_help.setText(_translate("MainWindow", "Online help...")) 256 | self.actionGet_help.setShortcut(_translate("MainWindow", "F1")) 257 | self.actionAbout.setText(_translate("MainWindow", "About...")) 258 | self.actionAbout_Qt.setText(_translate("MainWindow", "About Qt...")) 259 | self.actionExit.setText(_translate("MainWindow", "Exit")) 260 | self.actionCheck_for_updates.setText(_translate("MainWindow", "Check for updates...")) 261 | --------------------------------------------------------------------------------