Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,14 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v2.6.3
name: v2.6.3
tag_name: v2.7.0
name: v2.7.0
body: |
Store updates in the correct place on MacOS.
Allow users to change the folder naming structure from ID - Name to Name - ID in the settings.

These were getting shoved into the app's Contents/MacOS folder. Now they should be next to the app.
Include brackets around the ID in the folder name.

Set the min and max size for the main window so that it cannot be expanded horizontally on Linux and MacOS.
draft: false
prerelease: false
files: |
Expand Down
81 changes: 59 additions & 22 deletions PySN.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,32 +65,36 @@ def get_config():
config.read(config_path)
save_dir = config.get('paths', 'downloads')
rpcs3_dir = config.get('paths', 'RPCS3')
return save_dir, rpcs3_dir
folder_format = config.get('formats', 'folder_format', fallback='id_name')
return save_dir, rpcs3_dir, folder_format

#Saves the config file.
def save_config(mode, save_dir , rpcs3_dir):
def save_config(mode, save_dir , rpcs3_dir, folder_format='id_name'):
config = ConfigParser()
normalized_path, config_dir = ConfigSettings.get_path()
config_path = os.path.join(config_dir, 'config.ini')
with open(config_path, mode) as ini:
config.add_section('paths')
config.set('paths', 'downloads', str(save_dir))
config.set('paths', 'RPCS3', str(rpcs3_dir))
config.add_section('formats')
config.set('formats', 'folder_format', folder_format)
config.write(ini)

#Checks for the config file, and gets the settings from it. Saves default config if none present.
def check_config():
normalized_path, config_dir = ConfigSettings.get_path()
config_path = os.path.join(config_dir, 'config.ini')
if os.path.exists(config_path):
save_dir, rpcs3_dir = ConfigSettings.get_config()
save_dir, rpcs3_dir, folder_format = ConfigSettings.get_config()
else:
save_dir = (normalized_path + '/Updates/')
rpcs3_dir = 'No Games.yml Location Set!'
ConfigSettings.save_config('x', save_dir , rpcs3_dir)
return save_dir, rpcs3_dir
folder_format = 'id_name'
ConfigSettings.save_config('x', save_dir , rpcs3_dir, folder_format)
return save_dir, rpcs3_dir, folder_format

save_dir, rpcs3_dir = ConfigSettings.check_config()
save_dir, rpcs3_dir, folder_format = ConfigSettings.check_config()

#Used to send messages to the queue
class ButtonAction(Enum):
Expand All @@ -106,9 +110,9 @@ def __init__(self, *args, **kwargs):
self.geometry('540x320')
self.resizable(0,0)
self.title('Settings')
self.grid_rowconfigure((0, 1, 2, 3, 4), weight=1)
self.grid_rowconfigure((0, 1, 2, 3, 4, 5, 6), weight=1)
self.grid_columnconfigure((0, 1, 2, 3, 4, 5), weight=1)
self.temp_save, self.temp_rpcs3 = ConfigSettings.check_config()
self.temp_save, self.temp_rpcs3, self.temp_folder_format = ConfigSettings.check_config()
if sys.platform == 'win32':
self.after(200, lambda: self.iconbitmap(resource_path("AphIcon.ico")))

Expand All @@ -124,14 +128,20 @@ def __init__(self, *args, **kwargs):
self.rpcs3_dir_label.grid(row=2, column=1, columnspan=4, padx=0, pady=(0,0), sticky='sew')

self.yaml_dir_field = customtkinter.CTkTextbox(master=self, height=25, width = 400, wrap='none')
self.yaml_dir_field.grid(row=3, column=1, columnspan=3, padx=5, pady=(0,25), sticky='new')
self.yaml_dir_field.grid(row=3, column=1, columnspan=3, padx=5, pady=(0,0), sticky='new')
self.edit_button2 = customtkinter.CTkButton(master=self, width = 50, text='Edit', command = self.button_yml_loc)
self.edit_button2.grid(row=3, padx=5, pady=(0,0), column=4, sticky='new')

self.folder_format_label = customtkinter.CTkLabel(master=self, text='Folder Naming:', anchor='center')
self.folder_format_label.grid(row=4, column=1, columnspan=4, padx=0, pady=(0,0), sticky='sew')
self.folder_format_toggle = customtkinter.CTkSegmentedButton(master=self, values=['ID - Name', 'Name - ID'])
self.folder_format_toggle.grid(row=5, column=1, columnspan=4, padx=5, pady=(0,30), sticky='ew')
self.folder_format_toggle.set('ID - Name' if folder_format == 'id_name' else 'Name - ID')

self.save_button = customtkinter.CTkButton(master=self, text='Save', width = 100, command = self.button_save)
self.save_button.grid(row=4, padx=(0,5), column=2, sticky='e')
self.save_button.grid(row=6, padx=(0,5), column=2, sticky='e')
self.cancel_button = customtkinter.CTkButton(master=self, text='Cancel', width = 100, command=self.destroy)
self.cancel_button.grid(row=4, padx=(5,135), column=3, columnspan=2, sticky='w')
self.cancel_button.grid(row=6, padx=(5,135), column=3, columnspan=2, sticky='w')

self.save_dir_field.insert('0.0',self.temp_save)
self.save_dir_field.configure(state='disabled')
Expand Down Expand Up @@ -165,6 +175,7 @@ def button_yml_loc(self):
def button_save(self):
global rpcs3_dir
global save_dir
global folder_format
if self.temp_save.endswith('/'):
save_dir = self.temp_save
else:
Expand All @@ -173,7 +184,8 @@ def button_save(self):
rpcs3_dir = self.temp_rpcs3
else:
rpcs3_dir = self.temp_rpcs3 + '/'
ConfigSettings.save_config('w', save_dir , rpcs3_dir)
folder_format = 'id_name' if self.folder_format_toggle.get() == 'ID - Name' else 'name_id'
ConfigSettings.save_config('w', save_dir , rpcs3_dir, folder_format)
self.destroy()

#Download All window. I did not create the downall function as part of this class for whatever reason...
Expand Down Expand Up @@ -323,6 +335,8 @@ def __init__(self):
self.geometry('760x640')
self.title('PySN')
self.resizable(0,1)
self.minsize(760, 0)
self.maxsize(760, 99999)
self.toplevel_window = None
self.total_download_size = 0
self.completed_download_size = 0
Expand Down Expand Up @@ -449,9 +463,26 @@ def update_total_progress_ui(self):
self.after(50, self.update_total_progress_ui)

#Checks if the download path already exists and if hashes match. Changes the buttons and status label accordingly.
def is_shit_there(self, download_path, index, fileloc, console, sha1, expected_size=None):
def is_shit_there(self, name, title_id, download_path, index, fileloc, console, sha1, expected_size=None):
if not path.exists(fileloc):
return 0
if title_id and name:
if folder_format == 'id_name':
alt_download_path = save_dir + console + '/' + name + ' [' + title_id + ']'
else:
alt_download_path = save_dir + console + '/[' + title_id + '] ' + name
alt_fileloc = alt_download_path + '/' + path.basename(fileloc)
legacy_path = save_dir + console + '/' + title_id + ' ' + name
legacy_fileloc = legacy_path + '/' + path.basename(fileloc)
if path.exists(alt_fileloc):
download_path = alt_download_path
fileloc = alt_fileloc
elif path.exists(legacy_fileloc):
download_path = legacy_path
fileloc = legacy_fileloc
else:
return 0
else:
return 0

self.textbox.status_list[index].configure(text_color='yellow',text='Checking Hash...')
self.textbox.dlbutton_list[index].configure(text='Redownload', state='disabled')
Expand Down Expand Up @@ -622,12 +653,15 @@ def search(self, title_id, console, root, game_name):

#Assign a download path with some handling for odd characters in the game name. Add Widgets to the textbox and check if the file already exists.
name = game_name.replace(':', ' -').replace('/', ' ').replace('?', '').strip()
download_path = save_dir + console + '/' + title_id + ' ' + name
if folder_format == 'id_name':
download_path = save_dir + console + '/[' + title_id + '] ' + name
else:
download_path = save_dir + console + '/' + name + ' [' + title_id + ']'
update_file = path.basename(url)
fileloc = (download_path + '/' + update_file)
self.after(0, lambda gn=game_name, tid=title_id, v=ver, u=url, c=console, us=update_size, s=sha1, dp=download_path, fl=fileloc:
self.after(0, lambda gn=name, tid=title_id, v=ver, u=url, c=console, us=update_size, s=sha1, dp=download_path, fl=fileloc:
(self.textbox.add_item(gn, tid, ' v' + v, u, c, us, s, len(self.textbox.dlbutton_list), dp, fl),
self.is_shit_there(dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
self.is_shit_there(gn, tid, dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))

elif game_name == 'Invalid ID':
self.after(0, lambda: self.textbox.add_item('Invalid ID: ' + title_id, '', '', '', '', 0, '', '', '', ''))
Expand Down Expand Up @@ -667,12 +701,15 @@ def search_no_drm(self, title_id, console, root, game_name):
update_size_list.append(int((item.get('size'))))
name_list.append(game_name.replace(':', ' -').replace('/', ' ').replace('?', '').strip())
for version in package_list:
download_path = (save_dir + console + '/' + title_id + ' ' + name_list[i])
if folder_format == 'id_name':
download_path = (save_dir + console + '/[' + title_id + '] ' + name_list[i])
else:
download_path = (save_dir + console + '/' + name_list[i] + ' [' + title_id + ']')
update_file = 'DRM-Free ' + path.basename(url_list[i])
fileloc = (download_path + '/' + update_file)
self.after(0, lambda gn=name_list[i], tid=title_id, v=version, u=url_list[i], c=console, us=update_size_list[i], s=sha1_list[i], dp=download_path, fl=fileloc:
(self.textbox.add_item(gn, tid, ' v' + v + ' DRM-Free', u, c, us, s, len(self.textbox.dlbutton_list), dp, fl),
self.is_shit_there(dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
self.is_shit_there(gn, tid, dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
i = i+1
else: pass
else: pass
Expand Down Expand Up @@ -713,7 +750,7 @@ def search_ps3_fw(self, console):

self.after(0, lambda gn=game_name, tid=title_id, v=ver, u=url, c=console, us=update_size, s=sha1, dp=download_path, fl=fileloc:
(self.textbox.add_item(gn, tid, ' v' + v, u, c, us, s, len(self.textbox.dlbutton_list), dp, fl),
self.is_shit_there(dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
self.is_shit_there(gn, tid, dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))

else: self.after(0, lambda: self.textbox.add_item('Error Connecting to Server', '', '', '', '', 0, '', '', '', ''))

Expand Down Expand Up @@ -792,7 +829,7 @@ def search_ps4_ps5_vita_fw(self, console):

self.after(0, lambda gn=game_name, tid=title_id, v=ver_list[i], u=url, c=console, us=update_size_list[i], s=sha1, dp=download_path, fl=fileloc:
(self.textbox.add_item(gn, tid, ' v' + v, u, c, us, s, len(self.textbox.dlbutton_list), dp, fl),
self.is_shit_there(dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
self.is_shit_there(gn, tid, dp, len(self.textbox.dlbutton_list) - 1, fl, c, s, us)))
i = i+1
else: self.after(0, lambda: self.textbox.add_item('Error Connecting to Server', '', '', '', '', 0, '', '', '', ''))

Expand Down Expand Up @@ -894,7 +931,7 @@ def try_set(widget, val):
if status.cget('text') == 'Download Cancelled!':
os.remove(fileloc)
else:
self.is_shit_there(download_path, index, fileloc, console, sha1, size)
self.is_shit_there(name, title_id, download_path, index, fileloc, console, sha1, size)
else: pass

#Downloads all files, or only new files based on the check box in the downall window. Pretty sure it belongs in the DownloadAllWindow class.
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ MacOS
------------------------
Just download PySN_MacOS.zip from [Releases](https://github.com/AphelionWasTaken/PySN/releases/latest) and run the executable.

It is recommended that you move the extracted PySN app to a new folder/location. Otherwise downloads will be sent to the AppTranslocation folder by default. You can also change the download location in the settings.

Linux/BSD
------------------------
Just download PySN_Linux.zip from [Releases](https://github.com/AphelionWasTaken/PySN/releases/latest) and run the executable.
Expand All @@ -42,9 +40,9 @@ If you want to download firmware, just type in "fw" or "firmware" and hit enter

File Locations
------------------------
Title Updates are downloaded into an Updates folder within the directory containing PySN by default, and are separated by console and game (e.g. `PySN/Updates/PlayStation 3/BCUS98114 Gran Turismo 5`).
Title Updates are downloaded into an Updates folder within the directory containing PySN by default, and are separated by console and game (e.g. `PySN/Updates/PlayStation 3/[BCUS98114] Gran Turismo 5`).

You can change the update folder location in PySN's settings, but your updates will always be separated out by console and game.
You can change the update folder location in PySN's settings, as well as the folder naming structure if you prefer to have the game title before the game ID (for sorting alphabetically by title).

On Windows the config file is saved to the directory containing PySN.exe. On MacOS it is in Home/Library/Application Support/PySN. On Linux is is in Home/.config/PySN

Expand All @@ -64,11 +62,13 @@ Or run it however else you want, I don't care, I'm not a cop.

Screenshots
============
| | |
|:---:|:---:|
| <img height = 337 width = 400 src="https://github.com/user-attachments/assets/abe6727c-f83b-4eb8-894b-fdd0203bd064"> | <img height = 337 width = 400 src="https://github.com/user-attachments/assets/c956cfef-1644-4a43-accf-2f5fc9503fa3"> |
| <img height = 281.5 width = 400 src="https://github.com/user-attachments/assets/c4fc3cff-4594-4a49-bbb2-4d7e8b6940a1"> | <img height = 281.5 width = 400 src="https://github.com/user-attachments/assets/dbca640a-f7e6-456a-b494-ca7cbc5f830a">

<p align="center">
<img height = 337 width = 400 src="https://github.com/user-attachments/assets/abe6727c-f83b-4eb8-894b-fdd0203bd064" >
<img height = 337 width = 400 src="https://github.com/user-attachments/assets/c956cfef-1644-4a43-accf-2f5fc9503fa3" >
<p align="center">
<img height = 281.5 width = 400 src="https://github.com/user-attachments/assets/c4fc3cff-4594-4a49-bbb2-4d7e8b6940a1" >
<img height = 281.5 width = 400 src="https://github.com/user-attachments/assets/dbca640a-f7e6-456a-b494-ca7cbc5f830a" >
</p>


Contributors
Expand All @@ -77,4 +77,4 @@ Contributors
- [GalCiv](https://github.com/RipleyTom) - Thread handling/scheduling for downloads
- [Darkhost](https://github.com/Darkhost1999) - Testing/bug reporting
- [schm1dtmac](https://github.com/schm1dtmac) - Icns file and MacOS arm64 testing
- [FlexBy420](https://github.com/flexby420) - PlayStation 5 firmware searches
- [FlexBy420](https://github.com/flexby420) - PlayStation 5 firmware searches
Loading