├── 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 | 
139 |
140 | 
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 |
--------------------------------------------------------------------------------