├── Backup └── .gitkeep ├── Data ├── Cache │ ├── Groups │ │ └── Backup │ │ │ └── .gitkeep │ └── Icons │ │ └── metadata │ │ ├── avatar │ │ └── .gitkeep │ │ ├── game │ │ └── .gitkeep │ │ └── system apps │ │ ├── .gitkeep │ │ └── Database.json ├── BIN │ └── texconv.exe ├── Preference │ ├── 2kbg.@OfficialAhmed0 │ ├── 4kbg.@OfficialAhmed0 │ ├── HDbg.@OfficialAhmed0 │ ├── SDbg.@OfficialAhmed0 │ ├── ic1.@OfficialAhmed0 │ ├── Black.@OfficialAhmed0 │ ├── MainBG.@OfficialAhmed0 │ ├── White.@OfficialAhmed0 │ ├── error.@OfficialAhmed0 │ ├── previewTest.@OfficialAhmed0 │ └── bgImageChange.@OfficialAhmed0 └── Language │ ├── English.json │ ├── Arabic.json │ ├── German.json │ └── Spanish.json ├── icon └── icon.ico ├── requirements.txt ├── Interface ├── view │ ├── icons_screen.jpg │ └── main_screen.jpg ├── Default_icons.py ├── Custom_group.py ├── Upload.py ├── Mask.py ├── Settings.py └── Iconit.py ├── main.py ├── .gitignore ├── Module ├── Widget │ ├── Translate.py │ └── Shared.py ├── Custom_group.py ├── Default_icons.py ├── Constant │ ├── Constants.py │ └── Html.py ├── Alerts.py ├── Database │ └── Generate.py ├── Settings.py ├── Multi_upload.py ├── Icons.py ├── Mask.py ├── Upload.py └── Avatars.py ├── LICENSE ├── Common └── Uploader.py ├── README.md └── environment.py /Backup/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Data/Cache/Groups/Backup/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Data/Cache/Icons/metadata/avatar/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Data/Cache/Icons/metadata/game/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Data/Cache/Icons/metadata/system apps/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/icon/icon.ico -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/requirements.txt -------------------------------------------------------------------------------- /Data/BIN/texconv.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/BIN/texconv.exe -------------------------------------------------------------------------------- /Interface/view/icons_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Interface/view/icons_screen.jpg -------------------------------------------------------------------------------- /Interface/view/main_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Interface/view/main_screen.jpg -------------------------------------------------------------------------------- /Data/Preference/2kbg.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/2kbg.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/4kbg.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/4kbg.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/HDbg.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/HDbg.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/SDbg.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/SDbg.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/ic1.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/ic1.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/Black.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/Black.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/MainBG.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/MainBG.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/White.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/White.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/error.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/error.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/previewTest.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/previewTest.@OfficialAhmed0 -------------------------------------------------------------------------------- /Data/Preference/bgImageChange.@OfficialAhmed0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/HEAD/Data/Preference/bgImageChange.@OfficialAhmed0 -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import Interface.Iconit as Iconit 3 | from environment import Common 4 | from PyQt5 import QtWidgets 5 | 6 | if __name__ == "__main__": 7 | app = QtWidgets.QApplication(sys.argv) 8 | screen_res = app.desktop().screenGeometry() 9 | env = Common() 10 | env.set_screen_size(screen_res.width(), screen_res.height()) 11 | 12 | window = QtWidgets.QMainWindow() 13 | iconit_window = Iconit.Ui() 14 | iconit_window.setupUi(window) 15 | window.show() 16 | 17 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | 3 | Data/Pref/pref.ini 4 | 5 | *.dds 6 | *.png 7 | Performance.txt 8 | GAMES MADE CACHING SLOWER.txt 9 | 10 | upload performance.log 11 | Interface/New web-based UI.zip 12 | verrsrc.h 13 | 14 | Data/New web-based UI.zip 15 | pass.json 16 | Data/Cache/Icons/metadata/game/ignored.json 17 | Data/Cache/Icons/metadata/system/ignored.json 18 | Data/Cache/Icons/metadata/system apps/ignored.json 19 | Data/Cache/Icons/metadata/system apps/system_apps.json 20 | 21 | Data/Cache/Groups 22 | Data/Cache 23 | Logs.txt 24 | output 25 | dist 26 | test 27 | env 28 | GUI 29 | output 30 | .vs 31 | .vscode 32 | *.zip 33 | test.py 34 | -------------------------------------------------------------------------------- /Module/Widget/Translate.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class Translate: 4 | def __init__(self, lang_path) -> None: 5 | self.language_path = lang_path 6 | 7 | 8 | def get_supported_languages(self) -> dict: 9 | """ Fetch supported languages from the English json """ 10 | 11 | with open(f"{self.language_path}English.json") as file: 12 | return json.load(file).get("Languages") 13 | 14 | 15 | def get_translation(self, lang:str, data_to_fetch:str) -> dict: 16 | """ Get info from tranlated json files. (lang):represent the json name to fetch data from """ 17 | 18 | file = open(f"{self.language_path}{lang}.json", encoding="utf-8") 19 | return json.load(file).get(data_to_fetch) 20 | 21 | -------------------------------------------------------------------------------- /Data/Cache/Icons/metadata/system apps/Database.json: -------------------------------------------------------------------------------- 1 | { 2 | "NPXS20132": "Disc™", 3 | "NPXS20111": "Library", 4 | "NPXS21021": "PS5 Share", 5 | "NPXS20108": "What's New", 6 | "NPXS20110": "TV & Video", 7 | "NPXS20118": "Share Play", 8 | "CUSA00219": "Destiny DEMO", 9 | "CUSA02012": "Media Player", 10 | "NPXS20109": "Blu-ray Disc™", 11 | "NPXS20112": "Blu-ray Disc™", 12 | "CUSA00572": "SHAREfactory™", 13 | "NPXS20114": "PlayStation™Now", 14 | "NPXS20104": "Capture Gallery", 15 | "CUSA01697": "PlayStation™Now", 16 | "NPXS20106": "Music Unlimited", 17 | "NPXS20133": "FOLDER THUMBNAIL", 18 | "NPXS20117": "USB Music Player", 19 | "CUSA00960": "PlayStation™ Vue", 20 | "NPXS20102": "Internet Browser", 21 | "NPXS20107": "PlayStation™Video", 22 | "NPXS20120": "PlayStation™Video", 23 | "NPXS20979": "PlayStation STORE", 24 | "CUSA01780": "PlayStation™Spotify", 25 | "NPXS20105": "Live from PlayStation", 26 | "CUSA00568": "Destiny The taken king", 27 | "CUSA01000": "Destiny The taken king" 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is licensed under the (MIT) License. 2 | 3 | Copyright (c) 2019-2023 Officialahmed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." 10 | -------------------------------------------------------------------------------- /Common/Uploader.py: -------------------------------------------------------------------------------- 1 | """ 2 | Class for common methods for uploading 3 | """ 4 | import shutil, os 5 | from ftplib import FTP 6 | from environment import Common 7 | 8 | 9 | class Main(Common): 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | self.ftp:FTP = self.get_ftp() 15 | self.game_ids:dict = self.get_ids() 16 | 17 | 18 | def update_local_icon(self, icon_path, game_id): 19 | """ 20 | Move icon to local view path to update the iconit view screen 21 | """ 22 | 23 | shutil.move(icon_path, 24 | f"{self.metadata_path}{self.selected_mode}\\{game_id}.png" 25 | ) 26 | 27 | 28 | def png_to_dds(self, png_dir: str, output_dir: str) -> None: 29 | """ 30 | DDS conversion with DXT1 compression using 31 | Microsoft corp. (texconv) LICENSED software under MIT license 32 | """ 33 | 34 | os.system(f"Data\\BIN\\texconv -f BC1_UNORM {png_dir} -o {output_dir} -y") 35 | 36 | 37 | def upload_to_server(self, icon_to_upload:str, icon_name:str) -> bool: 38 | """ 39 | Send icon (png/dds) to PS4 through FTP 'STOR' COMMAND 40 | NOTE: current ftp dir should be the game folder i.e CUSAxxxxx 41 | """ 42 | 43 | try: 44 | with open(icon_to_upload, "rb") as binary_data: 45 | self.ftp.storbinary(f"STOR {icon_name}", binary_data, 1024) 46 | return True 47 | 48 | except: return False -------------------------------------------------------------------------------- /Module/Custom_group.py: -------------------------------------------------------------------------------- 1 | from environment import Common 2 | import json 3 | 4 | class Main(Common): 5 | 6 | def __init__(self) -> None: 7 | super().__init__() 8 | 9 | 10 | def save_group(self) -> bool: 11 | """ 12 | Save selected titles as JSON file 13 | """ 14 | 15 | group = {} 16 | group_title = self.group_title_input.text() 17 | 18 | if group_title: 19 | 20 | err = 'Cannot use "default" as group name' 21 | 22 | if group_title.lower() == "default" or group_title == err: 23 | 24 | # Avoid overwriting the default file 25 | self.group_title_input.setText(err) 26 | 27 | else: 28 | 29 | for checkbox in self.checkboxes: 30 | 31 | if checkbox.isChecked(): 32 | 33 | text = checkbox.text() 34 | 35 | id = text[1:10] 36 | title = text[12:] 37 | 38 | group[id] = title 39 | 40 | 41 | with open(f"{self.groups_path}{group_title}.json", 'w+') as file: 42 | file.write(json.dumps(group)) 43 | 44 | self.window.close() 45 | 46 | return True 47 | 48 | return False 49 | 50 | 51 | def select_all(self): 52 | 53 | for checkbox in self.checkboxes: 54 | checkbox.setChecked(True) 55 | 56 | 57 | def deselect_all(self): 58 | 59 | for checkbox in self.checkboxes: 60 | checkbox.setChecked(False) 61 | -------------------------------------------------------------------------------- /Module/Widget/Shared.py: -------------------------------------------------------------------------------- 1 | class Widget: 2 | """ Sharable pointers for ui widgets, to access them anywhere """ 3 | ip_label = None 4 | ip_input = None 5 | CacheBar = None 6 | port_input = None 7 | port_label = None 8 | mode_label = None 9 | cache_label = None 10 | StatusLabel = None 11 | GameIconsRadio = None 12 | 13 | def __init__(self) -> None: 14 | pass 15 | 16 | def set_cache_bar(self, ptr): 17 | Widget.CacheBar = ptr 18 | 19 | def get_cache_bar(self): 20 | return Widget.CacheBar 21 | 22 | def set_ip_label(self, ptr): 23 | Widget.ip_label = ptr 24 | 25 | def get_ip_label(self): 26 | return Widget.ip_label 27 | 28 | def set_port_label(self, ptr): 29 | Widget.port_label = ptr 30 | 31 | def get_port_label(self): 32 | return Widget.port_label 33 | 34 | def set_mode_label(self, ptr): 35 | Widget.mode_label = ptr 36 | 37 | def get_mode_label(self): 38 | return Widget.mode_label 39 | 40 | def set_cache_label(self, ptr): 41 | Widget.cache_label = ptr 42 | 43 | def get_cache_label(self): 44 | return Widget.cache_label 45 | 46 | def set_ip_input(self, ptr): 47 | Widget.ip_input = ptr 48 | 49 | def get_ip_input(self): 50 | return Widget.ip_input 51 | 52 | def set_port_input(self, ptr): 53 | Widget.port_input = ptr 54 | 55 | def get_port_input(self): 56 | return Widget.port_input 57 | 58 | def set_status_label(self, ptr): 59 | Widget.StatusLabel = ptr 60 | 61 | def get_status_label(self): 62 | return Widget.StatusLabel 63 | 64 | def set_game_icon_radio(self, ptr): 65 | Widget.GameIconsRadio = ptr 66 | 67 | def get_game_icon_radio(self): 68 | return Widget.GameIconsRadio -------------------------------------------------------------------------------- /Module/Default_icons.py: -------------------------------------------------------------------------------- 1 | import os, json 2 | from environment import Common 3 | 4 | from PyQt5 import QtWidgets 5 | 6 | class Main(Common): 7 | def __init__(self) -> None: 8 | super().__init__() 9 | 10 | 11 | def is_default_set(self) -> bool: 12 | """ Check Default titles """ 13 | 14 | if os.path.isfile(self.default_group_file): 15 | return True 16 | return False 17 | 18 | 19 | def set_default( 20 | self, 21 | custom_group_btn_obj:QtWidgets.QPushButton, 22 | default_group_btn_obj:QtWidgets.QPushButton, 23 | new_icons_number:QtWidgets.QLCDNumber 24 | ): 25 | 26 | try: 27 | ids: dict = self.get_ids() 28 | 29 | # Overwrite default JSON with the new ids 30 | with open(self.default_group_file, 'w+') as file: 31 | file.write(json.dumps(ids)) 32 | custom_group_btn_obj.setEnabled(True) 33 | 34 | icons_src = self.mode['game'].get('cache path') 35 | icons_dest = self.groups_backup_path 36 | 37 | # Backup all icons don't exist in the backup folder 38 | self.backup_icons(icons_src, icons_dest, ids.keys()) 39 | self.calc_new_titles(custom_group_btn_obj, new_icons_number) 40 | default_group_btn_obj.setDisabled(True) 41 | 42 | except Exception as e: 43 | self.log_to_external_file('Cannot make default file', str(e)) 44 | 45 | 46 | def calc_new_titles( 47 | self, 48 | custom_group_btn_obj:QtWidgets.QPushButton, 49 | new_icons_number:QtWidgets.QLCDNumber 50 | ): 51 | 52 | total_ids = len(self.get_ids()) 53 | total_default_ids = 0 54 | 55 | if self.is_default_set(): 56 | 57 | custom_group_btn_obj.setEnabled(True) 58 | total_default_ids = len(self.read_json(self.default_group_file)) 59 | 60 | new_titles = total_ids - total_default_ids 61 | new_icons_number.setProperty("intValue", new_titles) -------------------------------------------------------------------------------- /Module/Constant/Constants.py: -------------------------------------------------------------------------------- 1 | """ Read-only class """ 2 | 3 | class Constants: 4 | # ___________ Private attrs _______________# 5 | 6 | __PS4_SYS_SCE = "sce_sys" 7 | __PS4_ICON_SIZE = (512, 512) 8 | __PS4_PIC_SIZE = (1920, 1080) 9 | __PS4_4k_ICON_SIZE = (660, 660) 10 | __PS4_INT_ICONS = "user/appmeta/" 11 | __PS4_SYS_ICONS = "system_ex/app/" 12 | __PS4_EXT_ICONS = "user/appmeta/external/" 13 | __PS4_PRONOUNCIATION_FILE = "pronunciation.xml" 14 | 15 | __ICONS_BACKUP_NAME = "Backup" 16 | __ICON_SUPPORTED_FORMATS = "Image (*.png *.jpg *.jpeg)" 17 | 18 | __HASH_THEME_COLOR = { 19 | "red":"#e83c3c", 20 | "green":"#55ff00", 21 | "white":"#ffffff", 22 | "orange":"#ffaa00", 23 | } 24 | 25 | def __init__(self) -> None: 26 | # init the self parameter 27 | pass 28 | 29 | def __setattr__(self, __name: str, __value: any) -> None: 30 | raise AttributeError(f"READ-ONLY: Class 'Constant' allow getters only. Occured while trying to set [{__name}]") 31 | 32 | def get_color(self, str_color:str) -> str: 33 | return Constants.__HASH_THEME_COLOR[str_color] 34 | 35 | def get_ps4_pic_size(self) -> tuple: 36 | return Constants.__PS4_PIC_SIZE 37 | 38 | def get_ps4_icon_size(self) -> tuple: 39 | return Constants.__PS4_ICON_SIZE 40 | 41 | def get_ps4_4k_icon_size(self) -> tuple: 42 | return Constants.__PS4_4k_ICON_SIZE 43 | 44 | def get_sce_folder_name(self) -> str: 45 | return Constants.__PS4_SYS_SCE 46 | 47 | def get_system_icons_path(self) -> str: 48 | return Constants.__PS4_SYS_ICONS 49 | 50 | def get_backup_folder_name(self) -> str: 51 | return Constants.__ICONS_BACKUP_NAME 52 | 53 | def get_internal_icons_path(self) -> str: 54 | return Constants.__PS4_INT_ICONS 55 | 56 | def get_external_icons_path(self) -> str: 57 | return Constants.__PS4_EXT_ICONS 58 | 59 | def get_icon_supported_format(self) -> str: 60 | return Constants.__ICON_SUPPORTED_FORMATS 61 | 62 | def get_pronunciation_file_path(self) -> str: 63 | return Constants.__PS4_PRONOUNCIATION_FILE -------------------------------------------------------------------------------- /Module/Constant/Html.py: -------------------------------------------------------------------------------- 1 | from Module.Constant.Constants import Constants as Constant 2 | 3 | class Html: 4 | def __init__(self) -> None: 5 | self.__start = "" 6 | self.__end = "" 7 | self.__constant = Constant() 8 | self.color = { 9 | "error":self.__constant.get_color("red"), 10 | "warning":self.__constant.get_color("orange"), 11 | "success":self.__constant.get_color("green"), 12 | "ps4":self.__constant.get_color("white") 13 | } 14 | 15 | 16 | def p_tag(self, cstm_style, txt=None) -> str: 17 | """ generate Paragraph """ 18 | return f'

{txt}

' 19 | 20 | 21 | def a_tag(self, href:str, txt:str, color:str, size:int, cstm_style: str = "", align: str = "center", font="Arial") -> str: 22 | """ generate Link """ 23 | return f'{self.__start}

{txt}{self.__end}' 24 | 25 | 26 | def span_tag(self, txt:str, color:str, size:int, align:str = "center", weight = 700, font="Arial") -> str: 27 | """ generate Text """ 28 | return f'{self.__start}

{txt}{self.__end}' 29 | 30 | 31 | def tooltip_tag(self, txt:str) -> str: 32 | return f"

{txt}

" 33 | 34 | 35 | def bg_image(self, url:str) -> str: 36 | return f"background-image: url({url});" 37 | 38 | 39 | def border_image(self, url:str) -> str: 40 | return f"border-image: url({url});" 41 | 42 | 43 | def internal_log_msg(self, state, msg, size=10, custome_style="") -> str: 44 | """ generate a logging line as

tag""" 45 | 46 | style = f"margin: 0px; -qt-block-indent:0; text-indent:0px; font-size:{size}pt; color:{self.color.get(state)}; {custome_style}" 47 | return self.p_tag(style, f"[{state.upper()}] : {msg}") 48 | 49 | 50 | def caution_p_tag(self, clr): 51 | return f"

" 52 | 53 | def caution_msg_2(self, clr): 54 | return f"

" -------------------------------------------------------------------------------- /Module/Alerts.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QMessageBox 2 | 3 | class Main: 4 | 5 | def __init__(self, translation:str, language:str) -> None: 6 | super().__init__() 7 | 8 | self.translated_content: dict = translation.get_translation(language, "Alerts") 9 | 10 | 11 | def display(self, window_title:str, description:str) -> None: 12 | """ 13 | Display a widget(window) as an alert with useful information of an issue 14 | """ 15 | 16 | msg = "" 17 | 18 | match description: 19 | 20 | case "About": 21 | msg = f"{self.translated_content.get('About')}.\n\n{self.translated_content.get('Thanks')}" 22 | 23 | case "db success": 24 | msg = self.translated_content.get("DbSuccess") 25 | 26 | case "CUSTOMspecial_thanks": 27 | msg = f"{self.translated_content.get('SpecialThanks')}:-\n@Lapy05575948\n@DefaultDNB\n\nTesters:-\n@laz305\n@maxtinion\n@_deejay87_\n@PS__TRICKS\n\n{self.translated_content.get('ThanksAll')}" 28 | 29 | case "CUSTOMdoneRmvCache": 30 | msg = self.translated_content.get("CacheRemoved") 31 | 32 | case "PermissionDenied": 33 | msg = self.translated_content.get("PermissionErr") 34 | 35 | case "Invalid": 36 | msg = f"{self.translated_content.get('InvalidCred')}." 37 | 38 | case "IconIssue": 39 | msg = f"{self.translated_content.get('InvalidCred')}." 40 | 41 | case "icon size": 42 | msg = f"{self.translated_content.get('SmallSize')} (440x440)." 43 | 44 | case "disconnected": 45 | msg = f"{self.translated_content.get('Disconnected')}." 46 | 47 | case "invalid_ip_port": 48 | msg = f"{self.translated_content.get('InvalidInput')}." 49 | 50 | case "are_you_sure": 51 | msg = f"{self.translated_content.get('IsPS4')}." 52 | 53 | case "timeout": 54 | msg = f"{self.translated_content.get('TimeOut')}." 55 | 56 | case "connection_refused": 57 | msg = f"{self.translated_content.get('TimeOut')}." 58 | 59 | case "incompleteProcess": 60 | msg = f"{self.translated_content.get('IncompleteProcess')}." 61 | 62 | case _: 63 | msg = f"{description}." 64 | 65 | QMessageBox.warning(None, f"{window_title.upper()}", f"{msg}") 66 | 67 | 68 | def alert(self, title:str, message:str, question=True) -> bool: 69 | """ 70 | Render message box window for confirmation/Information 71 | """ 72 | 73 | msg_window = QMessageBox() 74 | 75 | icon = QMessageBox.Question 76 | buttons = QMessageBox.Yes | QMessageBox.No 77 | default_btn = QMessageBox.No 78 | 79 | if not question: 80 | icon = QMessageBox.Information 81 | buttons = QMessageBox.Yes 82 | default_btn = QMessageBox.Yes 83 | 84 | msg_window.setIcon(icon) 85 | msg_window.setWindowTitle(title) 86 | msg_window.setText(message) 87 | msg_window.setStandardButtons(buttons) 88 | msg_window.setDefaultButton(default_btn) 89 | 90 | if msg_window.exec_() == QMessageBox.Yes: 91 | return True 92 | 93 | return False 94 | -------------------------------------------------------------------------------- /Module/Database/Generate.py: -------------------------------------------------------------------------------- 1 | """ 2 | Game_database 3 | Fetch game titles and ids from 4 | https://github.com/DEFAULTDNB/DEFAULTDNB.github.io 5 | and store them locally as JSON 6 | 7 | System_database 8 | Fetch apps info from online repo 9 | 10 | and store them locally as JSON 11 | """ 12 | import requests, json 13 | 14 | 15 | class Game_Database: 16 | def __init__(self, db_file) -> None: 17 | self.db_file = db_file 18 | self.lines = "" 19 | self.games = {} 20 | self.raw_data = "https://raw.githubusercontent.com/DEFAULTDNB/DEFAULTDNB.github.io/master/ps4date.db" 21 | 22 | 23 | def fetch_game_title(self, game_id) -> tuple: 24 | """ return the local data """ 25 | result = None 26 | try: 27 | if not self.games: 28 | self.games = json.load(open(self.db_file)) 29 | 30 | if game_id in self.games: 31 | result = (True, self.games.get(game_id)) 32 | else: 33 | result = (False, f"{game_id} not found in database. It'll be fetched from PS4") 34 | 35 | except FileNotFoundError: 36 | result = (False, "Fetching data from console is going to take longer. Please consider downoading the database from the settings | FileNotFoundError") 37 | except Exception as e: 38 | result = (False, f"Couldn't read database! fetching process will be slower. Consider redownloading the database from the settings | {str(e)}") 39 | finally: 40 | return result 41 | 42 | 43 | def save(self) -> tuple: 44 | fetched_data = self.fetch_data() 45 | if fetched_data[0] == True: 46 | try: 47 | with open(self.db_file, "w+") as file: 48 | json.dump(self.get_game_id_and_title(), file) 49 | except: pass 50 | return fetched_data 51 | 52 | 53 | def fetch_data(self) -> tuple: 54 | try: 55 | self.lines = requests.get(self.raw_data).text.split("\n") 56 | return (True, ) 57 | except requests.ConnectionError: 58 | return (False, "ConnectionError| Internet connection failed") 59 | except Exception as e: 60 | return (False, f"Couldn't update database| {e}") 61 | 62 | 63 | def find_game_ids(self, line) -> list: 64 | """ return indices of CUSA/s in a line """ 65 | return [idx for idx, _ in enumerate(line) if line[idx:idx+4] == "CUSA"] 66 | 67 | 68 | def get_game_id_and_title(self) -> dict: 69 | data = {} 70 | for line in self.lines: 71 | title = line[ : line.find("(")-1] 72 | 73 | for cusa_index in self.find_game_ids(line): 74 | cusa = line[cusa_index : cusa_index+9] 75 | if cusa: 76 | data[cusa] = title 77 | return data 78 | 79 | 80 | class System_Database: 81 | def __init__(self, db_file) -> None: 82 | self.db_file = db_file 83 | self.raw_data = "https://raw.githubusercontent.com/OfficialAhmed/Iconit-PS4/master/Data/Cache/Icons/metadata/system%20apps/Database.json" 84 | self.database = self.read_database() 85 | 86 | 87 | def read_database(self) -> dict: 88 | """ fetch data from json """ 89 | 90 | db = {} 91 | try: db = json.load(open(self.db_file, encoding="utf-8")) 92 | except: pass 93 | return db 94 | 95 | 96 | def save(self) -> tuple: 97 | fetched_data = self.fetch_online_data() 98 | if fetched_data[0] == True: 99 | try: 100 | with open(self.db_file, "w+", encoding="utf-8") as file: 101 | file.write(self.database_txt) 102 | except: pass 103 | return fetched_data 104 | 105 | 106 | def fetch_online_data(self) -> tuple: 107 | try: 108 | self.database_txt:str = requests.get(self.raw_data).text 109 | if "404" in self.database_txt[:5]: 110 | return (False, "DatabaseLinkError| Database cannot be find from the given link") 111 | return (True, ) 112 | except requests.ConnectionError: 113 | return (False, "ConnectionError| Internet connection failed") 114 | except Exception as e: 115 | return (False, f"Couldn't update database| {e}") 116 | 117 | 118 | def get_id(self, app_id) -> str: 119 | """ Try to get title from db, otherwise from PS4 """ 120 | 121 | if self.database.get(app_id) != None: 122 | return self.database.get(app_id) 123 | else: 124 | return "" 125 | -------------------------------------------------------------------------------- /Module/Settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | The only class from Module that doest inherit from (Common) 3 | So we can call the methods from Common without polymorphism 4 | 5 | * local attributes can be called without inheritence 6 | * init custom method called only once to change class's local attr for any obj access 7 | """ 8 | 9 | from PyQt5 import QtWidgets 10 | from PyQt5.QtWidgets import QFileDialog 11 | import json 12 | 13 | class Main: 14 | paths: dict = {} 15 | DEFAULT_SETTINGS: dict = {} 16 | local_settings_cache: dict = {} 17 | 18 | def __init__(self) -> None: 19 | self.init() 20 | 21 | 22 | def init(self, cache_path="", lang_path="", data = {}, is_for_local_attr=False) -> None: 23 | """ fetch paths from environment class. [Called once by environment on init only] """ 24 | 25 | # sharable attr for other objects (child class) 26 | if is_for_local_attr: 27 | Main.paths["language_path"] = lang_path 28 | Main.paths["cache_path"] = cache_path 29 | Main.local_settings_cache = data 30 | Main.DEFAULT_SETTINGS = data 31 | 32 | self.path = Main.paths 33 | self.local_settings_cache = Main.local_settings_cache 34 | 35 | self.cache_path = self.path.get('cache_path') 36 | self.language_path = self.paths.get("language_path") 37 | 38 | 39 | def reset_to_defaults(self): 40 | """ overwrite external file (Settings.json) by default settings """ 41 | 42 | try: 43 | with open(f"{self.cache_path}Settings.json", "w+") as file: 44 | json.dump(Main.DEFAULT_SETTINGS, file) 45 | 46 | self.update_local_cache(self.cache_path) 47 | self.OptionsWin.close() 48 | except: pass 49 | 50 | 51 | def update_local_cache(self, cache_path) -> dict: 52 | """ Update the class's cache from external file (Settings.json) & return attributes""" 53 | 54 | try: 55 | with open(f"{cache_path}Settings.json", encoding="utf-8") as file: 56 | Main.local_settings_cache = json.load(file) 57 | self.local_settings_cache = Main.local_settings_cache 58 | except: pass 59 | finally: 60 | data = { 61 | "ip":self.local_settings_cache.get("ip"), 62 | "font":self.local_settings_cache.get("font"), 63 | "port":self.local_settings_cache.get("port"), 64 | "homebrew":self.local_settings_cache.get("homebrew"), 65 | "language":self.local_settings_cache.get("language"), 66 | "icons_path":self.local_settings_cache.get("icons_path"), 67 | "download_path":self.local_settings_cache.get("download_path") 68 | } 69 | return data 70 | 71 | 72 | def save_cache(self, font:str = "", icons_path:str = "", download_path:str = "", hb:str = "", port:str = "", ip:str = "", lang:str = "", window=False) -> None: 73 | """ Cache information to external file Settings.json """ 74 | 75 | # update local cache before using it 76 | self.update_local_cache(self.cache_path) 77 | 78 | # replace empty parameters with the local cache 79 | if not ip: ip = self.local_settings_cache.get("ip") 80 | if not hb: hb = self.local_settings_cache.get("homebrew") 81 | if not port: port = self.local_settings_cache.get("port") 82 | if not font: font = self.local_settings_cache.get("font") 83 | if not icons_path: icons_path = self.local_settings_cache.get("icons_path") 84 | if not download_path: download_path = self.local_settings_cache.get("download_path") 85 | if not lang: lang = self.local_settings_cache.get("language") 86 | 87 | data = {"font":font, "port":port, "ip":ip, "icons_path":icons_path, "download_path": download_path, "homebrew":hb, "language":lang} 88 | with open(f"{self.cache_path}Settings.json", "w+") as file: 89 | json.dump(data, file) 90 | 91 | if window: 92 | self.OptionsWin.close() 93 | 94 | 95 | def render_path_window(self, type) -> None: 96 | """ Render browsing window to pick default paths """ 97 | 98 | opt = QtWidgets.QFileDialog.Options() 99 | opt |= QtWidgets.QFileDialog.DontUseSheet 100 | dialog = QFileDialog() 101 | dialog.setOptions(opt) 102 | dialog.setDirectory(self.path.get('app_root_path')) 103 | 104 | if type == "download": 105 | path = QtWidgets.QFileDialog.getExistingDirectory( 106 | None, "Default download folder...", self.path.get('app_root_path'), options=opt 107 | ) 108 | 109 | if path: self.DownloadPath.setText(path) 110 | 111 | else: 112 | path = QtWidgets.QFileDialog.getExistingDirectory( 113 | None, "Default icons folder...", self.path.get('app_root_path'), options=opt 114 | ) 115 | 116 | if path: self.IconPath.setText(path) -------------------------------------------------------------------------------- /Interface/Default_icons.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtWidgets 2 | from Module.Default_icons import Main as Default_icons 3 | from Interface.Custom_group import Ui as Custom_group 4 | 5 | 6 | class Ui(Default_icons): 7 | 8 | def setupUi(self, window): 9 | self.window = window 10 | self.window.setObjectName("Form") 11 | self.window.resize(720, 420) 12 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.window) 13 | self.verticalLayout_2.setObjectName("verticalLayout_2") 14 | self.verticalLayout = QtWidgets.QVBoxLayout() 15 | self.verticalLayout.setObjectName("verticalLayout") 16 | self.caution_message = QtWidgets.QTextBrowser(self.window) 17 | self.caution_message.setObjectName("caution_message") 18 | self.verticalLayout.addWidget(self.caution_message) 19 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 20 | self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1) 21 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 22 | self.new_icons_label = QtWidgets.QLabel(self.window) 23 | self.new_icons_label.setObjectName("new_icons_label") 24 | self.horizontalLayout_2.addWidget(self.new_icons_label) 25 | self.new_icons_number = QtWidgets.QLCDNumber(self.window) 26 | self.new_icons_number.setFrameShape(QtWidgets.QFrame.Panel) 27 | self.new_icons_number.setFrameShadow(QtWidgets.QFrame.Plain) 28 | self.new_icons_number.setDigitCount(3) 29 | self.new_icons_number.setMode(QtWidgets.QLCDNumber.Dec) 30 | self.new_icons_number.setSegmentStyle(QtWidgets.QLCDNumber.Flat) 31 | self.new_icons_number.setProperty("intValue", 0) 32 | self.new_icons_number.setObjectName("new_icons_number") 33 | self.horizontalLayout_2.addWidget(self.new_icons_number) 34 | self.verticalLayout.addLayout(self.horizontalLayout_2) 35 | spacerItem = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 36 | self.verticalLayout.addItem(spacerItem) 37 | self.default_group_btn = QtWidgets.QPushButton(self.window) 38 | self.default_group_btn.setObjectName("default_group_btn") 39 | self.verticalLayout.addWidget(self.default_group_btn) 40 | self.custom_group_btn = QtWidgets.QPushButton(self.window) 41 | self.custom_group_btn.setObjectName("custom_group_btn") 42 | self.verticalLayout.addWidget(self.custom_group_btn) 43 | spacerItem1 = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 44 | self.verticalLayout.addItem(spacerItem1) 45 | self.verticalLayout_2.addLayout(self.verticalLayout) 46 | 47 | 48 | #______________ SIGNALS ____________________ # 49 | self.custom_group_btn.setEnabled(False) 50 | self.custom_group_btn.clicked.connect(self.render_custom_icons_window) 51 | self.default_group_btn.clicked.connect(lambda: self.set_default(self.custom_group_btn, self.default_group_btn, self.new_icons_number)) 52 | 53 | self.calc_new_titles(self.custom_group_btn, self.new_icons_number) 54 | 55 | self.retranslateUi(self.window) 56 | QtCore.QMetaObject.connectSlotsByName(self.window) 57 | 58 | 59 | def render_custom_icons_window(self): 60 | window = QtWidgets.QDialog() 61 | ui = Custom_group() 62 | ui.setupUi(window) 63 | window.show() 64 | 65 | 66 | def retranslateUi(self, window): 67 | _translate = QtCore.QCoreApplication.translate 68 | window.setWindowTitle(_translate("Form", "SET ICON GROUP")) 69 | 70 | self.translated_content: dict = self.translation.get_translation(self.language, "SetDefaultIcons") 71 | self.caution_message.setHtml( 72 | "\n" 74 | f"{self.html.caution_p_tag('#ff2a2d')}{self.translated_content.get('caution')}

\n" 75 | f"{self.html.caution_msg_2('#ff2a2d')}

\n" 76 | f"{self.html.caution_p_tag('#000000')}{self.translated_content.get('optionInfo')}

\n" 77 | f"{self.html.caution_msg_2('#000000')}

\n" 78 | f"{self.html.caution_p_tag('#6db80a')}*{self.translated_content.get('moreInfo')}*

\n" 79 | f"{self.html.caution_msg_2('#6db80a')}

\n" 80 | f"{self.html.caution_p_tag('#ea8d0b')}{self.translated_content.get('moreInfo2')}

\n" 81 | f"{self.html.caution_msg_2('#ea8d0b')}

\n" 82 | f"{self.html.caution_p_tag('#6db80a')}{self.translated_content.get('moreInfo3')}

" 83 | ) 84 | self.new_icons_label.setText(_translate("Form", self.translated_content.get('new_icons_label'))) 85 | self.default_group_btn.setText(_translate("Form", self.translated_content.get('default_group_btn'))) 86 | self.custom_group_btn.setText(_translate("Form", self.translated_content.get('custom_group_btn'))) 87 | 88 | -------------------------------------------------------------------------------- /Module/Multi_upload.py: -------------------------------------------------------------------------------- 1 | import os, PIL 2 | import Common.Uploader as Uploader 3 | 4 | from environment import Common 5 | from ftplib import FTP 6 | from PIL import Image 7 | 8 | 9 | class Main(Common): 10 | """ 11 | #### Generate required NO. of icons and uploading them to PS4. 12 | 13 | * Desc: Upon icons generation sucess, upload to PS4. 14 | Otherwise, keep them stored to retry on user next start 15 | """ 16 | 17 | def __init__(self) -> None: 18 | super().__init__() 19 | 20 | self.ftp:FTP = self.get_ftp() 21 | self.game_ids:dict = self.get_ids() 22 | self.multi_uploader = Uploader.Main() 23 | 24 | 25 | def last_process(self, last_group_path:str = "") -> str|None: 26 | """ 27 | Save/Load process file -> group path 28 | """ 29 | 30 | # If path passed (write), else (read) 31 | if last_group_path: 32 | with open(self.last_baked_group_file, "w+") as file: 33 | file.write(last_group_path) 34 | 35 | else: 36 | with open(self.last_baked_group_file) as file: 37 | return file.readline() 38 | 39 | 40 | def continue_upload(self, continue_btn_widget): 41 | """ 42 | Unfinished process detected, allow user to continue uploading the rest of baked icons 43 | """ 44 | 45 | self.generate_and_upload_icons(self.last_process()) 46 | continue_btn_widget.setEnabled(False) 47 | 48 | 49 | def generate_and_upload_icons(self, group_path) -> bool: 50 | """ 51 | generate required NO. of icons for each baked icon in the selected group 52 | * NOTE: Icons must be in baked folder for default and baking 53 | """ 54 | 55 | isaccept = self.alerts.alert("WARNING", "Uploading might take sometime depends on number of games you have and the speed of your connection with the PS4. Iconit might look like frozen but its going to upload the icons on the background. Are you sure you want to continue?") 56 | 57 | if not isaccept: 58 | return True 59 | 60 | self.get_state_widget().setText("UPLOADING... PLEASE WAIT") 61 | self.last_process(group_path) 62 | 63 | self.selected_group_icons = self.read_json(group_path) 64 | baked_icons = os.listdir(self.baked_path) 65 | icons_to_upload = [] 66 | 67 | for current_icon_id in self.selected_group_icons: 68 | 69 | # Process only successfull baked icons 70 | if f"{current_icon_id}.png" not in baked_icons: 71 | continue 72 | 73 | 74 | icon = Image.open(f"{self.baked_path}{current_icon_id}.png") 75 | resized_icon = icon.resize(self.constant.get_ps4_icon_size(), PIL.Image.ANTIALIAS) 76 | self.current_game_icons = self.game_ids.get(current_icon_id).get("icons") 77 | 78 | 79 | # Iterate through the icons required for the current game 80 | for current_image in self.current_game_icons: 81 | 82 | # iconXX (png/dds) format only 83 | if ".png" not in current_image and ".dds" not in current_image: 84 | continue 85 | 86 | if "icon" not in current_image: 87 | continue 88 | 89 | 90 | icon_cache_path = f"{self.icons_cache_path}{current_image}" 91 | icon_cache_path_no_extension = icon_cache_path[:-4] 92 | 93 | if ".png" in current_image: 94 | resized_icon.save(icon_cache_path) 95 | 96 | elif ".dds" in current_image: 97 | # Creat a temp .png, then convert it to .dds 98 | 99 | resized_icon.save(f"{icon_cache_path_no_extension}.png") 100 | self.multi_uploader.png_to_dds(f"{icon_cache_path_no_extension}.png", self.icons_cache_path) 101 | 102 | icons_to_upload.append(current_image) 103 | 104 | 105 | try: 106 | game_directory = self.get_ps4_game_location(current_icon_id) 107 | self.upload_baked_icons(game_directory, icons_to_upload) 108 | 109 | # Move baked icon to cache for local view upon upload success 110 | self.multi_uploader.update_local_icon(f"{self.baked_path}{current_icon_id}.png", current_icon_id) 111 | self.get_state_widget().setText("DONE!") 112 | 113 | except Exception as e: 114 | self.alerts.alert("UNSUCCESSFUL", f"Something went wrong while communicating with the PS4. Please rerun the application - Issue | {str(e)}", False) 115 | self.log_to_external_file(str(e), "upload - baked mask") 116 | self.get_state_widget().setText("UNSUCCESSFUL") 117 | self.upload_btn_widget.setEnabled(False) 118 | return False 119 | 120 | self.upload_btn_widget.setEnabled(False) 121 | return True 122 | 123 | 124 | def upload_baked_icons(self, game_directory:str, icons:list): 125 | """ 126 | Upload set of generated icons for the game. One call per game 127 | """ 128 | 129 | self.ftp.cwd(f"/{game_directory}") 130 | 131 | for icon in icons: 132 | icon_dir = f"{self.icons_cache_path}{icon}" 133 | self.multi_uploader.upload_to_server(icon_dir, icon) 134 | 135 | -------------------------------------------------------------------------------- /Interface/Custom_group.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtWidgets 2 | from Module.Custom_group import Main as Custom_group 3 | 4 | class Ui(Custom_group): 5 | 6 | def __init__(self) -> None: 7 | super().__init__() 8 | 9 | 10 | def setupUi(self, window): 11 | self.window = window 12 | self.window.setObjectName("Dialog") 13 | self.window.resize(503, 301) 14 | 15 | self.gridLayout = QtWidgets.QGridLayout() 16 | self.formLayout_6 = QtWidgets.QFormLayout() 17 | self.verticalLayout_2 = QtWidgets.QVBoxLayout() 18 | self.scrollAreaWidgetContents = QtWidgets.QWidget() 19 | self.scrollArea = QtWidgets.QScrollArea(self.window) 20 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.window) 21 | self.group_title_label = QtWidgets.QLabel(self.window) 22 | 23 | self.verticalLayout_2.setContentsMargins(0, 0, -1, -1) 24 | self.scrollArea.setWidgetResizable(True) 25 | self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 481, 222)) 26 | self.formLayout_3 = QtWidgets.QFormLayout(self.scrollAreaWidgetContents) 27 | 28 | self.gridLayout.setContentsMargins(-1, 0, -1, -1) 29 | self.gridLayout.setVerticalSpacing(10) 30 | 31 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 32 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 33 | 34 | self.gridLayout.addItem(spacerItem, 0, 0, 1, 1) 35 | self.gridLayout.addItem(spacerItem1, 0, 2, 1, 1) 36 | 37 | self.formLayout_3.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.gridLayout) 38 | self.scrollArea.setWidget(self.scrollAreaWidgetContents) 39 | self.verticalLayout_2.addWidget(self.scrollArea) 40 | self.formLayout_6.setContentsMargins(0, 0, -1, -1) 41 | self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.group_title_label) 42 | self.group_title_input = QtWidgets.QLineEdit(self.window) 43 | self.group_title_input.setAlignment(QtCore.Qt.AlignCenter) 44 | self.group_title_input.setClearButtonEnabled(True) 45 | self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.group_title_input) 46 | self.verticalLayout_2.addLayout(self.formLayout_6) 47 | 48 | self.buttonBox = QtWidgets.QDialogButtonBox(self.window) 49 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal) 50 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Save) 51 | self.buttonBox.setCenterButtons(True) 52 | self.verticalLayout_2.addWidget(self.buttonBox) 53 | self.horizontalLayout.addLayout(self.verticalLayout_2) 54 | 55 | self.buttonBox.setObjectName("buttonBox") 56 | self.gridLayout.setObjectName("gridLayout") 57 | self.scrollArea.setObjectName("scrollArea") 58 | self.formLayout_6.setObjectName("formLayout_6") 59 | self.formLayout_3.setObjectName("formLayout_3") 60 | self.horizontalLayout.setObjectName("horizontalLayout") 61 | self.verticalLayout_2.setObjectName("verticalLayout_2") 62 | self.group_title_input.setObjectName("group_title_input") 63 | self.group_title_label.setObjectName("group_title_label") 64 | self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") 65 | 66 | """ 67 | ################################################################# 68 | CUSTOM SELECT/DESELECT ALL BTNs 69 | ################################################################# 70 | """ 71 | 72 | self.selectAllBtn = QtWidgets.QPushButton() 73 | self.deselectAllBtn = QtWidgets.QPushButton() 74 | self.buttonBox.addButton(self.selectAllBtn, QtWidgets.QDialogButtonBox.ActionRole) 75 | self.buttonBox.addButton(self.deselectAllBtn, QtWidgets.QDialogButtonBox.ActionRole) 76 | 77 | 78 | """ 79 | ################################################################# 80 | RENDER GAME ID/TITLE AS CHECKBOX 81 | ################################################################# 82 | """ 83 | 84 | self.checkboxes:list = [] 85 | ids:dict = self.get_ids() 86 | row = 0 87 | 88 | for id, id_info in ids.items(): 89 | checkbox = QtWidgets.QCheckBox(self.scrollAreaWidgetContents) 90 | checkbox.setObjectName(id) 91 | checkbox.setText(f"[{id}] {id_info.get('title')}") 92 | 93 | self.gridLayout.addWidget(checkbox, row, 1, 1, 1) 94 | self.checkboxes.append(checkbox) 95 | 96 | row += 1 97 | 98 | 99 | self.retranslateUi() 100 | self.buttonBox.rejected.connect(self.window.reject) 101 | self.buttonBox.accepted.connect(lambda: self.save_group()) 102 | self.selectAllBtn.clicked.connect(lambda: self.select_all()) 103 | self.deselectAllBtn.clicked.connect(lambda: self.deselect_all()) 104 | 105 | 106 | QtCore.QMetaObject.connectSlotsByName(self.window) 107 | 108 | 109 | def retranslateUi(self): 110 | 111 | _translate = QtCore.QCoreApplication.translate 112 | self.window.setWindowTitle(_translate("Dialog", "CUSTOM GROUPs")) 113 | 114 | self.selectAllBtn.setText(_translate("Dialog", "Select all")) 115 | self.deselectAllBtn.setText(_translate("Dialog", "Deselect all")) 116 | self.group_title_label.setText(_translate("Dialog", "GROUP TITLE")) 117 | self.group_title_input.setPlaceholderText(_translate("Dialog", "i.e. Homebrews")) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Language _(Google translation)_ 2 | 3 | * `Spanish` [Español](https://translate.google.com/translate?sl=en&tl=es&u=https://github.com/OfficialAhmed/Iconit-PS4/blob/master/README.md) 4 | * `German` [Deutsch](https://translate.google.com/translate?sl=en&tl=de&u=https://github.com/OfficialAhmed/Iconit-PS4/blob/master/README.md) 5 | * `Arabic` [العربية](https://translate.google.com/translate?sl=en&tl=ar&u=https://github.com/OfficialAhmed/Iconit-PS4/blob/master/README.md) 6 | 7 | # Overview 8 | 9 | _Latest `exe` version `5.13 (July 12th, 2023)`_ 10 | 11 | _Currently working on `v5.56` mainly for performance & code factorization for `PS5 FTP` in the future_. 12 | 13 | *Download `icons` from * 14 | 15 | *Download `masks` from * 16 | 17 | *Download `.exe` the latest windows executable version from* 18 | 19 | ## About 20 | 21 | Pure Python implementation of an automated software to change `PS4` xmb icons, pictures and profile avatars. PS4 (5.05 - 9.00). 22 | 23 | 24 | This application is much convenient than uploading the icons manually, it automates the following: 25 | 26 | * Icon duplication 27 | * Apply mask for a set of icons 28 | * Backup original icons `icon0, icon1, ...` 29 | * Upload and overwrite icons to the `PS4` 30 | * Game ID `CUSA` to game title conversion 31 | * Image conversion `JPG, ICO, ...` to `PNG` 32 | * Image to compressed textures conversion `PNG` to `DDS dxt1` 33 | * Resize images to the required size `512x512`, `1920x1080` 34 | 35 | ## *What's new in v5* 36 | 37 | #### _For end users:_ 38 | 39 | * Fixed `picX_XX.png` & `picX_XX.dds` conversion 40 | * `Independent version` ImageMagick is no longer required 41 | * In the `Games List` _Game IDs_ now shown next to the game title 42 | * `Modes` can be changed without restarting the program 43 | * `Backup icons` will never be overwritten nor removed [_used to cache once then overwrite the older one_] 44 | 45 | * `Circular icon scrolling` when the last icon's reached, it'll begin again from the start and vice versa. 46 | 47 | * `Faster caching` new techniques added 48 | 49 | * `New option` __Download database__ for a faster caching process 50 | 51 | * `New Feature` you can now group a set of icons and apply a mask to them 52 | 53 | #### _For developers:_ 54 | 55 | * `Image Detection` detects all images of the form `iconX_XX` & `picX_XX` 56 | 57 | * `Clean up` code and file structure 58 | 59 | * `Buffer size` increased to ~65500 bytes 60 | 61 | * `Required Icons` now will be cached on PS4 connect (_Slower_) 62 | 63 | * `Required Icons` will be read from cache instead of FTP (_Faster_) 64 | 65 | * `OOP implementation` (_Faster but more memory usage_) 66 | 67 | * `Break GIL Limitation` implemented concurrent methods for CPU I/O bound (_Faster when lots of icons, slower otherwise_) 68 | 69 | * `Local database` implemented & fetched from [DEFAULTDNB](https://github.com/DEFAULTDNB/DEFAULTDNB.github.io). For game titles caching "_Read [wiki](https://github.com/OfficialAhmed/Iconit-PS4/wiki/Performance) for detailes_" 70 | 71 | * `Subproccess` implementation to convert `PNG` to `DDS` DXT1 compression using [DirectXTex/texconv](https://github.com/Microsoft/DirectXTex/wiki/Texconv) 72 | 73 | ________________________________________ 74 | 75 | ## Generate executable 76 | 77 | * (no console) 78 | 79 | `pyinstaller --noconfirm --onefile --windowed --icon "Iconit-path-here/Iconit-PS4/ico/icon.ico" --name "Iconit vX.X" "iconit-path-here/Iconit-PS4/main.py"` 80 | 81 | * (verrsrc) - if available 82 | `--version-file "Iconit-path-here/Iconit-PS4/verrsrc.h"` 83 | 84 | ## Current implementation bugs 85 | 86 | 1. Avatars option patch required 87 | 88 | 89 | ## To do list (UPCOMING UPDATE) 90 | 91 | 92 | * [x] Mask a Set Of Icons `New Feature` 93 | 94 | * [x] Group a Set Of Icons `New Feature` 95 | 96 | * [x] Utilize 100% CPU usage `Concurrent processing` 97 | 98 | * [x] Multiple Icon Duplication at Once - heavy R/W Bound `Threading` 99 | 100 | * [ ] Save/load cache backup on PS4 `New feature` 101 | 102 | * [ ] Backup/Restore cache to/from PS4 `New Feature` 103 | 104 | * [ ] Render Game ID `CUSAXXXXX` text on icon 105 | 106 | * [ ] Feature : User can edit displayed game title through cache file 107 | 108 | * [ ] Feature : Read titles from xmb DB 109 | 110 | 111 | ## _Credits_ 112 | 113 | * Special thanks to [@Lapy](https://twitter.com/Lapy05575948) for the help. 114 | 115 | * Special thanks to [@DEFAULTDNB](https://github.com/DEFAULTDNB) AKA [@KiiWii](https://twitter.com/DefaultDNB) for the Database 116 | 117 | 118 | ### _Testers_: 119 | 120 | * [@laz305](https://twitter.com/laz305) 121 | 122 | * [@maxtinion](https://twitter.com/maxtinion) 123 | 124 | * [@_deejay87_](https://twitter.com/_deejay87_) 125 | 126 | * [@PS__TRICKS](https://twitter.com/PS__TRICKS) 127 | 128 | 129 | # Attention 130 | 131 | Some antivirus detect the compiled/converted `.exe` version as **_trojan or malware_ [Win64:Evo-gen [Trj] or Trojan.Generic.horqm]** because the compiler _Pyinstaller_ used by some people to make malware but don't worry `Iconit.exe is False-positive`. If you don't trust it, you can check the commits and changes if there's any malicious code or compile the code on your own. Otherwise, you may whitelist it and continue. 132 | 133 | The _Pyinstaller_ devs stated it here [False-positive Pyinstaller](https://github.com/pyinstaller/pyinstaller/issues/6754) 134 | 135 | 136 | ## _Preview_ 137 | 138 | ![Main_screen](Interface/view/main_screen.jpg) 139 | 140 | ![Icons_screen](Interface/view/icons_screen.jpg) 141 | 142 | 143 | # License 144 | 145 | Iconit-PS4 - Copyright (C) 2019-2023 OfficialAhmed 146 | 147 | This software is free to use, distribute and/or modify it under the terms of the [MIT License](LICENSE) (MIT) 148 | -------------------------------------------------------------------------------- /Interface/Upload.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore, QtGui, QtWidgets 2 | from Module.Upload import Main as Upload 3 | 4 | 5 | class Ui(Upload): 6 | 7 | def __init__(self) -> None: 8 | super().__init__() 9 | 10 | 11 | def setupUi(self, window): 12 | self.selected_mode = self.get_selected_mode() 13 | self.browsed_icon_path = self.get_browsed_icon_path() 14 | 15 | font = QtGui.QFont() 16 | font.setFamily(self.font) 17 | font.setPointSize(12) 18 | font.setItalic(True) 19 | 20 | """ 21 | ################################################################# 22 | WINDOW SPECS 23 | ################################################################# 24 | """ 25 | window.resize(378, 229) 26 | window.setWindowTitle("Proceed sending images...") 27 | window.setMinimumSize(QtCore.QSize(500, 500)) 28 | window.setMaximumSize(QtCore.QSize(500, 500)) 29 | window.setWindowIcon(QtGui.QIcon(f"{self.pref_path}ic1.@OfficialAhmed0")) 30 | 31 | """ 32 | ################################################################# 33 | BUTTONS 34 | ################################################################# 35 | """ 36 | self.Yes = QtWidgets.QPushButton(window) 37 | self.Ok = QtWidgets.QPushButton(window) 38 | self.No = QtWidgets.QPushButton(window) 39 | 40 | btns = (self.Yes, self.Ok, self.No) 41 | x_axis = 155 42 | for inx, btn in enumerate(btns): 43 | if inx == 2: 44 | x_axis = 290 45 | btn.setFont(font) 46 | btn.setGeometry(QtCore.QRect(x_axis, 120, 100, 31)) 47 | btn.setStyleSheet("background-color: rgb(190, 190, 190);") 48 | x_axis += 60 49 | 50 | 51 | """ 52 | ################################################################# 53 | PROGRESS BARS - WIDGETS 54 | ################################################################# 55 | """ 56 | self.ValidationBar = QtWidgets.QProgressBar(window) 57 | self.ConversionBar = QtWidgets.QProgressBar(window) 58 | self.SendingBar = QtWidgets.QProgressBar(window) 59 | 60 | bars = (self.ValidationBar, self.ConversionBar, self.SendingBar) 61 | y_axis = 225 62 | for bar in bars: 63 | bar.setGeometry(QtCore.QRect(220, y_axis, 170, 40)) 64 | bar.setTextVisible(True) 65 | bar.setProperty("value", 0) 66 | bar.setAlignment(QtCore.Qt.AlignCenter) 67 | bar.setOrientation(QtCore.Qt.Horizontal) 68 | y_axis += 100 69 | 70 | """ 71 | ################################################################# 72 | LABELS - WIDGETS 73 | ################################################################# 74 | """ 75 | font.setPointSize(13) 76 | 77 | self.ValidationLabel = QtWidgets.QLabel(window) 78 | self.ConversionLabel = QtWidgets.QLabel(window) 79 | self.SendingLabel = QtWidgets.QLabel(window) 80 | 81 | labels = (self.ValidationLabel, self.ConversionLabel, self.SendingLabel) 82 | y_axis = 230 83 | for label in labels: 84 | label.setFont(font) 85 | label.setGeometry(QtCore.QRect(60, y_axis, 111, 31)) 86 | label.setFrameShape(QtWidgets.QFrame.NoFrame) 87 | label.setStyleSheet("color: rgb(255, 255, 255);") 88 | label.setAlignment(QtCore.Qt.AlignCenter) 89 | y_axis += 100 90 | 91 | del labels 92 | del y_axis 93 | del bars 94 | del btns 95 | 96 | """ 97 | ################################################################# 98 | EXTRA - ELEMENTS 99 | ################################################################# 100 | """ 101 | self.msg = QtWidgets.QLabel(window) 102 | self.msg.setFont(font) 103 | self.msg.setGeometry(QtCore.QRect(20, 50, 470, 40)) 104 | self.msg.setStyleSheet("color: rgb(255, 255, 255);") 105 | self.msg.setAlignment(QtCore.Qt.AlignCenter) 106 | self.msg.setFrameShape(QtWidgets.QFrame.Box) 107 | self.msg.setFrameShadow(QtWidgets.QFrame.Plain) 108 | 109 | self.graphicsView = QtWidgets.QGraphicsView(window) 110 | self.graphicsView.setGeometry(QtCore.QRect(-20, -10, 700, 700)) 111 | self.graphicsView.setStyleSheet("background-color: rgb(50, 50, 50);") 112 | 113 | self.line = QtWidgets.QFrame(window) 114 | self.line.setGeometry(QtCore.QRect(10, 160, 480, 20)) 115 | self.line.setFrameShape(QtWidgets.QFrame.HLine) 116 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 117 | 118 | self.centralwidget = QtWidgets.QWidget(window) 119 | 120 | """ 121 | ################################################################# 122 | SIGNALS 123 | ################################################################# 124 | """ 125 | self.Ok.clicked.connect(window.close) 126 | self.No.clicked.connect(window.close) 127 | self.Yes.clicked.connect(self.start_processing) 128 | 129 | """ 130 | ################################################################# 131 | VISBILITY 132 | ################################################################# 133 | """ 134 | self.graphicsView.raise_() 135 | self.Yes.raise_() 136 | self.No.raise_() 137 | self.line.raise_() 138 | self.ValidationBar.raise_() 139 | self.ConversionBar.raise_() 140 | self.SendingBar.raise_() 141 | self.ValidationLabel.raise_() 142 | self.ConversionLabel.raise_() 143 | self.SendingLabel.raise_() 144 | self.msg.raise_() 145 | self.retranslateUi() 146 | QtCore.QMetaObject.connectSlotsByName(window) 147 | 148 | 149 | def retranslateUi(self): 150 | 151 | win_name = "ConfirmWindow" 152 | translated_content: dict = self.translation.get_translation(self.language, win_name) 153 | _translate = QtCore.QCoreApplication.translate 154 | 155 | self.Yes.setText(_translate(win_name, translated_content.get("Yes"))) 156 | self.Ok.setText(_translate(win_name, translated_content.get("Ok"))) 157 | self.No.setText(_translate(win_name, translated_content.get("No"))) 158 | self.ValidationLabel.setText(_translate(win_name, translated_content.get("ValidationLabel"))) 159 | self.ConversionLabel.setText(_translate(win_name, translated_content.get("ConversionLabel"))) 160 | self.SendingLabel.setText(_translate(win_name, translated_content.get("SendingLabel"))) 161 | warning_message = f"{self.html.p_tag('margin: 0px;font-size:12pt; -qt-block-indent:0; text-indent:0px; color:#e83c3c', {translated_content.get('Warning')})}{translated_content.get('Question')}?" 162 | self.msg.setText(_translate(win_name, warning_message)) 163 | -------------------------------------------------------------------------------- /Data/Language/English.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "English", 3 | "Languages": { 4 | "English": "English", 5 | "Arabic": "Arabic", 6 | "German": "German", 7 | "Spanish": "Spanish" 8 | }, 9 | "Settings": { 10 | "WindowTitle": "Settings", 11 | "NoRadio": "OFF", 12 | "YesRadio": "ON", 13 | "SaveBtn": "SAVE", 14 | "CancelBtn": "CANCEL", 15 | "IpLabel": "DEFAULT IP", 16 | "PortLabel": "DEFAULT PORT", 17 | "DefaultBtn": "DEFAULT", 18 | "FontLabel": "Default Font", 19 | "HomebrewLabel": "ENABLE HOMEBREW", 20 | "LanguageLabel": "DEFAULT LANGUAGE", 21 | "IconsPathLabel": "DEFAULT ICONS PATH", 22 | "DownloadPathLabel": "DEFAULT DOWNLOAD PATH", 23 | "EnableHbInfo": "NOTE: Enabling this option will allow you to change homebrew icons but the caching will take longer depends on how many homebrews you have installed." 24 | }, 25 | "Iconit": { 26 | "GameIconsRadio": "Game Icon/Pic", 27 | "SystemIconsRadio": "System Icons", 28 | "AvatarIconsRadio": "Profile Avatar [Disabled]", 29 | "ModeLabel": "MODE", 30 | "IpLabel": "PS4 IP", 31 | "CacheLabel": "Cache", 32 | "PortLabel": "PS4 Port", 33 | "StatusLabel": "Awaiting ..", 34 | "StatusLabel_success": "CONNECTED", 35 | "StatusLabel_fail": "FAILED TO CONNECT", 36 | "OnlineIconsLink": "Download Free Icons", 37 | "PaypalLink": "Donate (PayPal)", 38 | "TwitterLink": "Created by", 39 | "SysIconsInfo": "Note: Full R/W permissions required ( PS4 Xplorer FTP by enabling danger mode)", 40 | "GameIconsInfo": "Note: You can enable Homebrew icons in the settings before connecting to the PS4", 41 | "TitleLink": "Iconit", 42 | "menuSettings": "Settings", 43 | "About": "About", 44 | "actionAbout": "About", 45 | "Options": "Options...", 46 | "ConnectBtn": "Connect PS4", 47 | "Remove_cache": "Remove cache", 48 | "Special_thanks": "Special thanks", 49 | "DownloadDatabase": "Download/Update Database" 50 | }, 51 | "Icons": { 52 | "NextBtn": "NEXT", 53 | "SelectBtn": "SELECT", 54 | "MaskBtn": "MASK MAKER...", 55 | "GameIdLabel": "CURRENT ID", 56 | "ChangeIconBtn": "CHANGE...", 57 | "IconSizeLabel": "DIMENSIONS", 58 | "SendBtn": "ICON/PICTURE", 59 | "TotalGamesLabel": "TOTAL GAMES", 60 | "ChangeBgBtn": "GAME PICTURE...", 61 | "IconLocationLabel": "ICON LOCATION", 62 | "IconSizeTxt": "Current icon dimension", 63 | "TwitterLink": "Created By @OfficialAhmed0", 64 | "PaypalLink": "PayPal", 65 | "LogsTxt": "Connected to PS4", 66 | "TitleLabel_GameIcons": "GAME ICONS", 67 | "TitleLabel_SysIcons": "SYSTEM ICONS", 68 | "HomebrewLabel_Y": "HOMEBREW ICON: YES", 69 | "HomebrewLabel_N": "HOMEBREW ICON: NO", 70 | "HomebrewLabel_T": "HOMEBREW ICON: TURNED OFF", 71 | "HomebrewLabel_Sys": "SYSTEM ICON: YES", 72 | "IconLocation_In": "INTERNAL", 73 | "IconLocation_Ex": "EXTERNAL", 74 | "menuMore": "MENU", 75 | "setDefaultIcons": "SET ICON GROUP", 76 | "ToolTips": { 77 | "NextBtn": "NEXT ICON", 78 | "MaskBtn": "APPLY MASK FOR THE ICON", 79 | "PreviousBtn": "PREVIOUS ICON", 80 | "ChangeIconBtn": "CHANGE THE ICON", 81 | "SelectBtn": "SELECT THE ICON FROM THE DROPDOWN LIST", 82 | "ChangeBgBtn": "CHANGE BACKGROUND IMAGE FOR THE GAME LAUNCH", 83 | "SendBtn": "UPLOAD THE ICON/BACKGROUND", 84 | "IconLocationTxt": "THE LOCATION WHERE THE CURRENT GAME IS LOCATED AT", 85 | "HomebrewLabel": "WETHER THE CURRENT ID IS A HOMEBREW OR NOT", 86 | "GameTitleLabel": "SOME GAME TITLES ARE UNKNOWN. PERHAPS THEY'RE HOMEBREW (PS2 GAME/UNITY GAME) ETC." 87 | }, 88 | "Logs": { 89 | "CorrectDim": "Image in correct dimensions", 90 | "SmallDim": "Image cannot be used nor resized (TOO SMALL)", 91 | "ErrDim": "Image cannot be used nor resized (TOO LARGE) limited to", 92 | "LargeDim": "Image will be resized (TOO LARGE)", 93 | "BackupS": "Backed up successfully" 94 | } 95 | }, 96 | "Alerts": { 97 | "Thanks": "Thank you for using Iconit", 98 | "About": "Iconit is an automated Windows application for jailbroken PS4 consoles to change xmb icons and profile avatars with the help of FTP payload", 99 | "AppVer": "App version", 100 | "DbSuccess": "Successfully downloaded aprox. 300kb database, caching games will be faster now.", 101 | "SpecialThanks": "Special Thanks", 102 | "ThanksAll": "Thanks to all the devs in the scene who made this possible", 103 | "CacheRemoved": "Cached icons have been removed. It'll take time to cache next time you connect to the PS4", 104 | "InvalidCred": "Error Iconit needs admin permission to perform the task. Rerun as administrator", 105 | "PermissionErr": "Invalid IP or Port (Alphabets not allowed)", 106 | "SmallSize": "Invalid icon size 'too small'. Minimum size required", 107 | "Disconnected": "Disconnected from PS4. Re-enable FTP payload", 108 | "InvalidInput": "Double check PS4 IP and Port\n Note: If you're using GoldHen FTP\n make sure you're not connected to the PS4 with a different app as it only allow one connection", 109 | "IsPS4": "Are you sure you're connected to PS4?", 110 | "TimeOut": "Double check the Ip and port DEV| TimeoutError", 111 | "ConnRefused": "PS4 has refused to connect, perhaps it's connected somewhere else DEV| ConnectionRefusedError", 112 | "IncompleteProcess": "It seems the last uploading process was incomplete, click the 'continue' button if you'd like to continue uploading" 113 | }, 114 | "SetDefaultIcons": { 115 | "caution": "CAUTION! Read carefully", 116 | "optionInfo": "This option will use the current set of icons as the default set. It's recommended that you make sure all icons have the original icon/without any mask, otherwise the resulting icons will end up incorrect", 117 | "moreInfo": "You only need to use this option once", 118 | "moreInfo2": "If you have new icons/games installed, rerun this option, otherwise the mask maker won't be able detect them.", 119 | "moreInfo3": "No worries I will let you know if I detect new icons", 120 | "new_icons_label": "NEW ICONS NOT IN [DEFAULT GROUP]", 121 | "default_group_btn": "SET/UPDATE DEFAULT GROUP", 122 | "custom_group_btn": "SET CUSTOM GROUP" 123 | }, 124 | "ConfirmWindow": { 125 | "Ok": "Ok", 126 | "No": "No", 127 | "Yes": "Yes", 128 | "SendingLabel": "SEND", 129 | "ValidationLabel": "VALIDATION", 130 | "ConversionLabel": "CONVERSION", 131 | "Question": "Are sure you want to proceed", 132 | "Warning": "ATTENTION! This will overwrite the icon on the PS4" 133 | }, 134 | "MaskMakerWindow": { 135 | "MaskBtn": "Mask...", 136 | "GroupBtn": "Group...", 137 | "RevertBtn": "Revert to default", 138 | "BakeBtn": "BAKE ICONS", 139 | "UploadState": "Waiting...", 140 | "BakeState": "BAKING REQUIRED", 141 | "ContinueProcessBtn": "Continue", 142 | "UploadBtn": "UPLOAD BAKED ICONS", 143 | "WindowTitle": "Iconit - Mask Maker", 144 | "FreeMasksLinkLabel": "Download Free Masks", 145 | "Warning": "ATTENTION! This will overwrite the icon on the PS4", 146 | "ContinueProcessLabel": "If anything goes wrong while uploading the icons click 'continue' anytime to proceed the process from where it stopped.", 147 | "WarnWindowTitle": "continue upload process", 148 | "RevertToDefaultWindow": { 149 | "WinTitle": "REVERT TO DEFAULT CONFIRMATION", 150 | "Msg1": "Are you sure you want to revert", 151 | "Msg2": "icons to the default style?" 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /Data/Language/Arabic.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "العربية", 3 | "Languages": { 4 | "Arabic": "العربية", 5 | "English": "الانجليزية", 6 | "German": "الالمانية", 7 | "Spanish": "الاسبانية" 8 | }, 9 | "Settings": { 10 | "WindowTitle": "الإعدادات", 11 | "NoRadio": "معطل", 12 | "YesRadio": "مفعل", 13 | "SaveBtn": "حفظ", 14 | "CancelBtn": "إلغاء", 15 | "IpLabel": "عنوان آلاي بي الافتراضي", 16 | "PortLabel": "رقم رقم المنفذ الافتراضي", 17 | "DefaultBtn": "إعدادات الافتراضية", 18 | "FontLabel": "الخط الافتراضي", 19 | "HomebrewLabel": "برامج التهكير", 20 | "LanguageLabel": "اللغة الافتراضية", 21 | "IconsPathLabel": "مجلد الايقونات الافتراضي", 22 | "DownloadPathLabel": "مجلد تحميل الايقونات الافتراضي", 23 | "EnableHbInfo": "انتبة: إن تفعيل هذة الخاصية ستتيح لك بتغيير ايقونات البرامج والالعاب المقرصنة ولكن سوف يأخذ وقتا اطول حتى يتم تخزين المعلومات اثناء الاتصال مع البلايستيشن لاول مرة" 24 | }, 25 | "Iconit": { 26 | "GameIconsRadio": "ايقونات و خلفيات الالعاب", 27 | "SystemIconsRadio": "أيقونات النظام", 28 | "AvatarIconsRadio": "شخصيات الحساب [غير متوفر]", 29 | "ModeLabel": "الوضع", 30 | "IpLabel": "عنوان آلاي بي", 31 | "CacheLabel": "نسبة التحميل", 32 | "PortLabel": "رقم المنفذ", 33 | "StatusLabel": "...منتظر", 34 | "StatusLabel_success": "متصل", 35 | "StatusLabel_fail": "فشل الاتصال", 36 | "OnlineIconsLink": "تحميل ايقونات مجانية", 37 | "PaypalLink": "تبرع (PayPal)", 38 | "TwitterLink": "المبرمج", 39 | "SysIconsInfo": "Lapy Xplorer انتبة: هذه الخاصية تتطلب اذونات خاصة متوفرة في بعض الانظمة مثل برنامج", 40 | "GameIconsInfo": "انتبة: بامكانك تغيير صور البرامج والالعاب المقرصنة في الخيارات", 41 | "TitleLink": "Iconit", 42 | "menuSettings": "الخيارات", 43 | "Options": "...الإعدادات", 44 | "ConnectBtn": "أَبدَء", 45 | "Remove_cache": "حذف معلومات البرنامج", 46 | "Special_thanks": "شكر وتقدير", 47 | "DownloadDatabase": "تحميل و تحديث قاعدة البيانات", 48 | "About": "عني", 49 | "actionAbout": "عني" 50 | }, 51 | "Icons": { 52 | "NextBtn": "التالي", 53 | "SelectBtn": "اختر", 54 | "MaskBtn": "صانع الإطار...", 55 | "GameIdLabel": "رقم الايقونة التسلسلي", 56 | "ChangeIconBtn": "تغيير الايقونة...", 57 | "IconSizeLabel": "قياسات الصورة", 58 | "SendBtn": "رفع الصورة و الايقونة", 59 | "TotalGamesLabel": "ترتيب الايقونة الحالية", 60 | "ChangeBgBtn": "تغيير صورة الخلفية...", 61 | "IconLocationLabel": "مكان الايقونة", 62 | "IconSizeTxt": "قياسات الصورة الحالية", 63 | "TwitterLink": "المطور @OfficialAhmed0", 64 | "PaypalLink": "PayPal", 65 | "LogsTxt": "متصل على", 66 | "TitleLabel_GameIcons": "أيقونات الالعاب", 67 | "TitleLabel_SysIcons": "أيقونات برامج النظام", 68 | "HomebrewLabel_Y": "برنامج مزيف : نعم", 69 | "HomebrewLabel_N": "برنامج مزيف : لا", 70 | "HomebrewLabel_T": "برنامج مزيف : الخاصية مقفلة", 71 | "HomebrewLabel_Sys": "أيقونات النظام: نـعم", 72 | "IconLocation_In": "تخزين داخلي", 73 | "IconLocation_Ex": "تخزين خارجي", 74 | "menuMore": "اخرى", 75 | "setDefaultIcons": "حفظ الصور الافتراضية", 76 | "ToolTips": { 77 | "NextBtn": "الايقونة التالية", 78 | "MaskBtn": "صنع ايطار للصورة", 79 | "PreviousBtn": "الايقونة السابقة", 80 | "ChangeIconBtn": "تغيير الايقونة التي تعرض في الشاشة ", 81 | "SelectBtn": "اختر الايقونة عن طريق الاسماء المتوفرة", 82 | "ChangeBgBtn": "تغيير واجهة اللعبة اثناء تشغيلها", 83 | "SendBtn": "رفع الصور على البلايستيشن 4", 84 | "IconLocationTxt": "مكان الايقونة الحالي", 85 | "HomebrewLabel": " (homebrew) هل هذه ايقونة برنامج او لعبة المزيفة", 86 | "GameTitleLabel": "أسـم الايقونة سيكون مجهول بسبب عدم تسميتها اثناء تطويرها مثل البرامج المزيفة والمجانية" 87 | }, 88 | "Logs": { 89 | "CorrectDim": "تم اختيار صورة بمقاسات صحيحة", 90 | "SmallDim": "لا يمكنك استخدام الصورة (قياسات الصورة صغيره جدا)", 91 | "ErrDim": " لا يمكنك استخدام الصورة ولايمكنني تغيير قياساتها (قياسات الصورة كبيره جدا) الاقصى", 92 | "LargeDim": "سيتم تغيير قياسات الصورة (قياسات الصورة كبيره)", 93 | "BackupS": "تمت عملية النسخ الاحتياطي للصورة بنجاح" 94 | } 95 | }, 96 | "Alerts": { 97 | "Thanks": "شكرا على دعمك برنامج الايقونات", 98 | "About": "برنامج الايقونات هو برنامج اوتوماتيكي للبلايستيشن 4 المهكرة لتغيير ايقونات وخلفيات الالعاب وكذلك صور شخصيات الحساب الخاصة عن طريق الخادم اف تي بي", 99 | "AppVer": "نسخة البرنامج", 100 | "DbSuccess": "تم تحميل تقريبا 300كيلوبايت من السيرفر. عملية التوصيل ستكون اسرع بكثير", 101 | "SpecialThanks": "شكر خاص", 102 | "ThanksAll": "شكر خاص لجميع المطورين و المهكرين الذين ساعدوني في تطوير البرنامج و الذي نشروا نسخ التهكير", 103 | "CacheRemoved": "تم حذف الصور المؤقتة. ستستغرق عملية التوصيل مدة اطول", 104 | "InvalidCred": "برنامج الايقونات يتطلب الاذونات المسؤل. فعل الخاصية من اذنك ليتمكن البرنامج من انتهاء العملية ", 105 | "PermissionErr": "غلط في رقم الاي بي او رقم الخادم (ممنوع ادخال الحروف)", 106 | "SmallSize": "قياسات الصورة غير صحيحة. صغيرة جدا", 107 | "Disconnected": "البلايستيشن قطعت الاتصال. الرجاء الاتصال مرة اخرى", 108 | "InvalidInput": "تحقق من الاي بي و رقم الخادم وتأكد من الاثنان متصلان بنفس شبكة الانترنت", 109 | "IsPS4": "هل انت متأكد من انك متصل مع بلايستيشن 4 ؟", 110 | "TimeOut": "تأكد من الارقام التي ادخلتها صحيحة DEV| TimeoutError", 111 | "ConnRefused": "البلايستيشن ترفض الاتصال ربما انها متصلة مع برنامج اخر اعد المحاولة بعد التأكد DEV| ConnectionRefusedError", 112 | "IncompleteProcess": "يبدو ان عملية رفع الصور لن تتم بالشكل الصحيح في المرة السابقة. انقر على زر المتابعة لتكملة العملية في الصفحة التالية" 113 | }, 114 | "SetDefaultIcons": { 115 | "caution": "تحذير! إقراء التالي", 116 | "optionInfo": "هذه الخاصية اساسية لبدء عملية الإطارات للصور. قبل الضغط على زر الحفظ رجاءا تأكد من ان ايقونات الالعاب جميعها بصيغة الاصلية اي بدون تعديل في الاطارات من قبل", 117 | "moreInfo": "بامكانك استخدام هذه الخاصية لصنع اطارات خاصة لكل فئة من الالعاب من اختيارك ولكن لابد من الضغط على زر حفظ نسخة احتياطية للصور", 118 | "moreInfo2": "عند تثبيت العاب جديدة كل ماعليك فعله هو حفظ نسخة احتياطية. البرنامج سيقوم بحفظ نسخة احتياطة للايقونات", 119 | "moreInfo3": "لا تقلق سوف احسب عدد الالعاب الجديدة و اذا كنت بحاجة لحفظ نسخة احتياطية ", 120 | "new_icons_label": "ايقونات جديدة ليست من ضمن النسخة الاحتياطية", 121 | "default_group_btn": "حفظ/تحديث النسخة احتياطية", 122 | "custom_group_btn": "إطارات لفئة مخصصة" 123 | }, 124 | "ConfirmWindow": { 125 | "Ok": "حسنًا", 126 | "No": "لا", 127 | "Yes": "نعم", 128 | "SendingLabel": "إرسال", 129 | "ValidationLabel": "التحقق", 130 | "ConversionLabel": "التحويل", 131 | "Question": "هل أنت متأكد أنك تريد المتابعة", 132 | "Warning": "تنبيه! سيتم استبدال الايقونة على جهاز PS4" 133 | }, 134 | "MaskMakerWindow": { 135 | "MaskBtn": "القناع...", 136 | "GroupBtn": "المجموعة...", 137 | "RevertBtn": "استرجاع الايقونات الافتراضية", 138 | "BakeBtn": "دمج الأيقونات", 139 | "UploadState": "في انتظار...", 140 | "BakeState": "مطلوب التحميص", 141 | "ContinueProcessBtn": "المتابعة", 142 | "UploadBtn": "رفع الأيقونات المدمجة", 143 | "WindowTitle": "صانع الايقونات - صانع الاقنعة", 144 | "FreeMasksLinkLabel": "تنزيل اقنعة مجانية", 145 | "Warning": "تنبيه! سيتم استبدال الرمز على جهاز PS4", 146 | "ContinueProcessLabel": "إذا حدث أي خطأ أثناء تحميل الأيقونات، انقر على 'المتابعة' في أي وقت لاستكمال عملية رفع الايقونات.", 147 | "WarnWindowTitle": "متابعة عملية الرفع", 148 | "RevertToDefaultWindow": { 149 | "WinTitle": "تأكيد العودة إلى الافتراضي", 150 | "Msg1": "هل أنت متأكد أنك تريد العودة إلى الايقونات الافتراضية؟", 151 | "Msg2": "ستتم إعادة الأيقونات إلى النمط الافتراضي" 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /Data/Language/German.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "Deutsche", 3 | "Languages": { 4 | "German": "Deutsche", 5 | "English": "Englisch", 6 | "Arabic": "Arabisch", 7 | "Spanish": "Spanisch" 8 | }, 9 | "Settings": { 10 | "WindowTitle": "Einstellungen", 11 | "NoRadio": "AUS", 12 | "YesRadio": "EIN", 13 | "SaveBtn": "SPEICHERN", 14 | "CancelBtn": "ABBRECHEN", 15 | "IpLabel": "Standard-IP", 16 | "PortLabel": "Standard-Port", 17 | "DefaultBtn": "STANDARD", 18 | "FontLabel": "Standard-Schriftart", 19 | "HomebrewLabel": "HOMEBREW AKTIVIEREN", 20 | "LanguageLabel": "Standard-Sprache", 21 | "IconsPathLabel": "Standard-Icons-Pfad", 22 | "DownloadPathLabel": "Standard-Download-Pfad", 23 | "EnableHbInfo": "HINWEIS: Durch Aktivieren dieser Option können Sie Homebrew-Icons ändern, dies kann jedoch länger dauern, abhängig von der Anzahl der installierten Homebrews." 24 | }, 25 | "Iconit": { 26 | "GameIconsRadio": "Spiel-Icon/Bild", 27 | "SystemIconsRadio": "System-Icons", 28 | "AvatarIconsRadio": "Profilavatar [Deaktiviert]", 29 | "ModeLabel": "MODUS", 30 | "IpLabel": "PS4-IP", 31 | "CacheLabel": "Cache", 32 | "PortLabel": "PS4-Port", 33 | "StatusLabel": "Warten ...", 34 | "StatusLabel_success": "VERBUNDEN", 35 | "StatusLabel_fail": "VERBINDUNG FEHLGESCHLAGEN", 36 | "OnlineIconsLink": "Kostenlose Icons herunterladen", 37 | "PaypalLink": "Spenden (PayPal)", 38 | "TwitterLink": "Erstellt von", 39 | "SysIconsInfo": "Hinweis: Vollständige Lese-/Schreibberechtigungen erforderlich (PS4 Xplorer FTP durch Aktivieren des Gefahrenmodus)", 40 | "GameIconsInfo": "Hinweis: Sie können in den Einstellungen Homebrew-Icons aktivieren, bevor Sie sich mit der PS4 verbinden.", 41 | "TitleLink": "Iconit", 42 | "menuSettings": "Einstellungen", 43 | "About": "Über", 44 | "actionAbout": "Über", 45 | "Options": "Optionen...", 46 | "ConnectBtn": "PS4 verbinden", 47 | "Remove_cache": "Cache entfernen", 48 | "Special_thanks": "Besonderer Dank", 49 | "DownloadDatabase": "Datenbank herunterladen/aktualisieren" 50 | }, 51 | "Icons": { 52 | "NextBtn": "WEITER", 53 | "SelectBtn": "AUSWÄHLEN", 54 | "MaskBtn": "MASKEN-ERSTELLER...", 55 | "GameIdLabel": "AKTUELLE ID", 56 | "ChangeIconBtn": "ÄNDERN...", 57 | "IconSizeLabel": "ABMESSUNGEN", 58 | "SendBtn": "ICON/BILD", 59 | "TotalGamesLabel": "GESAMTZAHL DER SPIELE", 60 | "ChangeBgBtn": "SPIELBILD...", 61 | "IconLocationLabel": "ICON-STANDORT", 62 | "IconSizeTxt": "Aktuelle Icon-Abmessung", 63 | "TwitterLink": "Erstellt von @OfficialAhmed0", 64 | "PaypalLink": "PayPal", 65 | "LogsTxt": "Mit PS4 verbunden", 66 | "TitleLabel_GameIcons": "SPIEL-ICONS", 67 | "TitleLabel_SysIcons": "SYSTEM-ICONS", 68 | "HomebrewLabel_Y": "HOMEBREW-ICON: JA", 69 | "HomebrewLabel_N": "HOMEBREW-ICON: NEIN", 70 | "HomebrewLabel_T": "HOMEBREW-ICON: AUSGESCHALTET", 71 | "HomebrewLabel_Sys": "SYSTEM-ICON: JA", 72 | "IconLocation_In": "INTERN", 73 | "IconLocation_Ex": "EXTERN", 74 | "menuMore": "MENÜ", 75 | "setDefaultIcons": "ICON-GRUPPE FESTLEGEN", 76 | "ToolTips": { 77 | "NextBtn": "NÄCHSTES ICON", 78 | "MaskBtn": "MASKE FÜR DAS ICON ANWENDEN", 79 | "PreviousBtn": "VORHERIGES ICON", 80 | "ChangeIconBtn": "ICON ÄNDERN", 81 | "SelectBtn": "ICON AUS DER AUSKLAPPLISTE AUSWÄHLEN", 82 | "ChangeBgBtn": "HINTERGRUNDBILD FÜR DEN SPIELSTART ÄNDERN", 83 | "SendBtn": "ICON/HINTERGRUND HOCHLADEN", 84 | "IconLocationTxt": "DER ORT, AN DEM DAS AKTUELLE SPIEL SICH BEFINDET", 85 | "HomebrewLabel": "OB DIE AKTUELLE ID EIN HOMEBREW IST ODER NICHT", 86 | "GameTitleLabel": "EINIGE SPIELTITEL SIND UNBEKANNT. MÖGLICHERWEISE HANDELT ES SICH UM HOMEBREWS (PS2-SPIEL/UNITY-SPIEL) USW." 87 | }, 88 | "Logs": { 89 | "CorrectDim": "Bild mit korrekten Abmessungen", 90 | "SmallDim": "Bild kann nicht verwendet oder verkleinert werden (ZU KLEIN)", 91 | "ErrDim": "Bild kann nicht verwendet oder verkleinert werden (ZU GROSS), begrenzt auf", 92 | "LargeDim": "Bild wird verkleinert (ZU GROSS)", 93 | "BackupS": "Sicherung erfolgreich erstellt" 94 | } 95 | }, 96 | "Alerts": { 97 | "Thanks": "Vielen Dank, dass Sie Iconit verwenden", 98 | "About": "Iconit ist eine automatisierte Windows-Anwendung für gejailbreakte PS4-Konsolen, um XMB-Icons und Profil-Avatare mithilfe des FTP-Payloads zu ändern.", 99 | "AppVer": "App-Version", 100 | "DbSuccess": "Ungefähr 300 KB Datenbank erfolgreich heruntergeladen, das Cachen von Spielen wird nun schneller sein.", 101 | "SpecialThanks": "Besonderer Dank", 102 | "ThanksAll": "Vielen Dank an alle Entwickler in der Szene, die dies ermöglicht haben", 103 | "CacheRemoved": "Gecachte Icons wurden entfernt. Beim nächsten Verbinden mit der PS4 dauert das Cachen länger.", 104 | "InvalidCred": "Fehler: Iconit benötigt Administratorrechte, um die Aufgabe auszuführen. Führen Sie es als Administrator erneut aus.", 105 | "PermissionErr": "Ungültige IP oder Port (Buchstaben sind nicht erlaubt)", 106 | "SmallSize": "Ungültige Icon-Größe 'zu klein'. Mindestgröße erforderlich", 107 | "Disconnected": "Verbindung zur PS4 getrennt. FTP-Payload erneut aktivieren", 108 | "InvalidInput": "Überprüfen Sie die PS4-IP und den Port erneut.\nHinweis: Wenn Sie GoldHen FTP verwenden,\nstellen Sie sicher, dass Sie nicht mit einer anderen App mit der PS4 verbunden sind, da nur eine Verbindung zulässig ist.", 109 | "IsPS4": "Sind Sie sicher, dass Sie mit der PS4 verbunden sind?", 110 | "TimeOut": "Überprüfen Sie die IP und den Port erneut. DEV| TimeoutError", 111 | "ConnRefused": "PS4 hat die Verbindung verweigert, möglicherweise ist sie an einem anderen Ort verbunden. DEV| ConnectionRefusedError", 112 | "IncompleteProcess": "Es scheint, dass der letzte Upload-Vorgang unvollständig war. Klicken Sie auf die Schaltfläche 'Weiter', wenn Sie den Upload fortsetzen möchten." 113 | }, 114 | "SetDefaultIcons": { 115 | "caution": "VORSICHT! Lesen Sie sorgfältig", 116 | "optionInfo": "Diese Option verwendet den aktuellen Satz von Icons als Standardset. Es wird empfohlen, sicherzustellen, dass alle Icons das Originalicon/ohne Maske haben, da sonst die resultierenden Icons falsch sein werden.", 117 | "moreInfo": "Sie müssen diese Option nur einmal verwenden", 118 | "moreInfo2": "Wenn Sie neue Icons/Spiele installiert haben, führen Sie diese Option erneut aus. Andernfalls kann der MaskMaker sie nicht erkennen.", 119 | "moreInfo3": "Keine Sorge, ich werde Sie benachrichtigen, wenn ich neue Icons erkenne.", 120 | "new_icons_label": "NEUE ICONS NICHT IN [STANDARDBEREICH]", 121 | "default_group_btn": "STANDARDGRUPPE FESTLEGEN/UPDATE", 122 | "custom_group_btn": "BENUTZERDEFINIERTE GRUPPE FESTLEGEN" 123 | }, 124 | "ConfirmWindow": { 125 | "Ok": "OK", 126 | "No": "Nein", 127 | "Yes": "Ja", 128 | "SendingLabel": "SENDEN", 129 | "ValidationLabel": "VALIDIERUNG", 130 | "ConversionLabel": "UMWANDLUNG", 131 | "Question": "Möchten Sie fortfahren?", 132 | "Warning": "ACHTUNG! Dadurch wird das Icon auf der PS4 überschrieben" 133 | }, 134 | "MaskMakerWindow": { 135 | "MaskBtn": "Maske...", 136 | "GroupBtn": "Gruppe...", 137 | "RevertBtn": "Zurücksetzen auf Standard", 138 | "BakeBtn": "ICONS ERSTELLEN", 139 | "UploadState": "Warten...", 140 | "BakeState": "ERSTELLUNG ERFORDERLICH", 141 | "ContinueProcessBtn": "Fortsetzen", 142 | "UploadBtn": "HOCHLADEN DER ERSTELLTEN ICONS", 143 | "WindowTitle": "Iconit - Mask Maker", 144 | "FreeMasksLinkLabel": "Kostenlose Masken herunterladen", 145 | "Warning": "ACHTUNG! Dadurch wird das Icon auf der PS4 überschrieben", 146 | "ContinueProcessLabel": "Wenn beim Hochladen der Icons etwas schiefgeht, klicken Sie jederzeit auf 'Fortsetzen', um den Vorgang ab der Stelle fortzusetzen, an der er angehalten wurde.", 147 | "WarnWindowTitle": "Upload-Vorgang fortsetzen?", 148 | "RevertToDefaultWindow": { 149 | "WinTitle": "BESTÄTIGUNG DER RÜCKKEHR ZUM STANDARD", 150 | "Msg1": "Möchten Sie wirklich auf die Standardeinstellungen zurücksetzen?", 151 | "Msg2": "Icons auf den Standardstil zurücksetzen?" 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /Data/Language/Spanish.json: -------------------------------------------------------------------------------- 1 | { 2 | "Language": "Español", 3 | "Languages": { 4 | "Spanish": "Español", 5 | "English": "Inglés", 6 | "Arabic": "Árabe", 7 | "German": "Alemán" 8 | }, 9 | "Settings": { 10 | "WindowTitle": "Configuración", 11 | "NoRadio": "APAGADO", 12 | "YesRadio": "ENCENDIDO", 13 | "SaveBtn": "GUARDAR", 14 | "CancelBtn": "CANCELAR", 15 | "IpLabel": "IP POR DEFECTO", 16 | "PortLabel": "PUERTO POR DEFECTO", 17 | "DefaultBtn": "PREDETERMINADO", 18 | "FontLabel": "Fuente por Defecto", 19 | "HomebrewLabel": "HABILITAR HOMEBREW", 20 | "LanguageLabel": "IDIOMA POR DEFECTO", 21 | "IconsPathLabel": "RUTA DE ICONOS POR DEFECTO", 22 | "DownloadPathLabel": "RUTA DE DESCARGA POR DEFECTO", 23 | "EnableHbInfo": "NOTA: Al habilitar esta opción podrás cambiar los iconos de homebrew, pero el almacenamiento en caché llevará más tiempo dependiendo de cuántos homebrews tengas instalados." 24 | }, 25 | "Iconit": { 26 | "GameIconsRadio": "Icono de Juego/Foto", 27 | "SystemIconsRadio": "Iconos del Sistema", 28 | "AvatarIconsRadio": "Avatar de Perfil [Desactivado]", 29 | "ModeLabel": "MODO", 30 | "IpLabel": "IP de PS4", 31 | "CacheLabel": "Caché", 32 | "PortLabel": "Puerto de PS4", 33 | "StatusLabel": "Esperando...", 34 | "StatusLabel_success": "CONEXIÓN ESTABLECIDA", 35 | "StatusLabel_fail": "FALLO EN LA CONEXIÓN", 36 | "OnlineIconsLink": "Descargar Iconos Gratuitos", 37 | "PaypalLink": "Donar (PayPal)", 38 | "TwitterLink": "Creado por", 39 | "SysIconsInfo": "Nota: Se requieren permisos completos de R/W (PS4 Xplorer FTP al habilitar el modo peligroso)", 40 | "GameIconsInfo": "Nota: Puedes habilitar los iconos de homebrew en la configuración antes de conectar con la PS4", 41 | "TitleLink": "Iconit", 42 | "menuSettings": "Configuración", 43 | "About": "Acerca de", 44 | "actionAbout": "Acerca de", 45 | "Options": "Opciones...", 46 | "ConnectBtn": "Conectar con PS4", 47 | "Remove_cache": "Eliminar caché", 48 | "Special_thanks": "Agradecimientos especiales", 49 | "DownloadDatabase": "Descargar/Actualizar Base de Datos" 50 | }, 51 | "Icons": { 52 | "NextBtn": "SIGUIENTE", 53 | "SelectBtn": "SELECCIONAR", 54 | "MaskBtn": "CREADOR DE MÁSCARAS...", 55 | "GameIdLabel": "ID ACTUAL", 56 | "ChangeIconBtn": "CAMBIAR...", 57 | "IconSizeLabel": "DIMENSIONES", 58 | "SendBtn": "ICONO/FOTO", 59 | "TotalGamesLabel": "JUEGOS TOTALES", 60 | "ChangeBgBtn": "IMAGEN DE JUEGO...", 61 | "IconLocationLabel": "UBICACIÓN DEL ICONO", 62 | "IconSizeTxt": "Dimensión actual del icono", 63 | "TwitterLink": "Creado por @OfficialAhmed0", 64 | "PaypalLink": "PayPal", 65 | "LogsTxt": "Conectado a la PS4", 66 | "TitleLabel_GameIcons": "ICONOS DE JUEGO", 67 | "TitleLabel_SysIcons": "ICONOS DEL SISTEMA", 68 | "HomebrewLabel_Y": "ICONO DE HOMEBREW: SÍ", 69 | "HomebrewLabel_N": "ICONO DE HOMEBREW: NO", 70 | "HomebrewLabel_T": "ICONO DE HOMEBREW: APAGADO", 71 | "HomebrewLabel_Sys": "ICONO DEL SISTEMA: SÍ", 72 | "IconLocation_In": "INTERNO", 73 | "IconLocation_Ex": "EXTERNO", 74 | "menuMore": "MENÚ", 75 | "setDefaultIcons": "ESTABLECER GRUPO DE ICONOS", 76 | "ToolTips": { 77 | "NextBtn": "SIGUIENTE ICONO", 78 | "MaskBtn": "APLICAR MÁSCARA AL ICONO", 79 | "PreviousBtn": "ICONO ANTERIOR", 80 | "ChangeIconBtn": "CAMBIAR EL ICONO", 81 | "SelectBtn": "SELECCIONAR EL ICONO DE LA LISTA DESPLEGABLE", 82 | "ChangeBgBtn": "CAMBIAR IMAGEN DE FONDO PARA EL LANZAMIENTO DEL JUEGO", 83 | "SendBtn": "SUBIR EL ICONO/FONDO", 84 | "IconLocationTxt": "LA UBICACIÓN DONDE SE ENCUENTRA EL JUEGO ACTUAL", 85 | "HomebrewLabel": "SI EL ID ACTUAL ES HOMEBREW O NO", 86 | "GameTitleLabel": "ALGUNOS TÍTULOS DE JUEGOS SON DESCONOCIDOS. PUEDE QUE SEAN HOMEBREW (JUEGO DE PS2/UNITY) ETC." 87 | }, 88 | "Logs": { 89 | "CorrectDim": "Imagen en dimensiones correctas", 90 | "SmallDim": "La imagen no se puede utilizar ni cambiar de tamaño (DEMASIADO PEQUEÑA)", 91 | "ErrDim": "La imagen no se puede utilizar ni cambiar de tamaño (DEMASIADO GRANDE) limitado a", 92 | "LargeDim": "La imagen se cambiará de tamaño (DEMASIADO GRANDE)", 93 | "BackupS": "Copia de seguridad realizada correctamente" 94 | } 95 | }, 96 | "Alerts": { 97 | "Thanks": "Gracias por usar Iconit", 98 | "About": "Iconit es una aplicación automatizada para Windows en consolas PS4 con jailbreak para cambiar los iconos de XMB y los avatares de perfil con la ayuda de la carga útil de FTP", 99 | "AppVer": "Versión de la aplicación", 100 | "DbSuccess": "Base de datos descargada correctamente (aprox. 300 KB), el almacenamiento en caché de los juegos será más rápido ahora.", 101 | "SpecialThanks": "Agradecimientos especiales", 102 | "ThanksAll": "Gracias a todos los desarrolladores de la escena que hicieron esto posible", 103 | "CacheRemoved": "Se han eliminado los iconos en caché. Se tardará tiempo en almacenar en caché la próxima vez que te conectes a la PS4", 104 | "InvalidCred": "Error: Iconit necesita permisos de administrador para realizar la tarea. Ejecútalo de nuevo como administrador", 105 | "PermissionErr": "IP o puerto no válidos (no se permiten letras)", 106 | "SmallSize": "Tamaño de icono no válido, 'demasiado pequeño'. Tamaño mínimo requerido", 107 | "Disconnected": "Desconectado de la PS4. Vuelve a habilitar la carga útil de FTP", 108 | "InvalidInput": "Verifica la IP y el puerto de la PS4\n Nota: Si estás usando GoldHen FTP,\n asegúrate de no estar conectado a la PS4 con otra aplicación, ya que solo permite una conexión", 109 | "IsPS4": "¿Estás seguro de que estás conectado a la PS4?", 110 | "TimeOut": "Verifica la IP y el puerto | Error de tiempo de espera", 111 | "ConnRefused": "La PS4 se ha negado a conectar, tal vez esté conectada en otro lugar | Error de conexión rechazada", 112 | "IncompleteProcess": "Parece que el último proceso de carga no se completó. Haz clic en el botón 'continuar' si deseas continuar la carga" 113 | }, 114 | "SetDefaultIcons": { 115 | "caution": "¡PRECAUCIÓN! Lee detenidamente", 116 | "optionInfo": "Esta opción utilizará el conjunto actual de iconos como el conjunto predeterminado. Se recomienda asegurarse de que todos los iconos tengan el icono original/sin ninguna máscara, de lo contrario, los iconos resultantes serán incorrectos", 117 | "moreInfo": "Solo necesitas usar esta opción una vez", 118 | "moreInfo2": "Si tienes nuevos iconos/juegos instalados, ejecuta de nuevo esta opción; de lo contrario, el fabricante de máscaras no podrá detectarlos.", 119 | "moreInfo3": "No te preocupes, te informaré si detecto nuevos iconos", 120 | "new_icons_label": "NUEVOS ICONOS NO EN [GRUPO PREDETERMINADO]", 121 | "default_group_btn": "ESTABLECER/ACTUALIZAR GRUPO PREDETERMINADO", 122 | "custom_group_btn": "ESTABLECER GRUPO PERSONALIZADO" 123 | }, 124 | "ConfirmWindow": { 125 | "Ok": "Aceptar", 126 | "No": "No", 127 | "Yes": "Sí", 128 | "SendingLabel": "ENVIANDO", 129 | "ValidationLabel": "VALIDACIÓN", 130 | "ConversionLabel": "CONVERSIÓN", 131 | "Question": "¿Estás seguro de que deseas continuar?", 132 | "Warning": "¡ATENCIÓN! Esto sobrescribirá el icono en la PS4" 133 | }, 134 | "MaskMakerWindow": { 135 | "MaskBtn": "Máscara...", 136 | "GroupBtn": "Grupo...", 137 | "RevertBtn": "Restaurar a los valores predeterminados", 138 | "BakeBtn": "HORNEAR ICONOS", 139 | "UploadState": "Esperando...", 140 | "BakeState": "HORNADO REQUERIDO", 141 | "ContinueProcessBtn": "Continuar", 142 | "UploadBtn": "SUBIR ICONOS HORNEADOS", 143 | "WindowTitle": "Iconit - Fabricante de Máscaras", 144 | "FreeMasksLinkLabel": "Descargar Máscaras Gratis", 145 | "Warning": "¡ATENCIÓN! Esto sobrescribirá el icono en la PS4", 146 | "ContinueProcessLabel": "Si algo sale mal durante la carga de los iconos, haz clic en 'continuar' en cualquier momento para continuar el proceso desde donde se detuvo.", 147 | "WarnWindowTitle": "continuar proceso de carga", 148 | "RevertToDefaultWindow": { 149 | "WinTitle": "CONFIRMACIÓN DE RESTABLECER A VALORES PREDETERMINADOS", 150 | "Msg1": "¿Estás seguro de que quieres restablecer", 151 | "Msg2": "los iconos al estilo predeterminado?" 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /Interface/Mask.py: -------------------------------------------------------------------------------- 1 | import os 2 | from PyQt5 import QtCore, QtGui, QtWidgets 3 | from Module.Mask import Main as Mask 4 | from Module.Multi_upload import Main as multi_upload 5 | 6 | class Ui(Mask): 7 | 8 | def setupUi(self, window): 9 | 10 | window.resize(690, 573) 11 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(window) 12 | self.mainLayout = QtWidgets.QVBoxLayout() 13 | 14 | spacer_widget_min_exp = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) 15 | spacer_widget_exp_min = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 16 | 17 | spacerItem = spacer_widget_min_exp 18 | self.mainLayout.addItem(spacerItem) 19 | self.UpperLayout = QtWidgets.QGridLayout() 20 | self.MaskView = QtWidgets.QGraphicsView(window) 21 | self.MaskView.setMinimumSize(QtCore.QSize(250, 260)) 22 | self.MaskView.setMaximumSize(QtCore.QSize(250, 250)) 23 | self.UpperLayout.addWidget(self.MaskView, 5, 1, 1, 1) 24 | self.gridLayout_3 = QtWidgets.QGridLayout() 25 | self.GroupName = QtWidgets.QLabel(window) 26 | self.gridLayout_3.addWidget(self.GroupName, 0, 2, 1, 1) 27 | 28 | self.GroupBtn = QtWidgets.QPushButton(window) 29 | self.GroupBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 30 | self.gridLayout_3.addWidget(self.GroupBtn, 0, 0, 1, 1) 31 | 32 | self.RevertBtn = QtWidgets.QPushButton(window) 33 | self.RevertBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 34 | self.gridLayout_3.addWidget(self.RevertBtn, 1, 0, 1, 1) 35 | 36 | self.UpperLayout.addLayout(self.gridLayout_3, 3, 1, 1, 1) 37 | self.gridLayout_2 = QtWidgets.QGridLayout() 38 | self.MaskName = QtWidgets.QLabel(window) 39 | self.gridLayout_2.addWidget(self.MaskName, 0, 2, 1, 1) 40 | 41 | self.MaskBtn = QtWidgets.QPushButton(window) 42 | self.MaskBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 43 | self.gridLayout_2.addWidget(self.MaskBtn, 0, 0, 1, 1) 44 | self.UpperLayout.addLayout(self.gridLayout_2, 2, 1, 1, 1) 45 | self.gridLayout_5 = QtWidgets.QGridLayout() 46 | self.BakeState = QtWidgets.QLabel(window) 47 | self.gridLayout_5.addWidget(self.BakeState, 1, 1, 1, 1) 48 | self.UploadState = QtWidgets.QLabel(window) 49 | self.gridLayout_5.addWidget(self.UploadState, 2, 1, 1, 1) 50 | 51 | self.UploadBtn = QtWidgets.QPushButton(window) 52 | 53 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) 54 | sizePolicy.setHorizontalStretch(1) 55 | sizePolicy.setVerticalStretch(0) 56 | sizePolicy.setHeightForWidth(self.UploadBtn.sizePolicy().hasHeightForWidth()) 57 | self.UploadBtn.setSizePolicy(sizePolicy) 58 | self.UploadBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 59 | self.gridLayout_5.addWidget(self.UploadBtn, 2, 0, 1, 1) 60 | 61 | self.BakeBtn = QtWidgets.QPushButton(window) 62 | self.BakeBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 63 | self.gridLayout_5.addWidget(self.BakeBtn, 1, 0, 1, 1) 64 | spacerItem1 = spacer_widget_min_exp 65 | self.gridLayout_5.addItem(spacerItem1, 0, 0, 1, 1) 66 | self.UpperLayout.addLayout(self.gridLayout_5, 7, 1, 1, 1) 67 | spacerItem2 = spacer_widget_exp_min 68 | self.UpperLayout.addItem(spacerItem2, 5, 3, 1, 1) 69 | spacerItem3 = spacer_widget_exp_min 70 | self.UpperLayout.addItem(spacerItem3, 5, 0, 1, 1) 71 | 72 | self.BakedView = QtWidgets.QGraphicsView(window) 73 | self.BakedView.setMinimumSize(QtCore.QSize(250, 260)) 74 | self.BakedView.setMaximumSize(QtCore.QSize(250, 250)) 75 | self.UpperLayout.addWidget(self.BakedView, 5, 2, 1, 1) 76 | 77 | spacerItem4 = spacer_widget_min_exp 78 | self.UpperLayout.addItem(spacerItem4, 4, 1, 1, 1) 79 | self.mainLayout.addLayout(self.UpperLayout) 80 | spacerItem5 = spacer_widget_min_exp 81 | self.mainLayout.addItem(spacerItem5) 82 | 83 | self.line = QtWidgets.QFrame(window) 84 | self.line.setFrameShape(QtWidgets.QFrame.HLine) 85 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 86 | self.mainLayout.addWidget(self.line) 87 | 88 | self.ButtomLayout = QtWidgets.QGridLayout() 89 | self.ButtomLayout.setContentsMargins(-1, 17, -1, -1) 90 | self.ContinueProcessLabel = QtWidgets.QLabel(window) 91 | 92 | self.ButtomLayout.addWidget(self.ContinueProcessLabel, 0, 3, 1, 1) 93 | spacerItem6 = spacer_widget_exp_min 94 | self.ButtomLayout.addItem(spacerItem6, 0, 4, 1, 1) 95 | 96 | self.ContinueProcessBtn = QtWidgets.QPushButton(window) 97 | self.ContinueProcessBtn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor)) 98 | self.ButtomLayout.addWidget(self.ContinueProcessBtn, 0, 1, 1, 1) 99 | spacerItem7 = spacer_widget_exp_min 100 | 101 | self.ButtomLayout.addItem(spacerItem7, 0, 0, 1, 1) 102 | self.mainLayout.addLayout(self.ButtomLayout) 103 | self.CreditsLayout = QtWidgets.QHBoxLayout() 104 | 105 | spacerItem8 = spacer_widget_exp_min 106 | self.CreditsLayout.addItem(spacerItem8) 107 | 108 | self.IconitLinkLabel = QtWidgets.QLabel(window) 109 | self.FreeMasksLinkLabel = QtWidgets.QLabel(window) 110 | 111 | links = (self.IconitLinkLabel, self.FreeMasksLinkLabel) 112 | 113 | for indx, link in enumerate(links): 114 | 115 | if indx == 1: 116 | spacerItem9 = spacer_widget_exp_min 117 | self.CreditsLayout.addItem(spacerItem9) 118 | 119 | link.setOpenExternalLinks(True) 120 | self.CreditsLayout.addWidget(link) 121 | 122 | spacerItem10 = spacer_widget_exp_min 123 | self.CreditsLayout.addItem(spacerItem10) 124 | 125 | self.mainLayout.addLayout(self.CreditsLayout) 126 | self.verticalLayout_2.addLayout(self.mainLayout) 127 | 128 | 129 | """ 130 | ################################################################# 131 | BTN BEHAVIOR ON RENDER 132 | ################################################################# 133 | """ 134 | self.BakeBtn.setEnabled(False) 135 | self.UploadBtn.setEnabled(False) 136 | self.RevertBtn.setEnabled(False) 137 | 138 | 139 | self.retranslateUi(window) 140 | QtCore.QMetaObject.connectSlotsByName(window) 141 | 142 | 143 | def retranslateUi(self, window): 144 | 145 | win_name = "MaskMakerWindow" 146 | _translate = QtCore.QCoreApplication.translate 147 | 148 | window.setWindowTitle(_translate(win_name, self.translated_content.get("WindowTitle"))) 149 | self.MaskName.setText(_translate(win_name, "")) 150 | self.GroupName.setText(_translate(win_name, "")) 151 | 152 | self.BakeBtn.setText(_translate(win_name, self.translated_content.get("BakeBtn"))) 153 | self.MaskBtn.setText(_translate(win_name, self.translated_content.get("MaskBtn"))) 154 | self.GroupBtn.setText(_translate(win_name, self.translated_content.get("GroupBtn"))) 155 | self.RevertBtn.setText(_translate(win_name, self.translated_content.get("RevertBtn"))) 156 | self.UploadBtn.setText(_translate(win_name, self.translated_content.get("UploadBtn"))) 157 | self.ContinueProcessBtn.setText(_translate(win_name, self.translated_content.get("ContinueProcessBtn"))) 158 | 159 | self.BakeState.setText( 160 | _translate( 161 | win_name, 162 | self.html.span_tag( 163 | self.translated_content.get("BakeState"), 164 | self.constant.get_color('orange'), 165 | 8 166 | ) 167 | ) 168 | ) 169 | 170 | self.ContinueProcessLabel.setText( 171 | _translate( 172 | win_name, 173 | self.translated_content.get("ContinueProcessLabel") 174 | ) 175 | ) 176 | 177 | self.IconitLinkLabel.setText( 178 | _translate( 179 | win_name, 180 | self.html.a_tag( 181 | 'https://github.com/OfficialAhmed/Iconit-PS4/releases', 182 | 'Iconit', 183 | '#f250e7', 184 | 9 185 | ) 186 | ) 187 | ) 188 | 189 | self.FreeMasksLinkLabel.setText( 190 | _translate( 191 | win_name, 192 | self.html.a_tag( 193 | 'https://all-exhost.github.io/Masks.html', 194 | self.translated_content.get("FreeMasksLinkLabel"), 195 | '#f250e7', 196 | 9 197 | ) 198 | ) 199 | ) 200 | 201 | self.UploadState.setText( 202 | _translate( 203 | win_name, 204 | self.translated_content.get("UploadState") 205 | ) 206 | ) 207 | 208 | 209 | """ 210 | ################################################################# 211 | CONTINUE PROCESS - FEATURE 212 | ################################################################# 213 | """ 214 | self.ContinueProcessBtn.setEnabled(False) 215 | 216 | for baked in os.listdir(self.baked_path): 217 | if ".png" in baked: 218 | self.ContinueProcessBtn.setEnabled(True) 219 | self.alerts.display(self.translated_content.get("WarnWindowTitle"), "incompleteProcess") 220 | break 221 | 222 | 223 | """ 224 | ################################################################# 225 | SIGNALS 226 | ################################################################# 227 | """ 228 | self.BakeBtn.clicked.connect(self.bake_mask) 229 | self.MaskBtn.clicked.connect(self.browse_mask) 230 | self.GroupBtn.clicked.connect(self.browse_icon_group) 231 | self.RevertBtn.clicked.connect(self.revert_to_default) 232 | 233 | multi_upload_obj = multi_upload() 234 | self.UploadBtn.clicked.connect(lambda: multi_upload_obj.generate_and_upload_icons(self.selected_group_path)) 235 | self.ContinueProcessBtn.clicked.connect(lambda: multi_upload_obj.continue_upload(self.ContinueProcessBtn)) 236 | 237 | """ 238 | ################################################################# 239 | Set common widgets 240 | ################################################################# 241 | """ 242 | self.set_state_widget(self.UploadState) 243 | self.set_upload_btn_widget(self.UploadBtn) 244 | -------------------------------------------------------------------------------- /Module/Icons.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Methods for changing icons the class inherits the 'Environment' 4 | 5 | """ 6 | 7 | from environment import Common 8 | 9 | from PyQt5.QtWidgets import QFileDialog 10 | from PyQt5 import QtWidgets, QtGui 11 | from PIL import Image 12 | 13 | import Interface.Upload as Upload 14 | import Interface.Mask as Mask 15 | import Interface.Default_icons as Default_icons 16 | 17 | class Main(Common): 18 | def __init__(self) -> None: 19 | super().__init__() 20 | 21 | # setStyleSheet Url works only with forward slash (/) 22 | self.pref_path = self.pref_path.replace("\\", "/") 23 | 24 | self.icon_current_index = 0 25 | self.ui = self.get_ui() 26 | self.ids = self.get_ids() 27 | self.window = self.get_window() 28 | self.icons_limit = len(self.ids) 29 | 30 | self.last_browsed_path = "" 31 | self.translated_content: dict = self.translation.get_translation(self.language, "Icons") 32 | self.translated_logs: dict = self.translated_content.get("Logs") 33 | 34 | 35 | def next(self) -> None: 36 | """ Go to next node on the list of icons """ 37 | self.icon_current_index += 1 38 | 39 | # Go back to head, if tail's been reached 40 | if self.icon_current_index == self.icons_limit: 41 | self.icon_current_index = 0 42 | 43 | self.background_to_original() 44 | self.refresh_ui() 45 | 46 | 47 | def previous(self) -> None: 48 | """ Go to previous node on the list of icons """ 49 | 50 | # Go back to Tail, if head's been reached 51 | if self.icon_current_index == 0: 52 | self.icon_current_index = self.icons_limit - 1 53 | else: 54 | self.icon_current_index -= 1 55 | 56 | self.background_to_original() 57 | self.refresh_ui() 58 | 59 | 60 | def select(self) -> None: 61 | self.background_to_original() 62 | self.icon_current_index = self.GameTitles.currentIndex() 63 | self.refresh_ui(is_from_dropdown_list=True) 64 | 65 | 66 | def change_dimensions_label(self, color="white", bg_image="", size="(512, 512)") -> None: 67 | """ Determine the size of an icon for the label (icon dimensions)""" 68 | 69 | self.IconSizeTxt.setText(f"{self.translated_content.get('IconSizeTxt')} {size}") 70 | style = f"color: {color};" 71 | if bg_image != "": 72 | style = f"{self.html.bg_image(bg_image)} color:{color}" 73 | 74 | self.IconSizeTxt.setStyleSheet(style) 75 | 76 | 77 | def background_to_original(self) -> None: 78 | """ Reset the background to default """ 79 | 80 | self.change_dimensions_label() 81 | self.change_label_background(is_default=True) 82 | self.window.setStyleSheet(self.html.bg_image(f"{self.pref_path}{self.background}")) 83 | 84 | 85 | def is_valid_image(self, image_type, image_path) -> bool: 86 | """ Read and check image dimensions before proceding """ 87 | 88 | is_valid = False 89 | 90 | match image_type: 91 | case "icon": 92 | most_dimenstions = (1080, 720) 93 | least_dimensions = self.constant.get_ps4_icon_size() 94 | background = "" 95 | 96 | case "picture": 97 | most_dimenstions = (2040, 1920) 98 | least_dimensions = self.constant.get_ps4_pic_size() 99 | background = f"{self.pref_path}Black.@OfficialAhmed0" 100 | 101 | icon_dimensions = Image.open(image_path).size 102 | icon_x, icon_y = icon_dimensions[0], icon_dimensions[1] 103 | 104 | # Correct size 105 | if icon_dimensions == least_dimensions: 106 | self.change_dimensions_label(self.constant.get_color("green"), background, size=str(icon_dimensions)) 107 | self.logging += self.html.internal_log_msg("success", self.translated_logs.get("CorrectDim")) 108 | is_valid = True 109 | 110 | # Less than the required [CANNOT BE RESIZED] 111 | elif icon_x < least_dimensions[0] or icon_y < least_dimensions[1]: 112 | self.change_dimensions_label(self.constant.get_color("red"), size = str(icon_dimensions)) 113 | self.logging += self.html.internal_log_msg("error", self.translated_logs.get("SmallDim")) 114 | 115 | # Greater than the limit [CANNOT BE RESIZED] 116 | elif icon_x > most_dimenstions[0] or icon_y > most_dimenstions[1]: 117 | self.change_dimensions_label(self.constant.get_color("red"), size = str(icon_dimensions)) 118 | self.logging += self.html.internal_log_msg("error", f"{self.translated_logs.get('ErrDim')} {most_dimenstions}") 119 | 120 | # Otherwise [CAN BE RESIZED] 121 | else: 122 | self.change_dimensions_label(self.constant.get_color("orange"), background, str(icon_dimensions)) 123 | self.logging += self.html.internal_log_msg("warning", self.translated_logs.get('LargeDim')) 124 | is_valid = True 125 | 126 | self.update_internal_logs() 127 | return is_valid 128 | 129 | 130 | def refresh_ui(self, is_from_dropdown_list:bool = False) -> None: 131 | """ Update the UI icon and labels """ 132 | 133 | # Get stored window, maybe upload window has been rendered before 134 | self.window = self.get_window() 135 | 136 | self.SendBtn.setDisabled(True) 137 | self.current_game_id = self.icon_names[self.icon_current_index] 138 | 139 | icon_path = self.mode.get(self.get_selected_mode()).get('cache path') 140 | current_icon_path = icon_path.replace('\\', '//') + f"{self.current_game_id}.png" 141 | 142 | icon_index = f"{self.icon_current_index+1}" 143 | if is_from_dropdown_list: 144 | icon_index = f"{self.GameTitles.currentIndex() + 1}" 145 | 146 | match self.selected_mode: 147 | 148 | case "game": 149 | 150 | if self.is_toggled_homebrew == "True": 151 | hb_label = "HomebrewLabel_Y" if "CUSA" not in self.current_game_id else "HomebrewLabel_N" 152 | else: 153 | hb_label = "HomebrewLabel_T" 154 | 155 | 156 | if self.ids.get(self.current_game_id).get("location").upper() == "INTERNAL": 157 | icon_location = "In" 158 | else: 159 | icon_location = "Ex" 160 | 161 | hb = self.translated_content.get(hb_label) 162 | location = self.translated_content.get(f"IconLocation_{icon_location}") 163 | 164 | self.IconLocationTxt.setText(location) 165 | self.GameTitleLabel.setText(self.ids.get(self.current_game_id).get("title")) 166 | 167 | 168 | case "system apps": 169 | hb = self.translated_content.get("HomebrewLabel_Sys") 170 | self.GameTitleLabel.setText(self.ids.get(self.current_game_id)) 171 | 172 | self.HomebrewLabel.setText(hb) 173 | self.GameIdTxt.setText(self.current_game_id) 174 | self.TotalGamesTxt.setText(f"{icon_index}/{self.icons_limit}") 175 | self.Icon.setStyleSheet(self.html.border_image(current_icon_path)) 176 | 177 | 178 | def change_icon(self): 179 | img = self.render_browser_window("CHOOSE AN ICON TO BE DISPLAYED ON THE XMB") 180 | 181 | if img: 182 | if self.is_valid_image("icon", img): 183 | self.SendBtn.setDisabled(False) 184 | self.Icon.setStyleSheet(self.html.border_image(img)) 185 | self.set_browsed_icon_path(img) 186 | else: 187 | self.set_browsed_icon_path("") 188 | 189 | 190 | def change_picture(self): 191 | img = self.render_browser_window("CHOOSE A PICTURE TO BE DISPLAYED WHEN GAME LAUNCHES") 192 | 193 | if img: 194 | self.background_to_original() 195 | if self.is_valid_image("picture", img): 196 | self.SendBtn.setDisabled(False) 197 | self.window.setStyleSheet(self.html.bg_image(img)) 198 | self.change_label_background(is_default = False) 199 | self.set_browsed_pic_path(img) 200 | else: 201 | self.set_browsed_pic_path("") 202 | 203 | 204 | def change_label_background(self, is_default:bool = True) -> None: 205 | """ Change labels' background for more visible user PIC0 """ 206 | 207 | label_bg = ( 208 | self.NextBtn, 209 | self.MaskBtn, 210 | self.LogsTxt, 211 | self.SendBtn, 212 | self.SelectBtn, 213 | self.GameIdTxt, 214 | self.GameTitles, 215 | self.TitleLabel, 216 | self.PaypalLink, 217 | self.GameIdLabel, 218 | self.PreviousBtn, 219 | self.ChangeBgBtn, 220 | self.TwitterLink, 221 | self.IconSizeTxt, 222 | self.ChangeIconBtn, 223 | self.TotalGamesTxt, 224 | self.HomebrewLabel, 225 | self.IconSizeLabel, 226 | self.GameTitleLabel, 227 | self.TotalGamesLabel, 228 | self.IconLocationTxt, 229 | self.IconLocationLabel, 230 | ) 231 | 232 | style = "color:white" 233 | if not is_default: 234 | path = f"{self.pref_path}Black.@OfficialAhmed0" 235 | style = f"{self.html.bg_image(path)} {style}" 236 | 237 | for bg in label_bg: 238 | bg.setStyleSheet(style) 239 | 240 | 241 | def render_mask_maker_window(self): 242 | self.SendBtn.setEnabled(False) 243 | self.window = QtWidgets.QDialog() 244 | self.ui = Mask.Ui() 245 | self.ui.setupUi(self.window) 246 | self.window.show() 247 | 248 | """ 249 | overwrite the environment window, 250 | to keep make Iconit window reusable 251 | """ 252 | self.set_window(self.window) 253 | 254 | 255 | def render_set_default_icons_window(self): 256 | self.SendBtn.setEnabled(False) 257 | self.window = QtWidgets.QDialog() 258 | self.ui = Default_icons.Ui() 259 | self.ui.setupUi(self.window) 260 | self.window.show() 261 | 262 | self.set_window(self.window) 263 | 264 | 265 | def render_upload_window(self): 266 | self.background_to_original() 267 | self.SendBtn.setEnabled(False) 268 | 269 | self.set_current_game_id(self.current_game_id) 270 | 271 | self.window = QtWidgets.QWidget() 272 | self.ui = Upload.Ui() 273 | self.ui.setupUi(self.window) 274 | self.window.show() 275 | 276 | self.logging += self.html.internal_log_msg("success", f"{self.current_game_id} {self.translated_logs.get('BackupS')}.") 277 | self.update_internal_logs() 278 | 279 | 280 | def render_browser_window(self, window_title) -> str: 281 | """ Display window and return path of an object i.e. Image, Picture if user choose an object [RETURN ICON PATH]""" 282 | 283 | options = QtWidgets.QFileDialog.Options() 284 | options |= QtWidgets.QFileDialog.DontUseSheet 285 | dialog = QFileDialog() 286 | dialog.setOptions(options) 287 | dialog.setDirectory(self.icons_path) 288 | 289 | image_path, _ = QtWidgets.QFileDialog.getOpenFileName( 290 | None, 291 | window_title, 292 | self.last_browsed_path, 293 | self.constant.get_icon_supported_format(), 294 | options=options, 295 | ) 296 | 297 | return image_path 298 | 299 | 300 | def update_internal_logs(self): 301 | """ overwrite logs with the new lines """ 302 | 303 | self.LogsTxt.setHtml(self.logging) 304 | self.LogsTxt.moveCursor(QtGui.QTextCursor.End) -------------------------------------------------------------------------------- /environment.py: -------------------------------------------------------------------------------- 1 | """ 2 | Common: class share common methods and attributes 3 | across different software windows (Inherite to use) 4 | 5 | html: class holds repeated html tags and styling for the UI 6 | constant: class holds constant vars. Only getters 7 | """ 8 | from ftplib import FTP 9 | 10 | import os, datetime, shutil, json 11 | import concurrent.futures 12 | import Module.Alerts as Alerts 13 | 14 | from Module.Settings import Main as Settings 15 | from Module.Widget.Shared import Widget 16 | 17 | from Module.Widget.Translate import Translate 18 | from Module.Database.Generate import Game_Database, System_Database 19 | 20 | from Module.Constant.Html import Html 21 | from Module.Constant.Constants import Constants as Constant 22 | 23 | 24 | class Common: 25 | """ 26 | * A Bridge class to connect between multiple classes 'different window process' 27 | 28 | Class attributes accessable anywhere `SHARABLE ACCROSS CHILDS` 29 | - (Change attribute value) via setters 30 | - (Access attribute value) via direct call i.e. 'class_name.attr_name' 31 | 32 | init attributes are child specific. 33 | Childs of this class have a copy of those attributes `NOT SHARABLE ACCROSS CHILDS` 34 | - (Change attribute value) via self assignment & setters 35 | - (Access attribute value) via self call 36 | """ 37 | 38 | #__________ Settings.json default state _________ # 39 | app_path = os.getcwd() 40 | default_settings = {"font":"Arial", "port":"21", "ip":"", "icons_path":app_path, "download_path":app_path, "homebrew":"False", "language":"English"} 41 | 42 | 43 | #__________ shared attrs _________ # 44 | screen_w = 0 45 | screen_h = 0 46 | ui = None 47 | window = None 48 | 49 | 50 | #__________ Store connection for GoldHen one connection _________________ # 51 | connection = None 52 | selected_mode = None 53 | 54 | current_game_id = "" 55 | browsed_icon_path = "" 56 | browsed_pic_path = "" 57 | 58 | 59 | #__________ Common mask baker window widgets _________________ # 60 | state_widget = None 61 | upload_btn_widget = None 62 | 63 | 64 | #__________ Different modes mapping _________________ # 65 | mode:dict = { 66 | "game" : { 67 | "ids" : {}, 68 | "ignore ids" : {}, 69 | "ignored file" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\game\\ignored.json", 70 | "cache path" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\game" + "\\", 71 | "cache file" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\game\\games.json", 72 | "default group path" : f"{app_path}\\Data\\Cache\\Groups\\Backup\\", 73 | "database" : Game_Database(f"{app_path}\\Data\\Cache\\Icons\\metadata\\game\\Database.json") 74 | }, 75 | 76 | "system apps" : { 77 | "ids" : {}, 78 | "ignore ids" : {}, 79 | "ignored file" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\system apps\\ignored.json", 80 | "cache path" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\system apps" + "\\", 81 | "cache file" : f"{app_path}\\Data\\Cache\\Icons\\metadata\\system apps\\system_apps.json", 82 | "database" : System_Database(f"{app_path}\\Data\\Cache\\Icons\\metadata\\system apps\\Database.json") 83 | }, 84 | 85 | "avatar" : { 86 | "ids" : {}, 87 | "ignore ids" : {}, 88 | "cache path" : "", 89 | "cache file" : "", 90 | "database" : "" 91 | } 92 | } 93 | 94 | 95 | def __init__(self) -> None: 96 | self.app_version = "5.13" 97 | self.app_release_date = "July 14th, 2023" 98 | 99 | self.external_game_ids = [] 100 | self.screen_w = Common.screen_w 101 | self.screen_h = Common.screen_h 102 | 103 | self.app_root_path = f"{Common.app_path}" + "\\" 104 | self.data_path = f"{self.app_root_path}Data" + "\\" 105 | self.temp_path = f"{self.data_path}Cache" + "\\" 106 | self.groups_path = f"{self.temp_path}Groups" + "\\" 107 | self.groups_backup_path = f"{self.groups_path}Backup" + "\\" 108 | self.pref_path = f"{self.data_path}Preference" + "\\" 109 | self.icons_cache_path = f"{self.temp_path}Icons" + "\\" 110 | self.language_path = f"{self.data_path}Language" + "\\" 111 | self.appmeta_path = f"{self.data_path}User\\appmeta" + "\\" 112 | self.metadata_path = f"{self.temp_path}Icons\\metadata" + "\\" 113 | 114 | self.baked_path = f"{self.groups_path}Baked" + "\\" 115 | self.last_baked_group_file = f"{self.baked_path}last_process" 116 | self.default_group_file = f"{self.groups_path}Default.json" 117 | self.undetected_games_file = f"{self.app_root_path}GAMES MADE CACHING SLOWER.txt" 118 | self.setting_path = "" 119 | 120 | self.ftp:FTP = FTP() 121 | self.html:Html = Html() 122 | self.widgets:Widget = Widget() 123 | self.constant:Constant = Constant() 124 | self.settings:Settings = Settings() 125 | 126 | self.settings.init(self.temp_path, self.language_path, self.default_settings, is_for_local_attr=True) 127 | self.translation = Translate(self.language_path) 128 | 129 | 130 | self.ps4_system_icons_dir = self.constant.get_system_icons_path() 131 | self.ps4_internal_icons_dir = self.constant.get_internal_icons_path() 132 | self.ps4_external_icons_dir = self.constant.get_external_icons_path() 133 | 134 | self.backup_path = f"{self.constant.get_backup_folder_name()}" + "\\" 135 | 136 | self.settings_cache:dict = self.settings.update_local_cache(self.temp_path) 137 | self.ip:str = self.settings_cache.get("ip") 138 | self.font:str = self.settings_cache.get("font") 139 | self.port:str = self.settings_cache.get("port") 140 | self.language:str = self.settings_cache.get("language") 141 | self.icons_path:str = self.settings_cache.get("icons_path") 142 | self.is_toggled_homebrew:str = self.settings_cache.get("homebrew") 143 | self.download_path:str = self.settings_cache.get("download_path") 144 | 145 | self.alerts = Alerts.Main(self.translation, self.language) 146 | self.logging = self.html.internal_log_msg("ps4", self.ip, 12, "font-weight:600; font-style:italic;") 147 | 148 | 149 | def get_ps4_game_location(self, game_id:str) -> str: 150 | """ 151 | if game exists internally return empty, else external 152 | """ 153 | 154 | folder = "" 155 | if self.get_ids().get(game_id).get("location") == "External": 156 | folder = "/external/" 157 | 158 | return f"{self.ps4_internal_icons_dir}{folder}{game_id}" 159 | 160 | 161 | def log_to_external_file(self, description:str, Type:str) -> None: 162 | """ 163 | Write info about the issue to external file 164 | """ 165 | 166 | data = lambda : f"{datetime.datetime.now()} | _DEV {Type.upper()}: {description}\n" 167 | 168 | try: error_file = open("Logs.txt", "a") 169 | except: error_file = open("Logs.txt", "w+") 170 | error_file.write(data()) 171 | 172 | 173 | def get_server_list(self, list:str = "directories") -> tuple: 174 | """ 175 | This is a solution since PS4 ftp doesnt support nlst(). 176 | * list: files = name of files if any 177 | * list: directories = directories names if any 178 | """ 179 | result = [] 180 | command = lambda line : result.append(line.split(" ")[-1]) 181 | 182 | if list == "files": 183 | command = lambda line : result.append(line.split(" ")[-1]) if line[:2] == "-r" else None 184 | 185 | self.ftp.retrlines("LIST ", command) 186 | return tuple(result) 187 | 188 | 189 | def download_data_from_server(self, file_name:str, file_path_with_extension:str) -> bool: 190 | """ 191 | download icons/xml etc from PS4 192 | """ 193 | 194 | try: 195 | with open(file_path_with_extension, "wb") as downloaded_file: 196 | self.ftp.retrbinary("RETR " + file_name, downloaded_file.write, 5120) 197 | return True 198 | 199 | except: 200 | return False 201 | 202 | 203 | def progress_bar(self, bar, percentage:int) -> None: 204 | """ 205 | Refresh the bar object by the passed percentage 206 | """ 207 | 208 | bar.setProperty("value", percentage) 209 | 210 | 211 | def backup_icons(self, src, dest, ids) -> None: 212 | """ 213 | copy icons using threads 214 | """ 215 | 216 | store_icon = lambda id: shutil.copy(f"{src}{id}.png", f"{dest}\\{id}.png") 217 | 218 | with concurrent.futures.ThreadPoolExecutor() as executor: 219 | # Submit each image as a separate task - if the icon not found in backup pass it to a thread to copy 220 | tasks = [executor.submit(store_icon, id) for id in ids if not os.path.exists(f"{dest}\\{id}.png")] 221 | 222 | # Wait for all tasks to complete on the thread 223 | concurrent.futures.wait(tasks) 224 | 225 | 226 | def read_json(self, json_path:str) -> dict: 227 | """ 228 | Return JSON object as dictionary 229 | Takes only path as str, no streaming 230 | """ 231 | 232 | return json.load(open(json_path)) 233 | 234 | 235 | def get_language(self) -> str: 236 | return Common.language 237 | 238 | def set_language(self, lang) -> None: 239 | Common.language = lang 240 | 241 | def get_window(self): 242 | return Common.window 243 | 244 | def set_window(self, ptr) -> None: 245 | Common.window = ptr 246 | 247 | def set_ui(self, ptr) -> None: 248 | Common.ui = ptr 249 | 250 | def get_ui(self): 251 | return Common.ui 252 | 253 | def set_ftp(self, ptr) -> None: 254 | Common.connection = ptr 255 | 256 | def get_ftp(self): 257 | return Common.connection 258 | 259 | def set_current_game_id(self, id:str) -> None: 260 | Common.current_game_id = id 261 | 262 | def get_current_game_id(self) -> str: 263 | return Common.current_game_id 264 | 265 | def set_browsed_pic_path(self, path:str) -> None: 266 | Common.browsed_pic_path = path 267 | 268 | def get_browsed_pic_path(self) -> str: 269 | return Common.browsed_pic_path 270 | 271 | def set_browsed_icon_path(self, path:str) -> None: 272 | Common.browsed_icon_path = path 273 | 274 | def get_browsed_icon_path(self) -> str: 275 | return Common.browsed_icon_path 276 | 277 | def set_ids(self, ids:dict) -> None: 278 | Common.mode.get(self.get_selected_mode())["ids"] = ids 279 | 280 | def get_ids(self) -> dict: 281 | return Common.mode.get(self.get_selected_mode()).get("ids") 282 | 283 | def set_selected_mode(self, mode:str) -> None: 284 | Common.selected_mode = mode 285 | 286 | def get_selected_mode(self) -> str: 287 | return Common.selected_mode 288 | 289 | def set_ip_port(self, ip, port) -> None: 290 | Common.default_settings["ip"], self.ip = ip, ip 291 | Common.default_settings["port"], self.port = port, port 292 | 293 | def get_ip(self) -> str: 294 | return Common.default_settings.get("ip") 295 | 296 | def get_port(self) -> int: 297 | return int(Common.default_settings["port"]) 298 | 299 | def set_screen_size(self, w, h) -> None: 300 | Common.screen_w = w 301 | Common.screen_h = h 302 | 303 | def set_state_widget(self, widget:Widget): 304 | Common.state_widget = widget 305 | 306 | def get_state_widget(self) -> Widget: 307 | return Common.state_widget 308 | 309 | def set_upload_btn_widget(self, widget:Widget): 310 | Common.upload_btn_widget = widget 311 | 312 | def get_upload_btn_widget(self) -> Widget: 313 | return Common.upload_btn_widget -------------------------------------------------------------------------------- /Module/Mask.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | from PyQt5 import QtWidgets 3 | from threading import Lock 4 | from environment import Common 5 | from Module.Multi_upload import Main as multi_uploader 6 | from PyQt5.QtWidgets import QFileDialog 7 | 8 | import os, shutil, concurrent.futures 9 | 10 | 11 | class Main(Common): 12 | 13 | def __init__(self) -> None: 14 | super().__init__() 15 | 16 | self.group_ids = {} 17 | self.last_browse_path = "" 18 | self.game_icon_location = "" 19 | self.mask_is_changed = False 20 | self.group_icons_is_changed = False 21 | self.preview_icon_path = self.pref_path.replace('\\', '/') 22 | self.ps4_icon_dimension = self.constant.get_ps4_icon_size() 23 | self.multi_uploader = multi_uploader() 24 | 25 | win_name = "MaskMakerWindow" 26 | self.translated_content: dict = self.translation.get_translation(self.language, win_name) 27 | 28 | 29 | def quit(self): 30 | exit() 31 | 32 | 33 | def show_mask(self) -> None: 34 | self.MaskView.setStyleSheet(f"border-image: url({self.mask_location}mask-style.png);") 35 | 36 | 37 | def show_baked_icon(self): 38 | path = self.temp_path.replace('\\', '/') 39 | self.BakedView.setStyleSheet(f"border-image: url({path}preview.png);") 40 | 41 | 42 | def bake_preview_icon(self) -> None: 43 | """ 44 | Bake a temp icon for preview with chosen mask 45 | """ 46 | 47 | style = Image.open(f"{self.temp_path}mask-style.png") 48 | cover = Image.open(f"{self.temp_path}mask.png").convert("L") 49 | mask = style.copy() 50 | 51 | self.apply_mask("preview", cover, mask) 52 | 53 | 54 | def validate_baking(self) -> None: 55 | """ 56 | Enable/Disable the baking button 57 | """ 58 | 59 | enable = False 60 | if self.group_icons_is_changed and self.mask_is_changed: 61 | 62 | self.bake_preview_icon() 63 | self.show_baked_icon() 64 | enable = True 65 | 66 | self.BakeBtn.setEnabled(enable) 67 | 68 | 69 | def check_bake_state(self): 70 | """ 71 | Check if all baked icons of selected group found 72 | """ 73 | 74 | is_found_baked = False 75 | try: group_ids = self.read_json(self.selected_group_path) 76 | except: group_ids = [] 77 | 78 | # Only check if group ids not empty 79 | if group_ids: 80 | 81 | for file in os.listdir(self.baked_path): 82 | 83 | if file[-4:] == '.png': 84 | 85 | # at least one icon not found terminate 86 | if file[:-4] not in group_ids: 87 | is_found_baked = False 88 | break 89 | 90 | is_found_baked = True 91 | 92 | if is_found_baked: 93 | clr = "green" 94 | baking_state = "BAKED ICONS FOUND" 95 | 96 | else: 97 | clr = "red" 98 | baking_state = "BAKING REQUIRED" 99 | 100 | self.UploadBtn.setEnabled(is_found_baked) 101 | self.UploadState.setText(self.html.span_tag(baking_state, self.constant.get_color(clr), 8)) 102 | 103 | 104 | def browse_icon_group(self) -> None: 105 | """ 106 | Get the json group chosen by the user 107 | """ 108 | 109 | self.group_icons_is_changed = False 110 | options = QtWidgets.QFileDialog.Options() 111 | options |= QtWidgets.QFileDialog.DontUseSheet 112 | options |= QtWidgets.QFileDialog.ReadOnly 113 | dialog = QFileDialog() 114 | dialog.setDirectory(self.groups_path) 115 | dialog.setOptions(options) 116 | 117 | group_path, _ = QtWidgets.QFileDialog.getOpenFileName( 118 | None, 119 | "Choose a group for the mask", 120 | self.groups_path, 121 | "json(*.json)", 122 | options=options, 123 | ) 124 | 125 | if group_path and group_path.split('/')[-2] == 'Groups': 126 | 127 | self.RevertBtn.setEnabled(True) 128 | self.selected_group = group_path.split('/')[-1] 129 | self.selected_group_path = group_path 130 | 131 | self.check_bake_state() 132 | self.BakeState.setText("Baking mask required") 133 | self.BakedView.setStyleSheet(f'border-image: url({self.preview_icon_path}previewTest.@OfficialAhmed0);') 134 | 135 | self.group_icons_is_changed = True 136 | self.GroupName.setText(self.selected_group) 137 | self.group_ids: dict = self.read_json(group_path) 138 | 139 | else: 140 | 141 | self.BakedView.setStyleSheet('') 142 | QtWidgets.QMessageBox.warning(None, "Error", f"Please select an icon-group from {self.groups_path}") 143 | 144 | self.validate_baking() 145 | 146 | 147 | def browse_mask(self) -> None: 148 | """ 149 | Allow Zip files to be selected through a new rendered window 150 | """ 151 | 152 | self.mask_is_changed = False 153 | options = QtWidgets.QFileDialog.Options() 154 | options |= QtWidgets.QFileDialog.DontUseSheet 155 | dialog = QFileDialog() 156 | dialog.setOptions(options) 157 | 158 | mask_location, _ = QtWidgets.QFileDialog.getOpenFileName( 159 | None, 160 | "CHOOSE MASK FOR THE ICONS", 161 | self.last_browse_path, 162 | "Zip(*.zip)", 163 | options=options, 164 | ) 165 | 166 | if mask_location: 167 | 168 | self.last_browse_path = mask_location 169 | 170 | # 120Kb size limit for ZIP archives 171 | if os.path.getsize(mask_location) <= 120000: 172 | 173 | try: 174 | # Check the unpacked zip, if compatible and contain masks 175 | shutil.unpack_archive(mask_location, self.temp_path, "zip") 176 | 177 | if os.path.exists(f"{self.temp_path}\\mask-style.png"): 178 | 179 | img_location = self.temp_path.replace('\\', '/') 180 | self.MaskName.setText(mask_location.split('/')[-1]) 181 | self.mask_location = img_location 182 | self.mask_is_changed = True 183 | self.show_mask() 184 | 185 | else: 186 | 187 | self.BakeState.setText("Invalid mask file") 188 | self.log_to_external_file("Invalid mask file", "Error") 189 | 190 | except Exception as e: 191 | self.log_to_external_file(str(e), "Error") 192 | 193 | else: 194 | self.BakeState.setText("ZIP file too large") 195 | 196 | else: 197 | self.MaskView.setStyleSheet('') 198 | 199 | self.validate_baking() 200 | 201 | 202 | def revert_to_default(self): 203 | """ 204 | Get selected group to `default style` from default cached icons 205 | 206 | * ##### User will be prompted before proceeding 207 | """ 208 | 209 | translated_content: dict = self.translated_content.get("RevertToDefaultWindow") 210 | 211 | is_confirmed = self.alerts.alert( 212 | translated_content.get("WinTitle"), 213 | f"{translated_content.get('Msg1')} *{self.selected_group}* {translated_content.get('Msg2')}" 214 | ) 215 | 216 | if is_confirmed: 217 | 218 | self.RevertBtn.setEnabled(False) 219 | selected_group = self.read_json(self.selected_group_path) 220 | 221 | # Copy group icons from cache to baked folder 222 | for id in selected_group: 223 | 224 | shutil.copy( 225 | f"{self.mode.get(self.get_selected_mode()).get('default group path')}{id}.png", 226 | f"{self.baked_path}{id}.png" 227 | ) 228 | 229 | if self.multi_uploader.generate_and_upload_icons(self.selected_group_path): 230 | title = "SUCCESSFULL REVERT" 231 | msg = f"*{self.selected_group}* has been successfully reverted to default" 232 | 233 | else: 234 | title = "UNSUCCESSFULL REVERT" 235 | msg = f"Something went wrong. Please refer to Logs.txt" 236 | 237 | self.alerts.alert( 238 | title, 239 | msg, 240 | False 241 | ) 242 | 243 | 244 | def apply_mask(self, id, cover, mask:Image, lock=None): 245 | """ 246 | - Shrink icon while keeping aspect ratio `(512, 512)` 247 | by pasting the icon/mask on transparent image 248 | - i.e. shrink size `(412, 412)` - trasparent `(100, 100)` - icon `(512, 512)` 249 | 250 | - Apply mask on style according to the cover(B&W photo) 251 | """ 252 | 253 | vertical_shift = 0 254 | horizontal_shift = 0 255 | width, height = self.ps4_icon_dimension 256 | transparent_icon = Image.new("RGBA", self.ps4_icon_dimension, (0, 0, 0, 0)) 257 | 258 | if id == "preview": 259 | game_icon = Image.open(f"{self.preview_icon_path}previewTest.@OfficialAhmed0") 260 | else: 261 | game_icon = Image.open(f"{self.temp_path}Groups\\Backup\\{id}.png") 262 | 263 | try: 264 | # Reading mask specifications from JSON 265 | info = self.read_json(f"{self.temp_path}set.json") 266 | 267 | vertical_shift = int(info.get("vertical_shift")) 268 | horizontal_shift = int(info.get("horizontal_shift")) 269 | width, height = int(info.get("width")), int(info.get("height")) 270 | except: pass 271 | 272 | resized_game_icon = game_icon.resize((width, height)) 273 | resized_game_icon_x = resized_game_icon.size[0] 274 | resized_game_icon_y = resized_game_icon.size[1] 275 | 276 | if vertical_shift or horizontal_shift: 277 | 278 | # Set Shift to 0 if not included in the mask 279 | vertical_shift = 0 if not vertical_shift else vertical_shift 280 | horizontal_shift = 0 if not horizontal_shift else horizontal_shift 281 | 282 | position = ((horizontal_shift, vertical_shift)) 283 | 284 | else: 285 | 286 | # Calculate the center point to center-align the shrunken icon 287 | center_point = ( (512 - resized_game_icon_x) // 2, (512 - resized_game_icon_y) // 2 ) 288 | position = center_point 289 | 290 | transparent_icon.paste(resized_game_icon, position) 291 | 292 | mask_copy = mask.copy() 293 | mask_copy.paste(transparent_icon, (0, 0), cover) 294 | 295 | if lock: 296 | # One thread writing to the system at a time 297 | with lock: 298 | mask_copy.save(f"{self.baked_path}{id}.png") 299 | 300 | else: 301 | # Preview baked 302 | mask_copy.save(f"{self.temp_path}preview.png") 303 | 304 | 305 | def bake_mask(self) -> None: 306 | """ 307 | #### Low-level implementation 308 | 309 | * Baking icon & mask concurrently using Threads 310 | """ 311 | 312 | try: 313 | 314 | # Lock - Restrict to one thread at a time writing to memory to prevent race conditions/synchronization issues 315 | lock = Lock() 316 | 317 | with concurrent.futures.ThreadPoolExecutor() as executor: 318 | 319 | style = Image.open(f"{self.temp_path}mask-style.png") 320 | cover = Image.open(f"{self.temp_path}mask.png").convert("L") 321 | mask = style.copy() 322 | 323 | # Apply mask for all ids 324 | tasks = [executor.submit(self.apply_mask, id, cover, mask, lock) for id in self.group_ids] 325 | 326 | concurrent.futures.wait(tasks) 327 | 328 | self.BakeState.setText("DONE!") 329 | self.UploadBtn.setEnabled(True) 330 | 331 | except Exception as e: 332 | self.BakeState.setText("Error baking mask, read logs.txt") 333 | self.log_to_external_file(str(e), "Error") 334 | 335 | finally: 336 | self.BakeBtn.setEnabled(False) 337 | -------------------------------------------------------------------------------- /Module/Upload.py: -------------------------------------------------------------------------------- 1 | 2 | from PIL import Image 3 | from environment import Common 4 | from traceback import extract_stack 5 | import os, shutil, PIL, datetime 6 | import Common.Uploader as Uploader 7 | 8 | class Main(Common): 9 | 10 | def __init__(self) -> None: 11 | super().__init__() 12 | self.ftp = self.get_ftp() 13 | self.game_ids: dict = self.get_ids() 14 | self.pics_to_upload: list = [] 15 | self.icons_to_upload: list = [] 16 | self.current_game_id = self.get_current_game_id() 17 | self.browsed_pic_path = self.get_browsed_pic_path() 18 | self.browsed_icon_path = self.get_browsed_icon_path() 19 | 20 | self.current_game_icons = [] 21 | 22 | self.multi_uploader = Uploader.Main() 23 | 24 | 25 | def start_processing(self) -> None: 26 | """ 27 | take the user image, resize and duplicate it according to the mode selected 28 | """ 29 | 30 | try: 31 | self.image = None 32 | 33 | self.Yes.setEnabled(False) 34 | self.No.setEnabled(False) 35 | 36 | match self.selected_mode: 37 | case "system apps": 38 | self.generate_system_icons() 39 | case "game": 40 | self.generate_game_icons() 41 | case "avatar": 42 | self.generate_avatar_icons() 43 | 44 | self.progress_bar(self.ConversionBar, 100) 45 | 46 | self.send_generated_images() 47 | self.remove_generated_images() 48 | 49 | self.progress_bar(self.SendingBar, 100) 50 | 51 | self.No.hide() 52 | self.Yes.hide() 53 | self.Ok.raise_() 54 | 55 | except ConnectionResetError: 56 | self.ftp = self.get_ftp() 57 | self.ftp.connect(self.get_ip(), self.get_port()) 58 | self.ftp.login("", "") 59 | self.set_ftp(self.ftp) 60 | 61 | except FileNotFoundError: pass 62 | 63 | except Exception as e: 64 | self.update_message(False, "Encountered a problem while resizing icon", extract_stack(), str(e) + f"{e} : ") 65 | 66 | finally: 67 | self.set_browsed_pic_path("") 68 | self.set_browsed_icon_path("") 69 | 70 | 71 | def get_timestamp(self) -> str: 72 | """ 73 | To avoid NameExists for the backup, rename it to current time 74 | """ 75 | 76 | date = datetime.datetime.now() 77 | time = date.time() 78 | 79 | return f"{date.day}_{date.month}_{time.hour}_{time.minute}-{time.microsecond}" 80 | 81 | 82 | def backup_icon(self) -> None: 83 | """ 84 | Copy icon from local cache for backup 85 | """ 86 | 87 | icon_name = f"{self.current_game_id}.png" 88 | backup_icon_name = f"{self.get_timestamp()}.png" 89 | icon_path = f"{self.metadata_path}{self.selected_mode}\\{icon_name}" 90 | 91 | try: 92 | shutil.copyfile(icon_path, f"{self.backup_path}{backup_icon_name}") 93 | 94 | except Exception as e: 95 | self.log_to_external_file(f"{e} | TRACEBACK {extract_stack()}", "Error") 96 | 97 | 98 | def generate_system_icons(self) -> None: 99 | """ 100 | Resize system icons and append them into the list for the uploading method 101 | """ 102 | 103 | self.ftp.cwd(f"/{self.ps4_system_icons_dir}{self.current_game_id}/sce_sys") 104 | game_files = self.get_server_list(list="files") 105 | 106 | icon_name = "icon0.png" 107 | is_icon_4k = False 108 | self.icons_to_upload = [icon_name] 109 | 110 | if "icon0_4k.png" in game_files: 111 | icon_name = "icon0_4k.png" 112 | self.icons_to_upload.append(icon_name) 113 | is_icon_4k = True 114 | 115 | with open(f"{self.temp_path}Icons\\icon0.png", "wb") as picture: 116 | self.ftp.retrbinary("RETR " + icon_name, picture.write) 117 | 118 | self.backup_icon() 119 | self.progress_bar(self.ValidationBar, 100) 120 | 121 | if self.browsed_icon_path: 122 | icon = Image.open(self.browsed_icon_path) 123 | 124 | icon.resize(self.constant.get_ps4_icon_size(), PIL.Image.ANTIALIAS) 125 | icon.save(self.icons_cache_path + "icon0.png") 126 | self.progress_bar(self.ConversionBar, 50) 127 | 128 | if is_icon_4k: 129 | icon.resize(self.constant.get_ps4_4k_icon_size(), PIL.Image.ANTIALIAS) 130 | icon.save(self.icons_cache_path + "icon0_4k.png") 131 | 132 | 133 | def generate_game_icons(self) -> None: 134 | """ 135 | Resize game icons and append them into the list for the uploading method 136 | """ 137 | 138 | game_directory = self.get_ps4_game_location(self.current_game_id) 139 | self.ftp.cwd(f"/{game_directory}") 140 | 141 | self.current_game_icons = self.game_ids.get(self.current_game_id).get("icons") 142 | 143 | self.progress_bar(self.ValidationBar, 100) 144 | 145 | if self.browsed_pic_path or self.browsed_icon_path: 146 | 147 | # Backup changed images before modification 148 | self.backup_icon() 149 | 150 | if self.browsed_icon_path: 151 | icon = Image.open(self.browsed_icon_path) 152 | resized_icon = icon.resize(self.constant.get_ps4_icon_size(), PIL.Image.ANTIALIAS) 153 | 154 | if self.browsed_pic_path: 155 | pic = Image.open(self.browsed_pic_path) 156 | resized_pic = pic.resize(self.constant.get_ps4_pic_size(), PIL.Image.ANTIALIAS) 157 | 158 | progress = 0 159 | progress_weight = 100 // len(self.current_game_icons) 160 | 161 | for current_image in self.current_game_icons: 162 | 163 | icon_cache_path = f"{self.icons_cache_path}{current_image}" 164 | icon_cache_path_no_extension = icon_cache_path[:-4] 165 | 166 | """ 167 | ###################################################################### 168 | Icon / Pic has changed, generate required NO. of icons 169 | ###################################################################### 170 | """ 171 | if ".png" not in current_image and ".dds" not in current_image: 172 | # ignore extensions other than png/dds 173 | continue 174 | 175 | if ".png" in current_image: 176 | 177 | if self.browsed_icon_path and "icon" in current_image: 178 | resized_icon.save(icon_cache_path) 179 | 180 | if self.browsed_pic_path and 'pic' in current_image: 181 | resized_pic.save(icon_cache_path) 182 | 183 | elif ".dds" in current_image: 184 | # Creat a temp .png, then convert it to .dds 185 | 186 | if self.browsed_icon_path and "icon" in current_image: 187 | resized_icon.save(f"{icon_cache_path_no_extension}.png") 188 | self.multi_uploader.png_to_dds(f"{icon_cache_path_no_extension}.png", self.icons_cache_path) 189 | 190 | if self.browsed_pic_path and 'pic' in current_image: 191 | resized_pic.save(f"{icon_cache_path_no_extension}.png") 192 | self.multi_uploader.png_to_dds(f"{icon_cache_path_no_extension}.png", self.icons_cache_path) 193 | 194 | 195 | if self.browsed_icon_path and "icon" in current_image: 196 | self.icons_to_upload.append(current_image) 197 | 198 | if self.browsed_pic_path and 'pic' in current_image: 199 | self.pics_to_upload.append(current_image) 200 | 201 | progress += progress_weight 202 | self.progress_bar(self.ConversionBar, progress) 203 | 204 | 205 | def generate_avatar_icons(self): 206 | """ 207 | Resize avatar icons and append them into the list for the uploading method 208 | """ 209 | 210 | # FIXME: OLD IMPLEMENTATION. TO BE FIXED IN ANOTHER UPDATE. 211 | 212 | sysProfileRoot = "system_data/priv/cache/profile/" 213 | try: 214 | """ 215 | ############################################################### 216 | ####### Resize Icon and make copies 217 | ############################################################### 218 | """ 219 | required_dds = ("avatar64", "avatar128", "avatar260", "avatar440") 220 | ResizeImg = Image.open(self.browsed_icon_path) 221 | avatar = ResizeImg.resize((440, 440), PIL.Image.ANTIALIAS) 222 | avatar.save(self.temp_path + "avatar.png") 223 | self.CheckingBar.setProperty("value", 20) 224 | progress = 20 225 | progressed = 60 226 | for dds in required_dds: 227 | if "64" in dds: 228 | avatar = ResizeImg.resize((64, 64), PIL.Image.ANTIALIAS) 229 | avatar.save(self.temp_path + "avatar64.png") 230 | self.CheckingBar.setProperty("value", 40) 231 | else: 232 | s = int(dds[-3:]) 233 | avatar = ResizeImg.resize((s, s), PIL.Image.ANTIALIAS) 234 | avatar.save(self.temp_path + "avatar" + str(s) + ".png") 235 | self.CheckingBar.setProperty("value", progressed) 236 | progressed += 20 237 | self.CheckingBar.setProperty("value", 100) 238 | 239 | 240 | """ 241 | ################################################################ 242 | ### Convert PNG To DDS 243 | ################################################################ 244 | 245 | """ 246 | progress = 25 247 | progressed = 0 248 | 249 | 250 | for dds in required_dds: 251 | self.multi_uploader.png_to_dds( 252 | self.temp_path + dds + ".png", 253 | self.temp_path, 254 | ) 255 | 256 | progressed += progress 257 | os.remove(self.temp_path + dds + ".png") 258 | self.msg.setStyleSheet( 259 | "font: 10pt; color: rgb(5, 255, 20);" 260 | ) 261 | self.msg.setText( 262 | "Done. Give it some time & the avatar will change." 263 | ) 264 | self.Ok.setEnabled(False) 265 | self.Ok.raise_() 266 | self.No.hide() 267 | self.Yes.hide() 268 | 269 | except Exception as e: 270 | self.log_to_external_file(f"{e} | TRACEBACK {extract_stack()}", "Error") 271 | 272 | """ 273 | ################################################################ 274 | ### Upload icons 275 | ################################################################ 276 | """ 277 | self.ftp.cwd(sysProfileRoot + "/" + self.CurrentUser) 278 | with open(self.temp_path + "avatar.png", "rb") as save_file: 279 | self.ftp.storbinary("STOR " + "avatar.png", save_file, 1024) 280 | 281 | for avatar in required_dds: 282 | with open(self.temp_path + avatar + ".dds", "rb") as save_file: 283 | self.ftp.storbinary("STOR " + avatar + ".dds", save_file, 1024) 284 | 285 | 286 | def send_generated_images(self) -> None: 287 | self.sending_progress = 0 288 | self.sending_progress_weight = int(100 / (len(self.icons_to_upload) + len(self.pics_to_upload))) 289 | 290 | if self.icons_to_upload: 291 | self.send_icon_to_ps4() 292 | 293 | if self.pics_to_upload: 294 | self.send_pic_to_ps4() 295 | 296 | 297 | def send_pic_to_ps4(self) -> None: 298 | """ 299 | upload generated icons to ps4 300 | """ 301 | 302 | for pic in self.pics_to_upload: 303 | self.multi_uploader.upload_to_server(f"{self.icons_cache_path}{pic}", pic) 304 | 305 | self.sending_progress += self.sending_progress_weight 306 | self.progress_bar(self.SendingBar, self.sending_progress) 307 | 308 | 309 | def send_icon_to_ps4(self) -> None: 310 | """ 311 | send icon to ps4, upon sucess copy icon0 for app preview 312 | """ 313 | 314 | try: 315 | for icon in self.icons_to_upload: 316 | 317 | if self.multi_uploader.upload_to_server(f"{self.icons_cache_path}{icon}", icon): 318 | 319 | if icon == "icon0.png": 320 | self.multi_uploader.update_local_icon(f"{self.icons_cache_path}{icon}", self.current_game_id) 321 | 322 | self.sending_progress += self.sending_progress_weight 323 | self.progress_bar(self.SendingBar, self.sending_progress) 324 | 325 | else: 326 | 327 | self.update_message(False, "Sorry! an issue has occured, Sending has been canceled") 328 | break 329 | 330 | self.update_message(True, "Successful!. Icons will take some time to change on PS4") 331 | 332 | except Exception as e: 333 | self.update_message(False, "Sorry! PS4 has denied the signal", extract_stack(), str(e)) 334 | 335 | 336 | 337 | def update_message(self, is_sucess:bool, msg:str, traceback_stack:list = [], dev_msg:str= "", font_size:int= 10) -> None: 338 | 339 | color = self.constant.get_color("green") 340 | 341 | if not is_sucess: 342 | 343 | color = self.constant.get_color("red") 344 | self.log_to_external_file(f"{msg} | DEV {dev_msg} | TRACEBACK {traceback_stack}", "Error") 345 | msg += ". Read logs.txt" 346 | 347 | self.msg.setStyleSheet(f"font: {font_size}pt; color: {color};") 348 | self.msg.setText(msg) 349 | 350 | 351 | def remove_generated_images(self) -> None: 352 | """ Delete temp icons generated for the server """ 353 | 354 | for file in os.listdir(self.icons_cache_path): 355 | 356 | if '.' in file: 357 | os.remove(f"{self.icons_cache_path}{file}") 358 | 359 | self.browsed_pic_path = "" 360 | self.browsed_icon_path = "" 361 | 362 | -------------------------------------------------------------------------------- /Interface/Settings.py: -------------------------------------------------------------------------------- 1 | import json 2 | from PyQt5 import QtCore, QtGui, QtWidgets 3 | from Module.Settings import Main as Settings 4 | from Module.Widget.Translate import Translate 5 | 6 | class Ui(Settings): 7 | def __init__(self) -> None: 8 | super().__init__() 9 | self.settings_cache = self.get_settings_cache() 10 | self.translation = Translate(self.language_path) 11 | self.supported_languages: dict = self.translation.get_supported_languages() 12 | self.translated_languages: dict = self.translation.get_translation(self.settings_cache.get("language"), "Languages") 13 | self.translated_languages_in_english = {key: value for value, key in self.translated_languages.items()} 14 | 15 | 16 | def get_settings_cache(self) -> dict: 17 | try: 18 | with open(f"{self.cache_path}Settings.json", encoding="utf-8") as file: 19 | return json.load(file) 20 | except: return self.local_settings_cache 21 | 22 | 23 | def setupUi(self, OptionsWin): 24 | self.OptionsWin = OptionsWin 25 | self.OptionsWin.setObjectName("OptionsWin") 26 | self.OptionsWin.resize(680, 400) 27 | 28 | 29 | #_________________ VISUALS ________________________# 30 | self.FontObj = QtGui.QFont() 31 | self.FontObj.setFamily(self.settings_cache.get("font")) 32 | self.FontObj.setPointSize(10) 33 | self.OptionsWin.setFont(self.FontObj) 34 | 35 | self.arrow_cursor = QtGui.QCursor(QtCore.Qt.ArrowCursor) 36 | self.pointing_cursor = QtGui.QCursor(QtCore.Qt.PointingHandCursor) 37 | 38 | 39 | #_________________ BUTTONS ________________________# 40 | self.SaveBtn = QtWidgets.QPushButton(self.OptionsWin) 41 | self.NoRadio = QtWidgets.QRadioButton(self.OptionsWin) 42 | self.YesRadio = QtWidgets.QRadioButton(self.OptionsWin) 43 | self.CancelBtn = QtWidgets.QPushButton(self.OptionsWin) 44 | self.DefaultBtn = QtWidgets.QPushButton(self.OptionsWin) 45 | self.IconsPathBtn = QtWidgets.QToolButton(self.OptionsWin) 46 | self.DownloadPathBtn = QtWidgets.QToolButton(self.OptionsWin) 47 | 48 | self.SaveBtn.setMaximumSize(QtCore.QSize(16777215, 28)) 49 | self.CancelBtn.setMaximumSize(QtCore.QSize(16777215, 28)) 50 | self.DefaultBtn.setMaximumSize(QtCore.QSize(16777215, 28)) 51 | self.IconsPathBtn.setMaximumSize(QtCore.QSize(50, 16777215)) 52 | self.DownloadPathBtn.setMaximumSize(QtCore.QSize(30, 30)) 53 | 54 | self.IconsPathBtn.setMinimumSize(QtCore.QSize(30, 30)) 55 | self.DownloadPathBtn.setMinimumSize(QtCore.QSize(30, 30)) 56 | 57 | self.NoRadio.setChecked(True) 58 | self.CancelBtn.setDefault(True) 59 | 60 | self.IconsPathBtn.setText("...") 61 | self.DownloadPathBtn.setText("...") 62 | 63 | self.YesRadio.setCursor(self.pointing_cursor) 64 | self.NoRadio.setCursor(self.pointing_cursor) 65 | 66 | 67 | #_________________ LABELS ________________________# 68 | self.IpLabel = QtWidgets.QLabel(self.OptionsWin) 69 | self.PortLabel = QtWidgets.QLabel(self.OptionsWin) 70 | self.FontLabel = QtWidgets.QLabel(self.OptionsWin) 71 | self.LanguageLabel = QtWidgets.QLabel(self.OptionsWin) 72 | self.HomebrewLabel = QtWidgets.QLabel(self.OptionsWin) 73 | self.IconsPathLabel = QtWidgets.QLabel(self.OptionsWin) 74 | self.DownloadPathLabel = QtWidgets.QLabel(self.OptionsWin) 75 | 76 | 77 | #_________________ INPUTS ________________________# 78 | self.Ip = QtWidgets.QLineEdit(self.OptionsWin) 79 | self.Port = QtWidgets.QSpinBox(self.OptionsWin) 80 | self.Fonts = QtWidgets.QFontComboBox(self.OptionsWin) 81 | self.IconPath = QtWidgets.QLineEdit(self.OptionsWin) 82 | self.Languages = QtWidgets.QComboBox(self.OptionsWin) 83 | self.DownloadPath = QtWidgets.QLineEdit(self.OptionsWin) 84 | self.EnableHbInfo = QtWidgets.QPlainTextEdit(self.OptionsWin) 85 | 86 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 87 | sizePolicy.setHeightForWidth(self.Ip.sizePolicy().hasHeightForWidth()) 88 | sizePolicy.setHeightForWidth(self.Port.sizePolicy().hasHeightForWidth()) 89 | sizePolicy.setHeightForWidth(self.Fonts.sizePolicy().hasHeightForWidth()) 90 | sizePolicy.setHeightForWidth(self.IconPath.sizePolicy().hasHeightForWidth()) 91 | sizePolicy.setHeightForWidth(self.DownloadPath.sizePolicy().hasHeightForWidth()) 92 | 93 | self.Ip.setSizePolicy(sizePolicy) 94 | self.Port.setSizePolicy(sizePolicy) 95 | self.IconPath.setSizePolicy(sizePolicy) 96 | self.DownloadPath.setSizePolicy(sizePolicy) 97 | 98 | self.Languages.setFrame(False) 99 | self.Languages.setFont(self.FontObj) 100 | self.Languages.setMaxVisibleItems(5) 101 | self.Languages.setCursor(self.pointing_cursor) 102 | self.Languages.setSizePolicy(sizePolicy) 103 | 104 | self.Fonts.setFrame(False) 105 | self.Fonts.setEditable(False) 106 | self.Fonts.setFont(self.FontObj) 107 | self.Fonts.setSizePolicy(sizePolicy) 108 | self.Fonts.setCurrentFont(self.FontObj) 109 | 110 | 111 | self.Ip.setFrame(False) 112 | self.Ip.setMaxLength(22) 113 | self.Ip.setFont(self.FontObj) 114 | self.Ip.setClearButtonEnabled(True) 115 | self.Ip.setCursor(self.pointing_cursor) 116 | self.Ip.setAlignment(QtCore.Qt.AlignCenter) 117 | 118 | self.Port.setFrame(False) 119 | self.Port.setMaximum(99999) 120 | self.Port.setFont(self.FontObj) 121 | self.Port.setAlignment(QtCore.Qt.AlignCenter) 122 | self.Port.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) 123 | self.Port.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 124 | 125 | paths = (self.IconPath, self.DownloadPath) 126 | for path in paths: 127 | path.setFrame(False) 128 | path.setReadOnly(True) 129 | path.setFont(self.FontObj) 130 | path.setClearButtonEnabled(True) 131 | path.setAlignment(QtCore.Qt.AlignCenter) 132 | path.setStyleSheet("border-radius: 13px;") 133 | 134 | self.EnableHbInfo.setEnabled(True) 135 | self.EnableHbInfo.setReadOnly(True) 136 | self.EnableHbInfo.setFont(self.FontObj) 137 | self.EnableHbInfo.setFrameShape(QtWidgets.QFrame.Box) 138 | 139 | max_40_height = QtCore.QSize(16777215, 40) 140 | max_50_height = QtCore.QSize(16777215, 50) 141 | 142 | self.Ip.setMaximumSize(max_40_height) 143 | self.Port.setMaximumSize(max_40_height) 144 | self.Fonts.setMaximumSize(max_50_height) 145 | self.IconPath.setMaximumSize(max_40_height) 146 | self.Languages.setMaximumSize(max_50_height) 147 | self.DownloadPath.setMaximumSize(max_40_height) 148 | self.EnableHbInfo.setMaximumSize(QtCore.QSize(16777215, 60)) 149 | 150 | 151 | #_________________ TAB ORDER ________________________# 152 | self.OptionsWin.setTabOrder(self.Fonts, self.Ip) 153 | self.OptionsWin.setTabOrder(self.Ip, self.IconsPathBtn) 154 | self.OptionsWin.setTabOrder(self.YesRadio, self.NoRadio) 155 | self.OptionsWin.setTabOrder(self.SaveBtn, self.CancelBtn) 156 | self.OptionsWin.setTabOrder(self.NoRadio, self.DefaultBtn) 157 | self.OptionsWin.setTabOrder(self.DefaultBtn, self.SaveBtn) 158 | self.OptionsWin.setTabOrder(self.DownloadPath, self.IconPath) 159 | self.OptionsWin.setTabOrder(self.CancelBtn, self.EnableHbInfo) 160 | self.OptionsWin.setTabOrder(self.DownloadPathBtn, self.YesRadio) 161 | self.OptionsWin.setTabOrder(self.EnableHbInfo, self.DownloadPath) 162 | self.OptionsWin.setTabOrder(self.IconsPathBtn, self.DownloadPathBtn) 163 | spacerItem = QtWidgets.QSpacerItem(50, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) 164 | 165 | 166 | #_________________ LAYOUTS ________________________# 167 | self.lineSeperator = QtWidgets.QFrame(self.OptionsWin) 168 | self.lineSeperator.setFrameShape(QtWidgets.QFrame.HLine) 169 | self.lineSeperator.setFrameShadow(QtWidgets.QFrame.Sunken) 170 | 171 | self.TopLayout = QtWidgets.QGridLayout() 172 | self.MainLayout = QtWidgets.QVBoxLayout() 173 | self.FirstLayout = QtWidgets.QHBoxLayout() 174 | self.BottomLayout = QtWidgets.QHBoxLayout() 175 | self.SecondLayout = QtWidgets.QHBoxLayout() 176 | self.HombrewLayout = QtWidgets.QHBoxLayout() 177 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.OptionsWin) 178 | 179 | self.TopLayout.setVerticalSpacing(20) 180 | self.TopLayout.setHorizontalSpacing(10) 181 | self.TopLayout.setContentsMargins(-1, 10, -1, 10) 182 | 183 | self.verticalLayout_2.addLayout(self.MainLayout) 184 | 185 | self.MainLayout.addLayout(self.TopLayout) 186 | self.MainLayout.addWidget(self.lineSeperator) 187 | self.MainLayout.addLayout(self.BottomLayout) 188 | self.FirstLayout.addWidget(self.IconPath) 189 | self.FirstLayout.addWidget(self.IconsPathBtn) 190 | self.SecondLayout.addWidget(self.DownloadPath) 191 | self.SecondLayout.addWidget(self.DownloadPathBtn) 192 | 193 | self.TopLayout.addWidget(self.Languages, 0, 1, 1, 1,) 194 | self.TopLayout.addWidget(self.LanguageLabel, 0, 0, 1, 1) 195 | 196 | self.TopLayout.addWidget(self.Fonts, 1, 1, 1, 1) 197 | self.TopLayout.addWidget(self.FontLabel, 1, 0, 1, 1) 198 | 199 | self.TopLayout.addWidget(self.Ip, 2, 1, 1, 1) 200 | self.TopLayout.addWidget(self.IpLabel, 2, 0, 1, 1) 201 | 202 | self.TopLayout.addWidget(self.Port, 3, 1, 1, 1) 203 | self.TopLayout.addWidget(self.PortLabel, 3, 0, 1, 1) 204 | 205 | self.TopLayout.addLayout(self.FirstLayout, 4, 1, 1, 1) 206 | self.TopLayout.addWidget(self.IconsPathLabel, 4, 0, 1, 1) 207 | 208 | self.TopLayout.addLayout(self.SecondLayout, 5, 1, 1, 1) 209 | self.TopLayout.addWidget(self.DownloadPathLabel, 5, 0, 1, 1) 210 | 211 | self.TopLayout.addLayout(self.HombrewLayout, 6, 1, 1, 1) 212 | self.TopLayout.addWidget(self.HomebrewLabel, 6, 0, 1, 1) 213 | 214 | self.BottomLayout.setContentsMargins(50, 0, 50, -1) 215 | self.BottomLayout.addItem(spacerItem) 216 | self.BottomLayout.addWidget(self.SaveBtn) 217 | self.BottomLayout.addWidget(self.CancelBtn) 218 | self.BottomLayout.addWidget(self.DefaultBtn) 219 | 220 | self.HombrewLayout.addWidget(self.NoRadio) 221 | self.HombrewLayout.addWidget(self.YesRadio) 222 | self.HombrewLayout.addWidget(self.EnableHbInfo) 223 | 224 | 225 | #_________________ DEFAULT VALUES _____________________# 226 | self.Port.setValue(int(self.settings_cache.get("port"))) 227 | self.Ip.setPlaceholderText(self.settings_cache.get("ip")) 228 | self.Fonts.setCurrentText(self.settings_cache.get("font")) 229 | self.IconPath.setPlaceholderText(self.settings_cache.get("icons_path")) 230 | self.DownloadPath.setPlaceholderText(self.settings_cache.get("download_path")) 231 | 232 | 233 | #_________________ SIGNALS _______________________# 234 | self.NoRadio.setChecked(True) 235 | if self.settings_cache.get("homebrew") == "True": 236 | self.YesRadio.setChecked(True) 237 | 238 | self.SaveBtn.clicked.connect( 239 | lambda: self.save_cache( 240 | self.Fonts.currentText(), 241 | self.IconPath.text(), 242 | self.DownloadPath.text(), 243 | str(self.YesRadio.isChecked()), 244 | self.Port.text(), 245 | self.Ip.text(), 246 | self.supported_languages.get( 247 | self.translated_languages_in_english.get( 248 | self.Languages.currentText() 249 | ) 250 | ), True 251 | ) 252 | ) 253 | 254 | self.CancelBtn.clicked.connect( 255 | lambda: self.OptionsWin.close() 256 | ) 257 | 258 | self.IconsPathBtn.clicked.connect( 259 | lambda: self.render_path_window("icons") 260 | ) 261 | 262 | self.DownloadPathBtn.clicked.connect( 263 | lambda: self.render_path_window("download") 264 | ) 265 | 266 | self.DefaultBtn.clicked.connect(self.reset_to_defaults) 267 | 268 | QtCore.QMetaObject.connectSlotsByName(self.OptionsWin) 269 | self.translate_ui() 270 | 271 | 272 | def translate_ui(self): 273 | translated_content: dict = self.translation.get_translation(self.settings_cache.get("language"), "Settings") 274 | self._translate = QtCore.QCoreApplication.translate 275 | 276 | self.translate_dropdown_list_items() 277 | 278 | self.NoRadio.setText(self._translate("OptionsWin", translated_content.get("NoRadio"))) 279 | self.YesRadio.setText(self._translate("OptionsWin", translated_content.get("YesRadio"))) 280 | self.SaveBtn.setText(self._translate("OptionsWin", translated_content.get("SaveBtn"))) 281 | self.CancelBtn.setText(self._translate("OptionsWin", translated_content.get("CancelBtn"))) 282 | self.IpLabel.setText(self._translate("OptionsWin", translated_content.get("IpLabel"))) 283 | self.DefaultBtn.setText(self._translate("OptionsWin", translated_content.get("DefaultBtn"))) 284 | self.PortLabel.setText(self._translate("OptionsWin", translated_content.get("PortLabel"))) 285 | self.FontLabel.setText(self._translate("OptionsWin", translated_content.get("FontLabel"))) 286 | self.HomebrewLabel.setText(self._translate("OptionsWin", translated_content.get("HomebrewLabel"))) 287 | self.LanguageLabel.setText(self._translate("OptionsWin", translated_content.get("LanguageLabel"))) 288 | self.IconsPathLabel.setText(self._translate("OptionsWin", translated_content.get("IconsPathLabel"))) 289 | self.DownloadPathLabel.setText(self._translate("OptionsWin", translated_content.get("DownloadPathLabel"))) 290 | self.EnableHbInfo.setPlainText(self._translate("OptionsWin", translated_content.get("EnableHbInfo"))) 291 | self.OptionsWin.setWindowTitle(self._translate("OptionsWin", translated_content.get("WindowTitle"))) 292 | 293 | 294 | def translate_dropdown_list_items(self) -> None: 295 | """ Translate the languages dropdown list according to the default language chosen """ 296 | 297 | for index, language in enumerate(self.supported_languages): 298 | self.Languages.addItem(self.translated_languages.get(language)) 299 | 300 | if language == self.settings_cache.get("language"): 301 | self.Languages.setCurrentIndex(index) -------------------------------------------------------------------------------- /Module/Avatars.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | Methods for Changing Avatars 4 | the class inherits 'Environment' 5 | 6 | """ 7 | 8 | from environment import Common 9 | 10 | from PyQt5 import QtWidgets 11 | import json, os 12 | 13 | class Main(Common): 14 | def __init__(self) -> None: 15 | super().__init__() 16 | self.fileName = "online.json" 17 | self.dir = self.temp_path + "Icons\Avatars\\" 18 | 19 | def logIt(self, description, Type): 20 | import datetime 21 | 22 | try: 23 | error_file = open("Logs.txt", "a") 24 | except: 25 | error_file = open("Logs.txt", "w") 26 | 27 | if Type == "Warning": 28 | error_file.write( 29 | str(datetime.datetime.now()) 30 | + " | " 31 | + "_DEV Warning: " 32 | + str(description) 33 | + "\n" 34 | ) 35 | else: 36 | error_file.write( 37 | str(datetime.datetime.now()) 38 | + " | " 39 | + "_DEV ERROR: " 40 | + str(description) 41 | + "\n" 42 | ) 43 | 44 | def convert2Url(self, path): 45 | result = "" 46 | for i in path: 47 | if i == "\\": 48 | result += "/" 49 | else: 50 | result += i 51 | return result 52 | 53 | def RenameAccount(self): 54 | from ftplib import FTP 55 | 56 | ftp = FTP() 57 | try: 58 | fn = self.firstName.text() 59 | ln = self.lastName.text() 60 | if fn == "": 61 | fn = "Unknown" 62 | if ln == "": 63 | ln = "Username" 64 | file = ( 65 | self.AppSettingDir 66 | + "Cache\\Icons\\Avatars\\" 67 | + self.user[self.currentUserCounter] 68 | + ".json" 69 | ) 70 | ftp.set_debuglevel(2) 71 | ftp.connect(self.ip, int(self.port)) 72 | ftp.login("", "") 73 | ftp.cwd( 74 | "system_data/priv/cache/profile/" + self.user[self.currentUserCounter] 75 | ) 76 | jsonFile = open(file, "r") 77 | json_object = json.load(jsonFile) 78 | 79 | json_object["firstName"] = fn 80 | json_object["lastName"] = ln 81 | jsonFile.close() 82 | 83 | jsonFile = open(file, "w+") 84 | json.dump(json_object, jsonFile) 85 | jsonFile.close() 86 | 87 | with open(file, "rb") as fakeOnlineFile: 88 | ftp.storlines("STOR online.json", fakeOnlineFile) 89 | self.Rename_btn.setStyleSheet("color:rgb(61, 226, 61);") 90 | self.Rename_btn.setText("Done (Restart required)") 91 | self.Rename_btn.setDisabled(True) 92 | except Exception as e: 93 | self.Rename_btn.setStyleSheet("color:rgb(226, 41, 41);") 94 | self.Rename_btn.setText("Error check logs file") 95 | self.logIt(str(e), "error") 96 | 97 | def Revert(self): 98 | self.CheckAvatar = True 99 | self.resizeUpload() 100 | self.CheckAvatar = False 101 | 102 | def getAccountName(self): 103 | name = "" 104 | try: 105 | jsonFile = open( 106 | self.AppSettingDir 107 | + "Cache\Icons\Avatars\\" 108 | + self.user[self.currentUserCounter] 109 | + ".json", 110 | "r", 111 | ) 112 | readJson = json.load(jsonFile) 113 | jsonFile.close() 114 | 115 | name = readJson["firstName"] + " " + readJson["lastName"] 116 | except Exception as e: 117 | self.logIt(str(e), "Warning") 118 | return name 119 | 120 | def UpdateInfo(self): 121 | try: 122 | if os.path.isfile( 123 | self.dataPath 124 | + "Cache//Icons//Avatars//" 125 | + self.user[self.currentUserCounter] 126 | + "Original.png" 127 | ): 128 | self.Originalcon.setStyleSheet( 129 | "border-image: url(" 130 | + self.dataPath 131 | + "Cache//Icons//Avatars//" 132 | + self.user[self.currentUserCounter] 133 | + "Original.png);" 134 | ) 135 | self.label1.setText("Do you want the") 136 | self.label2.setText("original profile icon?") 137 | self.Revert_btn.setEnabled(True) 138 | self.avatar = ( 139 | self.dataPath 140 | + "Cache//Icons//Avatars//" 141 | + self.user[self.currentUserCounter] 142 | + "Original.png" 143 | ) 144 | else: 145 | # Couldn't get original image from Sony server while caching 146 | self.Originalcon.setStyleSheet( 147 | "border-image: url(" 148 | + self.dataPath 149 | + "pref//error.@OfficialAhmed0);" 150 | ) 151 | self.label1.setText("Ops! This account doesn't have") 152 | self.label2.setText("an original avatar.") 153 | self.Revert_btn.setEnabled(False) 154 | self.ChangeAvatar.setStyleSheet( 155 | "border-image: url(" 156 | + self.dataPath 157 | + "Cache//Icons//Avatars//" 158 | + self.user[self.currentUserCounter] 159 | + ".png);" 160 | ) 161 | self.AccountID.setText(self.user[self.currentUserCounter]) 162 | self.AccountName.setText(self.getAccountName()) 163 | self.firstName.setText("") 164 | self.lastName.setText("") 165 | self.Rename_btn.setStyleSheet("color:rgb(255, 255, 255);") 166 | self.Rename_btn.setText("Rename account") 167 | except Exception as e: 168 | self.logit(str(e), "error") 169 | 170 | def Next(self): 171 | if self.currentUserCounter < self.numOfUsers - 1: 172 | self.ResizeUpload_btn.setEnabled(False) 173 | self.Rename_btn.setDisabled(False) 174 | self.currentUserCounter += 1 175 | # Original Icon 176 | self.UpdateInfo() 177 | # End of Original Icon 178 | self.TotalAccounts.setText( 179 | str(self.currentUserCounter + 1) + "/" + str(self.numOfUsers) 180 | ) 181 | 182 | def Prev(self): 183 | if self.currentUserCounter > 0 and self.currentUserCounter < self.numOfUsers: 184 | self.Rename_btn.setDisabled(False) 185 | self.TotalAccounts.setText( 186 | str(self.currentUserCounter) + "/" + str(self.numOfUsers) 187 | ) 188 | self.currentUserCounter -= 1 189 | # Original Icon 190 | self.UpdateInfo() 191 | # End of Original Icon 192 | 193 | def Download(self): 194 | from PyQt5.QtWidgets import QFileDialog, QDialog 195 | 196 | try: 197 | opt = QtWidgets.QFileDialog.Options() 198 | opt |= QtWidgets.QFileDialog.DontUseSheet 199 | dialog = QFileDialog() 200 | dialog.setOptions(opt) 201 | dialog.setDirectory(self.download_path) 202 | path, _ = QtWidgets.QFileDialog.getSaveFileName( 203 | None, 204 | "Where to Download?", 205 | "MyChangeAvatar.png", 206 | "PNG (*.png)", 207 | options=opt, 208 | ) 209 | if path: 210 | import shutil 211 | 212 | shutil.copyfile( 213 | self.AppSettingDir 214 | + "Cache\\Icons\\Avatars\\" 215 | + self.user[self.currentUserCounter] 216 | + ".png", 217 | path, 218 | ) 219 | except Exception as e: 220 | self.logit(str(e), "error") 221 | 222 | def Change(self): 223 | from PyQt5.QtWidgets import QFileDialog, QDialog 224 | 225 | try: 226 | opt = QtWidgets.QFileDialog.Options() 227 | opt |= QtWidgets.QFileDialog.DontUseSheet 228 | dialog = QFileDialog() 229 | dialog.setOptions(opt) 230 | dialog.setDirectory(self.icons_path) 231 | path, _ = QtWidgets.QFileDialog.getOpenFileName( 232 | None, 233 | "Pick an image greater than (440x440) ...", 234 | "", 235 | "PNG(*.png);; jpg(*.jpg);; Jpeg(*.jpeg);; icon(*.ico);; DDS(*.dds)", 236 | options=opt, 237 | ) 238 | if path: # not empty 239 | import PIL 240 | 241 | valid = False 242 | image = PIL.Image.open(path) 243 | 244 | if image.size[0] >= 440 or image.size[1] >= 440: 245 | if image.size[0] >= 440: 246 | if image.size[1] >= 440: 247 | valid = True 248 | elif image.size[1] >= 440: 249 | if image.size[0] >= 440: 250 | valid = True 251 | 252 | if valid: 253 | self.iconPath = "" 254 | for i in path: 255 | if i == "\\": 256 | self.iconPath += "/" 257 | else: 258 | self.iconPath += i 259 | self.ChangeAvatar.setStyleSheet( 260 | "border-image: url(" + self.iconPath + ");" 261 | ) 262 | self.ResizeUpload_btn.setEnabled(True) 263 | else: 264 | self.Error("Invalid icon size") 265 | except Exception as e: 266 | self.logit(str(e), "error") 267 | 268 | def resizeUpload(self): 269 | import Interface.Upload as Upload 270 | import shutil 271 | 272 | try: 273 | self.ResizeUpload_btn.setEnabled(False) 274 | if self.CheckAvatar == False: 275 | self.avatar = self.iconPath 276 | shutil.copyfile( 277 | self.avatar, 278 | self.AppSettingDir 279 | + "Cache\Icons\Avatars\\" 280 | + self.user[self.currentUserCounter] 281 | + ".png", 282 | ) 283 | elif self.CheckAvatar == True: 284 | self.avatar = ( 285 | self.AppSettingDir 286 | + "Cache\Icons\Avatars\\" 287 | + self.user[self.currentUserCounter] 288 | + "Original.png" 289 | ) 290 | shutil.copyfile( 291 | self.avatar, 292 | self.AppSettingDir 293 | + "Cache\Icons\Avatars\\" 294 | + self.user[self.currentUserCounter] 295 | + ".png", 296 | ) 297 | 298 | path = "" 299 | for i in self.avatar: 300 | if i == "\\": 301 | path += "/" 302 | else: 303 | path += i 304 | self.ChangeAvatar.setStyleSheet("border-image: url(" + path + ");") 305 | 306 | self.windo = QtWidgets.QWidget() 307 | self.ui = Upload.Ui() 308 | self.ui.setupUi( 309 | self.windo, 310 | self.avatar, 311 | self.ip, 312 | self.port, 313 | "", 314 | "Profileit", 315 | "", 316 | self.user[self.currentUserCounter], 317 | ) 318 | self.windo.show() 319 | except Exception as e: 320 | self.logit(str(e), "error") 321 | 322 | def start_cache(self): 323 | ############################################### 324 | # Prepare Avatars 325 | ################################################ 326 | 327 | # Remove old data 328 | if len(os.listdir(self.dir)) != 0: 329 | data = os.listdir(self.dir) 330 | try: 331 | for i in data: 332 | os.remove(dir + i) 333 | except Exception as e: 334 | self.log_to_external_file(str(e), "Warning") 335 | 336 | progress = int(100 / len(self.userID)) 337 | progressed = 0 338 | self.CacheBar.setProperty("value", 1) 339 | 340 | for user in self.userID: 341 | self.ftp.cwd("/") 342 | self.ftp.cwd(self.sysProfileRoot + "/" + user) 343 | 344 | with open(self.dir + "\\" + user + ".png", "wb") as file: 345 | # cache avatar if available 346 | try: 347 | self.ftp.retrbinary("RETR " + "avatar.png", file.write, 1024) 348 | except Exception as e: 349 | self.log_to_external_file(str(e), "Warning") 350 | with open(self.dir + "\\" + user + ".json", "wb") as file: 351 | # Fix (v4.07) make a fake one if online json not found 352 | # fix (json not found) v4.51 353 | try: 354 | self.ftp.retrbinary("RETR " + self.fileName, file.write, 1024) 355 | except Exception as e: 356 | print(str(e)) 357 | 358 | import json 359 | 360 | data = { 361 | "avatarUrl": "http://static-resource.np.community.playstation.net/a/vatar_xl/WWS_E/E0012_XL.png", 362 | "firstName": "Unknown", 363 | "lastName": "username", 364 | "pictureUrl": "https://image.api.np.km.playstation.net/images/?format=png&w=440&h=440&image=https%3A%2F%2Fkfscdn.api.np.km.playstation.net%2F00000000000008%2F000000000000003.png&sign=blablabla019501", 365 | "trophySummary": '{"level":1,"progress":0,"earnedTrophies":{"platinum":0,"gold":0,"silver":0,"bronze":0}}', 366 | "isOfficiallyVerified": "true", 367 | "aboutMe": "Temporary file created by Iconit app by @OfficialAhmed0", 368 | } 369 | 370 | with open(self.dir + "\\" + user + ".json", "w+") as jsonFile: 371 | json.dump(data, jsonFile, indent=4) 372 | 373 | with open(self.dir + "\\" + user + ".json", "rb") as json: 374 | self.ftp.storbinary("STOR online.json", json, 1024) 375 | 376 | # Download original Profile Icon from Sony server 377 | import requests as req 378 | 379 | try: 380 | with open(self.dir + "\\" + user + ".json") as file: 381 | # Extract Icon url from json file 382 | read = file.read() 383 | url = read[ 384 | read.find("avatarUrl") 385 | + len("avatarUrl") 386 | + 3 : read.find(".png") 387 | + len(".png") 388 | ] 389 | cont = url.split("\/") 390 | link = "" 391 | 392 | for i in cont: 393 | if i != "": 394 | link += i + "//" 395 | img = req.get(link[:-2]) 396 | 397 | with open(self.dir + "\\" + user + "Original.png", "wb") as origIcon: 398 | origIcon.write(img.content) 399 | 400 | except Exception as e: 401 | self.log_to_external_file(str(e), "Warning") 402 | 403 | for i in range(1, progress): 404 | self.CacheBar.setProperty("value", progressed + i) 405 | 406 | progressed += progress 407 | 408 | self.CacheBar.setProperty("value", 100) 409 | self.render_window() 410 | 411 | def Error(self, Type): 412 | import Interface.Alerts as Alerts 413 | 414 | self.window = QtWidgets.QDialog() 415 | self.ui = Alerts.Ui() 416 | self.ui.setupUi(self.window, Type) 417 | self.window.show() 418 | -------------------------------------------------------------------------------- /Interface/Iconit.py: -------------------------------------------------------------------------------- 1 | import os 2 | from Module.Constant.Html import Html 3 | from Module.Iconit import Main as Iconit 4 | from PyQt5 import QtCore, QtGui, QtWidgets 5 | 6 | import Interface.Settings as Settings 7 | 8 | 9 | class Ui(Iconit): 10 | 11 | def __init__(self) -> None: 12 | super().__init__() 13 | 14 | def setupUi(self, window): 15 | self.html = Html() 16 | 17 | font = QtGui.QFont() 18 | font.setBold(True) 19 | font.setItalic(True) 20 | font.setPointSize(13) 21 | font.setFamily(self.font) 22 | 23 | pointing_hand_cursor = QtGui.QCursor(QtCore.Qt.PointingHandCursor) 24 | 25 | """ 26 | ################################################################# 27 | WINDOW SPECS 28 | ################################################################# 29 | """ 30 | window.setWindowTitle(f"Iconit v{self.app_version} ({self.app_release_date})") 31 | 32 | window.resize(720, 620) 33 | window.setMinimumSize(QtCore.QSize(720, 620)) 34 | window.setWindowModality(QtCore.Qt.WindowModal) 35 | window.setTabShape(QtWidgets.QTabWidget.Rounded) 36 | sizePolicy = QtWidgets.QSizePolicy( 37 | QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred 38 | ) 39 | sizePolicy.setHeightForWidth(window.sizePolicy().hasHeightForWidth()) 40 | window.setSizePolicy(sizePolicy) 41 | 42 | self.mainWidget = QtWidgets.QWidget(window) 43 | bg_path = self.pref_path.replace("\\", "/") 44 | self.mainWidget.setObjectName("mainWidget") 45 | self.mainWidget.setStyleSheet( 46 | f""" 47 | QWidget#mainWidget{'{'} 48 | background-image: url({bg_path}MainBG.@OfficialAhmed0); 49 | background-size: cover; 50 | background-repeat: no-repeat; 51 | background-position: center; 52 | {'}'} 53 | """ 54 | ) 55 | 56 | """ 57 | ################################################################# 58 | LABELS 59 | ################################################################# 60 | """ 61 | font.setPointSize(22) 62 | font.setItalic(False) 63 | 64 | self.TransparentLayer = QtWidgets.QWidget(self.mainWidget) 65 | self.TransparentLayer.setObjectName("TransparentLayer") 66 | sizePolicy = QtWidgets.QSizePolicy( 67 | QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding 68 | ) 69 | sizePolicy.setHeightForWidth( 70 | self.TransparentLayer.sizePolicy().hasHeightForWidth() 71 | ) 72 | self.TransparentLayer.setSizePolicy(sizePolicy) 73 | self.TransparentLayer.setStyleSheet( 74 | f""" 75 | QWidget#TransparentLayer{'{'} 76 | border-radius: 100px; 77 | background-color: qlineargradient( 78 | spread:pad, x1:0, y1:0, x2:1, y2:1, 79 | stop:0 rgba(124, 57, 191, 120), 80 | stop:1 rgba(42, 44, 89, 220) 81 | ); 82 | {'}'} 83 | """ 84 | ) 85 | self.IpLabel = QtWidgets.QLabel(self.TransparentLayer) 86 | self.ModeLabel = QtWidgets.QLabel(self.TransparentLayer) 87 | self.PortLabel = QtWidgets.QLabel(self.TransparentLayer) 88 | self.CacheLabel = QtWidgets.QLabel(self.TransparentLayer) 89 | self.StatusLabel = QtWidgets.QLabel(self.TransparentLayer) 90 | 91 | """ 92 | ################################################################# 93 | BUTTONS 94 | ################################################################# 95 | """ 96 | font.setPointSize(14) 97 | font.setItalic(True) 98 | 99 | self.ConnectBtn = QtWidgets.QPushButton(self.TransparentLayer) 100 | self.GameIconsRadio = QtWidgets.QRadioButton(self.TransparentLayer) 101 | self.SystemIconsRadio = QtWidgets.QRadioButton(self.TransparentLayer) 102 | self.AvatarIconsRadio = QtWidgets.QRadioButton(self.TransparentLayer) 103 | 104 | btns = ( 105 | self.GameIconsRadio, 106 | self.SystemIconsRadio, 107 | self.AvatarIconsRadio, 108 | self.ConnectBtn, 109 | ) 110 | 111 | for btn in btns: 112 | btn.setFont(font) 113 | btn.setCursor(pointing_hand_cursor) 114 | btn.setStyleSheet("color: rgb(255, 255, 255);") 115 | 116 | font.setBold(True) 117 | font.setItalic(False) 118 | font.setPointSize(13) 119 | self.ConnectBtn.setFont(font) 120 | self.ConnectBtn.setStyleSheet("color: rgb(0, 0, 0);") 121 | self.GameIconsRadio.setChecked(True) 122 | 123 | """ 124 | ################################################################# 125 | INPUTS 126 | ################################################################# 127 | """ 128 | font.setPointSize(13) 129 | font.setBold(False) 130 | font.setItalic(True) 131 | 132 | self.PortInput = QtWidgets.QLineEdit(self.TransparentLayer) 133 | self.IpInput = QtWidgets.QLineEdit(self.TransparentLayer) 134 | inputs = (self.IpInput, self.PortInput) 135 | 136 | for input in inputs: 137 | input.setFont(font) 138 | input.setMaxLength(16) 139 | input.setText(self.port) 140 | input.setSizePolicy(sizePolicy) 141 | input.setClearButtonEnabled(True) 142 | input.setAlignment(QtCore.Qt.AlignCenter) 143 | input.setStyleSheet("border-radius: 10px;") 144 | 145 | """ 146 | ################################################################# 147 | DESCRIPTION 148 | ################################################################# 149 | """ 150 | 151 | self.SysIconsInfo = QtWidgets.QLabel(self.TransparentLayer) 152 | self.GameIconsInfo = QtWidgets.QLabel(self.TransparentLayer) 153 | 154 | """ 155 | ################################################################# 156 | CUSTOM PROGRESS BAR 157 | ################################################################# 158 | """ 159 | 160 | self.CacheBar = QtWidgets.QProgressBar(self.TransparentLayer) 161 | self.CacheBar.setStyleSheet( 162 | f""" 163 | QProgressBar{'{'} 164 | border-radius: 10px; 165 | color:rgb(255, 255, 255) 166 | {'}'} 167 | QProgressBar::chunk{'{'} 168 | border-radius: 10px; 169 | background: QLinearGradient( x1:0.358, y1:0.602182, x2:0.960318, y2:0.648, stop:0 rgb(124, 57, 191), stop:0.903409 rgb(242, 80, 231)); 170 | {'}'} 171 | """ 172 | ) 173 | self.CacheBar.setProperty("value", 0) 174 | self.CacheBar.setAlignment(QtCore.Qt.AlignCenter) 175 | 176 | """ 177 | ################################################################# 178 | CLICKABLE LINKS 179 | ################################################################# 180 | """ 181 | 182 | self.TitleLink = QtWidgets.QLabel(self.mainWidget) 183 | self.PaypalLink = QtWidgets.QLabel(self.TransparentLayer) 184 | self.TwitterLink = QtWidgets.QLabel(self.TransparentLayer) 185 | self.OnlineIconsLink = QtWidgets.QLabel(self.TransparentLayer) 186 | 187 | links = ( 188 | self.TitleLink, 189 | self.PaypalLink, 190 | self.TwitterLink, 191 | self.OnlineIconsLink, 192 | ) 193 | 194 | for link in links: 195 | link.setFont(font) 196 | link.setOpenExternalLinks(True) 197 | 198 | font.setPointSize(19) 199 | font.setItalic(False) 200 | font.setBold(True) 201 | self.TitleLink.setFont(font) 202 | 203 | """ 204 | ################################################################# 205 | SPACERS AND LAYOUTS (Order matter) 206 | ################################################################# 207 | """ 208 | 209 | SpacerMinExp = QtWidgets.QSpacerItem( 210 | 20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding 211 | ) 212 | SpacerExpMin = QtWidgets.QSpacerItem( 213 | 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum 214 | ) 215 | SpacerFixMin = QtWidgets.QSpacerItem( 216 | 500, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum 217 | ) 218 | spacerItem = SpacerExpMin 219 | spacerItem1 = SpacerMinExp 220 | spacerItem2 = SpacerExpMin 221 | spacerItem3 = SpacerMinExp 222 | spacerItem4 = SpacerFixMin 223 | spacerItem5 = SpacerMinExp 224 | spacerItem6 = SpacerMinExp 225 | spacerItem7 = SpacerExpMin 226 | spacerItem8 = SpacerMinExp 227 | spacerItem9 = SpacerExpMin 228 | spacerItem10 = SpacerExpMin 229 | spacerItem11 = SpacerMinExp 230 | 231 | self.GridLayout = QtWidgets.QGridLayout() 232 | self.GridLayout.addItem(spacerItem3, 1, 2, 1, 1) 233 | self.GridLayout.addItem(spacerItem2, 2, 3, 1, 1) 234 | self.GridLayout.addItem(spacerItem, 2, 0, 1, 1) 235 | self.GridLayout.addItem(spacerItem1, 5, 2, 1, 1) 236 | self.GridLayout.addItem(spacerItem4, 6, 2, 1, 1) 237 | self.GridLayout.addWidget(self.IpInput, 2, 2, 1, 1) 238 | self.GridLayout.addWidget(self.IpLabel, 2, 1, 1, 1) 239 | self.GridLayout.addWidget(self.PortLabel, 3, 1, 1, 1) 240 | self.GridLayout.addWidget(self.PortInput, 3, 2, 1, 1) 241 | self.GridLayout.addWidget(self.GameIconsRadio, 8, 2, 1, 1) 242 | self.GridLayout.addWidget(self.GameIconsInfo, 9, 2, 1, 1) 243 | self.GridLayout.addWidget(self.ModeLabel, 10, 1, 1, 1) 244 | self.GridLayout.addWidget(self.SystemIconsRadio, 10, 2, 1, 1) 245 | self.GridLayout.addWidget(self.SysIconsInfo, 11, 2, 1, 1) 246 | self.GridLayout.addWidget(self.AvatarIconsRadio, 12, 2, 1, 1) 247 | 248 | self.VerticalLayout4 = QtWidgets.QVBoxLayout(self.TransparentLayer) 249 | self.VerticalLayout5 = QtWidgets.QVBoxLayout(self.mainWidget) 250 | self.VerticalLayout6 = QtWidgets.QVBoxLayout() 251 | 252 | self.GridLayout2 = QtWidgets.QGridLayout() 253 | self.GridLayout2.setContentsMargins(-1, 15, -1, -1) 254 | self.GridLayout2.addItem(spacerItem5, 0, 2, 1, 1) 255 | self.GridLayout2.addItem(spacerItem7, 2, 0, 1, 1) 256 | self.GridLayout2.addItem(spacerItem6, 2, 1, 2, 1) 257 | self.GridLayout2.addItem(spacerItem8, 5, 2, 1, 1) 258 | self.GridLayout2.addItem(spacerItem9, 1, 2, 1, 1) 259 | self.GridLayout2.addItem(spacerItem10, 2, 3, 1, 1) 260 | self.GridLayout2.addWidget(self.CacheBar, 4, 2, 1, 1) 261 | self.GridLayout2.addWidget(self.CacheLabel, 4, 1, 1, 1) 262 | self.GridLayout2.addWidget(self.PaypalLink, 7, 2, 1, 1) 263 | self.GridLayout2.addWidget(self.ConnectBtn, 2, 2, 2, 1) 264 | self.GridLayout2.addWidget(self.TwitterLink, 8, 2, 1, 1) 265 | self.GridLayout2.addWidget(self.OnlineIconsLink, 6, 2, 1, 1) 266 | self.VerticalLayout4.addItem(spacerItem11) 267 | self.VerticalLayout5.addWidget(self.TitleLink) 268 | self.VerticalLayout6.addWidget(self.StatusLabel) 269 | self.VerticalLayout6.addLayout(self.GridLayout) 270 | self.VerticalLayout6.addLayout(self.GridLayout2) 271 | self.VerticalLayout4.addLayout(self.VerticalLayout6) 272 | self.VerticalLayout5.addWidget(self.TransparentLayer) 273 | 274 | """ 275 | ################################################################# 276 | SETTINGS TREE 277 | ################################################################# 278 | """ 279 | window.setCentralWidget(self.mainWidget) 280 | self.menubar = QtWidgets.QMenuBar(window) 281 | self.menubar.setObjectName("menubar") 282 | self.menubar.setCursor(pointing_hand_cursor) 283 | self.menubar.setGeometry(QtCore.QRect(0, 0, 701, 22)) 284 | 285 | self.menuSettings = QtWidgets.QMenu(self.menubar) 286 | self.menuSettings.setObjectName("menuSettings") 287 | 288 | window.setMenuBar(self.menubar) 289 | self.About = QtWidgets.QAction(window) 290 | self.Options = QtWidgets.QAction(window) 291 | self.actionAbout = QtWidgets.QAction(window) 292 | self.Remove_cache = QtWidgets.QAction(window) 293 | 294 | self.Special_thanks = QtWidgets.QAction(window) 295 | self.DownloadDatabase = QtWidgets.QAction(window) 296 | self.actionRemove_cache = QtWidgets.QAction(window) 297 | 298 | self.menuSettings.addAction(self.Options) 299 | self.menuSettings.addAction(self.Remove_cache) 300 | self.menuSettings.addAction(self.DownloadDatabase) 301 | self.menuSettings.addSeparator() 302 | self.menuSettings.addAction(self.About) 303 | self.menuSettings.addAction(self.Special_thanks) 304 | 305 | self.menubar.addAction(self.menuSettings.menuAction()) 306 | 307 | """ 308 | ################################################################# 309 | SIGNALS 310 | ################################################################# 311 | """ 312 | self.AvatarIconsRadio.toggled["bool"].connect(self.SysIconsInfo.hide) 313 | self.AvatarIconsRadio.toggled["bool"].connect(self.GameIconsInfo.hide) 314 | self.GameIconsRadio.toggled["bool"].connect(self.GameIconsInfo.setVisible) 315 | self.SystemIconsRadio.toggled["bool"].connect(self.SysIconsInfo.setVisible) 316 | QtCore.QMetaObject.connectSlotsByName(window) 317 | 318 | self.About.triggered.connect(self.open_about) 319 | self.Options.triggered.connect(self.open_options) 320 | self.ConnectBtn.clicked.connect(self.check_ip_port) 321 | self.Remove_cache.triggered.connect(self.remove_cache) 322 | self.Special_thanks.triggered.connect(self.open_credits) 323 | self.DownloadDatabase.triggered.connect(self.download_database) 324 | 325 | # Update the cache and set following values from Settings.json 326 | cache = self.settings.update_local_cache(self.temp_path) 327 | self.PortInput.setText(cache.get("port")) 328 | self.IpInput.setText(cache.get("ip")) 329 | self.set_language(cache.get("language")) 330 | 331 | # Share pointers of the widgets 332 | self.widgets.set_ip_input(self.IpInput) 333 | self.widgets.set_ip_label(self.IpLabel) 334 | self.widgets.set_cache_bar(self.CacheBar) 335 | self.widgets.set_port_input(self.PortInput) 336 | self.widgets.set_port_label(self.PortLabel) 337 | self.widgets.set_mode_label(self.ModeLabel) 338 | self.widgets.set_cache_label(self.CacheLabel) 339 | self.widgets.set_status_label(self.StatusLabel) 340 | self.widgets.set_game_icon_radio(self.GameIconsRadio) 341 | 342 | """ 343 | ################################################################# 344 | BETA v5 Disable 345 | ################################################################# 346 | """ 347 | 348 | self.AvatarIconsRadio.setEnabled(False) 349 | self.translate_ui() 350 | 351 | def translate_ui(self): 352 | translated_content: dict = self.translation.get_translation( 353 | self.language, "Iconit" 354 | ) 355 | 356 | _translate = QtCore.QCoreApplication.translate 357 | self.GameIconsRadio.setText( 358 | _translate("window", translated_content.get("GameIconsRadio")) 359 | ) 360 | self.SystemIconsRadio.setText( 361 | _translate("window", translated_content.get("SystemIconsRadio")) 362 | ) 363 | self.AvatarIconsRadio.setText( 364 | _translate("window", translated_content.get("AvatarIconsRadio")) 365 | ) 366 | self.ModeLabel.setText( 367 | _translate( 368 | "window", 369 | self.html.span_tag( 370 | translated_content.get("ModeLabel"), "#f2ae30", 16, font=self.font 371 | ), 372 | ) 373 | ) 374 | self.IpLabel.setText( 375 | _translate( 376 | "window", 377 | self.html.span_tag( 378 | translated_content.get("IpLabel"), "#f2ae30", 16, font=self.font 379 | ), 380 | ) 381 | ) 382 | self.CacheLabel.setText( 383 | _translate( 384 | "window", 385 | self.html.span_tag( 386 | translated_content.get("CacheLabel"), "#f2ae30", 10, font=self.font 387 | ), 388 | ) 389 | ) 390 | self.PortLabel.setText( 391 | _translate( 392 | "window", 393 | self.html.span_tag( 394 | translated_content.get("PortLabel"), "#f2ae30", 16, font=self.font 395 | ), 396 | ) 397 | ) 398 | self.StatusLabel.setText( 399 | _translate( 400 | "window", 401 | self.html.span_tag( 402 | translated_content.get("StatusLabel"), "#f2ae30", 18, font=self.font 403 | ), 404 | ) 405 | ) 406 | self.OnlineIconsLink.setText( 407 | _translate( 408 | "window", 409 | self.html.a_tag( 410 | "https://all-exhost.github.io/Icons.html", 411 | translated_content.get("OnlineIconsLink"), 412 | "#f250e7", 413 | 8, 414 | font=self.font, 415 | ), 416 | ) 417 | ) 418 | self.PaypalLink.setText( 419 | _translate( 420 | "window", 421 | self.html.a_tag( 422 | "https://www.paypal.com/paypalme/Officialahmed0", 423 | translated_content.get("PaypalLink"), 424 | "#f250e7", 425 | 8, 426 | font=self.font, 427 | ), 428 | ) 429 | ) 430 | self.TwitterLink.setText( 431 | _translate( 432 | "window", 433 | self.html.a_tag( 434 | "https://twitter.com/OfficialAhmed0", 435 | f"{translated_content.get('TwitterLink')} @OfficialAhmed0", 436 | "#f250e7", 437 | 8, 438 | font=self.font, 439 | ), 440 | ) 441 | ) 442 | self.SysIconsInfo.setText( 443 | _translate( 444 | "window", 445 | self.html.span_tag( 446 | translated_content.get("SysIconsInfo"), "#f2ae30", 8, font=self.font 447 | ), 448 | ) 449 | ) 450 | self.GameIconsInfo.setText( 451 | _translate( 452 | "window", 453 | self.html.span_tag( 454 | translated_content.get("GameIconsInfo"), 455 | "#f2ae30", 456 | 8, 457 | font=self.font, 458 | ), 459 | ) 460 | ) 461 | self.TitleLink.setText( 462 | _translate( 463 | "window", 464 | self.html.a_tag( 465 | "https://github.com/OfficialAhmed/Iconit-PS4/releases", 466 | f"{translated_content.get('TitleLink')} v{self.app_version}", 467 | "#f250e7", 468 | 18, 469 | font=self.font, 470 | ), 471 | ) 472 | ) 473 | 474 | self.About.setText(_translate("window", translated_content.get("About"))) 475 | self.actionAbout.setText( 476 | _translate("window", translated_content.get("actionAbout")) 477 | ) 478 | self.Options.setText(_translate("window", translated_content.get("Options"))) 479 | self.ConnectBtn.setText( 480 | _translate("window", translated_content.get("ConnectBtn")) 481 | ) 482 | self.menuSettings.setTitle( 483 | _translate("window", translated_content.get("menuSettings")) 484 | ) 485 | 486 | self.Remove_cache.setText( 487 | _translate("window", translated_content.get("Remove_cache")) 488 | ) 489 | self.Special_thanks.setText( 490 | _translate("window", translated_content.get("Special_thanks")) 491 | ) 492 | self.DownloadDatabase.setText( 493 | _translate("window", translated_content.get("DownloadDatabase")) 494 | ) 495 | self.ConnectBtn.setShortcut("Return") 496 | self.Options.setShortcut("alt") 497 | 498 | 499 | def open_options(self): 500 | self.window = QtWidgets.QWidget() 501 | self.ui = Settings.Ui() 502 | self.ui.setupUi(self.window) 503 | self.window.show() 504 | 505 | 506 | def open_about(self): 507 | self.alerts.display("about", "About") 508 | 509 | 510 | def open_credits(self): 511 | self.alerts.display("credits", "CUSTOMspecial_thanks") 512 | 513 | 514 | def download_database(self): 515 | game_db = self.mode.get("game").get("database").save() 516 | apps_db = self.mode.get("system apps").get("database").save() 517 | txt = "" 518 | 519 | if game_db[0] == True: 520 | txt += "Games Database Successful \n" 521 | else: 522 | txt += f"Games Database Unuccessful | {game_db[1]} \n" 523 | 524 | if apps_db[0] == True: 525 | txt += "Apps Database Successful \n" 526 | else: 527 | txt += f"Apps Database Unuccessful | {apps_db[1]} \n" 528 | 529 | self.alerts.display( 530 | "database", 531 | txt 532 | + " \n\nNOTE: DON'T USE THIS OPTION MANY TIMES OR YOU WILL BE BLOCKED BY THE SERVER, 2 OR 3 TIMES A DAY", 533 | ) 534 | 535 | def remove_cache(self): 536 | try: 537 | ignore_cache = (".gitkeep", "Database.json") 538 | 539 | path = self.mode.get("game").get("cache path") 540 | all = os.listdir(path) 541 | for cache in all: 542 | if cache not in ignore_cache: 543 | os.remove(f"{path}{cache}") 544 | 545 | path = self.mode.get("system apps").get("cache path") 546 | all = os.listdir(path) 547 | for cache in all: 548 | if cache not in ignore_cache: 549 | os.remove(f"{path}{cache}") 550 | 551 | self.alerts.display("cache", "CUSTOMdoneRmvCache") 552 | 553 | except PermissionError: 554 | self.alerts.display("cache", "PermissionDenied") 555 | except Exception as e: 556 | self.alerts.display("cache", str(e)) 557 | --------------------------------------------------------------------------------