Skip to content

Commit 7771e75

Browse files
Merge pull request #150 from alvindimas05/main
Ryujinx Support for MacOS
2 parents 63c0fac + 612fe12 commit 7771e75

File tree

15 files changed

+188
-53
lines changed

15 files changed

+188
-53
lines changed

.github/workflows/build-multiplatform.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ jobs:
2525
- name: Linux
2626
os: ubuntu-20.04
2727
artifactsPath: ./src/dist/*.AppImage
28+
- name: MacOS Intel
29+
os: macos-latest-large
30+
artifactsPath: ./src/dist/*.zip
31+
- name: MacOS Silicon
32+
os: macos-latest
33+
artifactsPath: ./src/dist/*.zip
2834
- name: Windows_dir
2935
os: windows-latest
3036
artifactsPath: ./src/dist/*.zip
@@ -34,10 +40,10 @@ jobs:
3440
runs-on: ${{ matrix.os }}
3541
steps:
3642
- uses: actions/checkout@v3
37-
- name: Set up Python 3.11
43+
- name: Set up Python 3.12
3844
uses: actions/setup-python@v3
3945
with:
40-
python-version: "3.11.5"
46+
python-version: "3.12.0"
4147
- name: Install dependencies Linux
4248
if: startsWith(matrix.name, 'Linux' )
4349
run: |
@@ -48,16 +54,21 @@ jobs:
4854
run: |
4955
python -m pip install --upgrade pip
5056
pip install requests patool screeninfo packaging wmi GPUtil psutil pyinstaller https://github.com/MaxLastBreath/ttkbootstrapFIX/zipball/master
57+
- name: Install dependencies MacOS
58+
if: startsWith(matrix.name, 'MacOS' )
59+
run: |
60+
python -m pip install --upgrade pip
61+
pip install requests patool screeninfo packaging wmi GPUtil psutil pyinstaller https://github.com/MaxLastBreath/ttkbootstrapFIX/zipball/master
5162
- name: Build ${{ matrix.name }}
5263
run: |
5364
cd ./src/
54-
python ./compile_onedir.py --version ${{ inputs.tag }}
55-
if: endsWith(matrix.name, 'dir')
65+
python ./compile_onedir.py
66+
if: ${{ endsWith(matrix.name, 'dir') || startsWith(matrix.name, 'MacOS') }}
5667
- name: Build ${{ matrix.name }}
5768
run: |
5869
cd ./src/
59-
python ./compile.py --version ${{ inputs.tag }}
60-
if: endsWith(matrix.name, 'dir') == false
70+
python ./compile.py
71+
if: ${{ !endsWith(matrix.name, 'dir') && !startsWith(matrix.name, 'MacOS') }}
6172
- name: Upload artifacts
6273
uses: actions/upload-artifact@v3
6374
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ src/modules/logger.txt
3131
*.txt
3232
/src/dist
3333
/src/build
34+
venv

src/GUI/LOGO.icns

448 KB
Binary file not shown.

src/How to Compile.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ For Linux
1212

1313
pyinstaller run.py --onefile --name TOTK_Mod_Manager --add-data "GUI:GUI" --hidden-import PIL --hidden-import PIL._tkinter_finder --hidden-import PIL._tkinter
1414

15+
For MacOS
16+
17+
pyinstaller run.py --onefile --name TOTK_Mod_Manager --add-data "GUI:GUI" --add-data "json.data:json.data" --windowed
18+
1519
For Linux users aiming to generate Windows executable files or Windows users aiming to create Linux AppImage files, you can utilize the Docker image available for building purposes.
1620

1721
Please refer to the following repository for guidance: https://github.com/batonogov/docker-pyinstaller

src/compile.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import subprocess
33
import os
44
from configuration.settings import *
5-
latest_version = Version.strip("manager-")
65

6+
latest_version = Version.strip("manager-")
77

88
if __name__ == "__main__":
99
if platform.system() == "Windows":
@@ -18,7 +18,7 @@
1818
]
1919
subprocess.run(command, shell=True)
2020

21-
if platform.system() == "Linux":
21+
elif platform.system() == "Linux":
2222
command = [
2323
"pyinstaller",
2424
"--onefile",
@@ -32,3 +32,21 @@
3232
"--hidden-import=ttkbootstrap"
3333
]
3434
subprocess.run(command, check=True)
35+
36+
elif platform.system() == "Darwin":
37+
command = [
38+
"pyinstaller",
39+
"--onefile",
40+
"--windowed",
41+
"--noconfirm",
42+
f"--name=TOTK Optimizer",
43+
"run.py",
44+
"--add-data", "GUI:GUI",
45+
"--add-data", "json.data:json.data",
46+
"--icon", "GUI/LOGO.icns",
47+
"--hidden-import=PIL",
48+
"--hidden-import=PIL._tkinter_finder",
49+
"--hidden-import=ttkbootstrap",
50+
]
51+
subprocess.run(command, check=True)
52+
if os.path.exists("dist/TOTK Optimizer"): os.remove("dist/TOTK Optimizer")

src/compile_onedir.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import zipfile
44
import os
55
from configuration.settings import *
6+
import argparse
67

78
latest_version = Version.strip("manager-")
89

10+
parser = argparse.ArgumentParser()
11+
parser.add_argument('-o', '--os')
12+
args = parser.parse_args()
13+
914
def create_zip(source_dir, dest_file):
1015
with zipfile.ZipFile(dest_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
1116
for root, dirs, files in os.walk(source_dir):
@@ -15,6 +20,18 @@ def create_zip(source_dir, dest_file):
1520
zip_path = os.path.join('TOTK Optimizer', relative_path)
1621
zipf.write(file_path, zip_path)
1722

23+
def delete_directory(folder):
24+
for filename in os.listdir(folder):
25+
file_path = os.path.join(folder, filename)
26+
try:
27+
if os.path.isfile(file_path) or os.path.islink(file_path):
28+
os.unlink(file_path)
29+
elif os.path.isdir(file_path):
30+
shutil.rmtree(file_path)
31+
except Exception as e:
32+
print('Failed to delete %s. Reason: %s' % (file_path, e))
33+
os.rmdir(folder)
34+
1835
if __name__ == "__main__":
1936
if platform.system() == "Windows":
2037
command = [
@@ -27,7 +44,6 @@ def create_zip(source_dir, dest_file):
2744
"--icon", "GUI/LOGO.ico"
2845
]
2946
subprocess.run(command, shell=True)
30-
create_zip(f'dist/TOTK Optimizer {latest_version}', f'dist/TOTK_Optimizer_{latest_version}_Windows.zip')
3147
elif platform.system() == "Linux":
3248
command = [
3349
"pyinstaller",
@@ -42,4 +58,33 @@ def create_zip(source_dir, dest_file):
4258
"--hidden-import=ttkbootstrap"
4359
]
4460
subprocess.run(command, check=True)
45-
create_zip(f'dist/TOTK Optimizer {latest_version}', f'dist/TOTK_Optimizer_{latest_version}_Linux.zip')
61+
create_zip(f'dist/TOTK Optimizer {latest_version}', f'dist/TOTK_Optimizer_{latest_version}_Linux.zip')
62+
63+
elif platform.system() == "Darwin":
64+
command = [
65+
"pyinstaller",
66+
"--onefile",
67+
"--windowed",
68+
"--noconfirm",
69+
f"--name=TOTK Optimizer",
70+
"run.py",
71+
"--add-data", "GUI:GUI",
72+
"--add-data", "json.data:json.data",
73+
"--icon", "GUI/LOGO.icns",
74+
"--hidden-import=PIL",
75+
"--hidden-import=PIL._tkinter_finder",
76+
"--hidden-import=ttkbootstrap",
77+
]
78+
subprocess.run(command, check=True)
79+
80+
processor = "Silicon"
81+
if platform.processor() == "i386":
82+
processor = "Intel"
83+
84+
os.mkdir('dist/archive')
85+
os.rename('dist/TOTK Optimizer.app', 'dist/archive/TOTK Optimizer.app')
86+
create_zip('dist/archive', f'dist/TOTK_Optimizer_{latest_version}_MacOS_{processor}.zip')
87+
88+
# Remove unnecessary files
89+
if os.path.exists("dist/TOTK Optimizer"): os.remove("dist/TOTK Optimizer")
90+
if os.path.exists("dist/archive"): delete_directory("dist/archive")

src/configuration/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
repo_url = 'https://api.github.com/repos/MaxLastBreath/TOTK-mods'
1313
localconfig = "TOTKOptimizer.ini"
1414

15+
if platform.system() == "Darwin":
16+
localconfig = os.path.join(macos_path, localconfig)
17+
1518
# Read Config File.
1619
font = ""
1720
theme = ""

src/form.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ def __init__(self, window):
3636
self.mode = config.get("Mode", "managermode", fallback="Legacy")
3737
self.all_pages = ["main", "extra", "randomizer"]
3838

39+
# Force to Ryujinx default
40+
if platform.system() == "Darwin":
41+
self.mode = "Ryujinx"
42+
3943
# Set neccesary variables.
4044
self.Curr_Benchmark = None
4145
self.Legacydir = None
@@ -712,16 +716,20 @@ def Cheat_UI_elements(self, canvas):
712716
canvas.create_image(0, 0, anchor="nw", image=self.background_UI_Cheats, tags="overlay")
713717

714718
def switchmode(self, command="true"):
719+
715720
if command == "true":
716721
if self.mode == "Legacy":
717722
self.mode = "Ryujinx"
718723
for canvas in self.all_canvas:
719724
canvas.itemconfig("overlay-1", image=self.background_RyuBG)
720725
canvas.itemconfig("information", text=f"{self.mode} TOTK Optimizer")
721726
canvas.itemconfig("Legacy", state="hidden")
722-
self.switch_text.set("Switch to Legacy")
727+
if self.os_platform == "Darwin":
728+
self.switch_text.set("Only Ryujinx supported")
729+
else:
730+
self.switch_text.set("Switch to Legacy")
723731
return
724-
elif self.mode == "Ryujinx":
732+
elif self.mode == "Ryujinx" and self.os_platform != "Darwin":
725733
self.mode = "Legacy"
726734
for canvas in self.all_canvas:
727735
canvas.itemconfig("overlay-1", image=self.background_LegacyBG)
@@ -736,7 +744,10 @@ def switchmode(self, command="true"):
736744
canvas.itemconfig("overlay-1", image=self.background_RyuBG)
737745
canvas.itemconfig("information", text=f"{self.mode} TOTK Optimizer")
738746
canvas.itemconfig("Legacy", state="hidden")
739-
self.switch_text.set("Switch to Legacy")
747+
if self.os_platform == "Darwin":
748+
self.switch_text.set("Only Ryujinx supported")
749+
else:
750+
self.switch_text.set("Switch to Legacy")
740751
return
741752
elif command == "Mode":
742753
return self.mode

src/modules/checkpath.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def checkpath(self, mode):
2525
if self.os_platform == "Linux":
2626
if mode == "Legacy":
2727
flatpak = os.path.join(home_directory, ".var", "app", "org.yuzu_emu.yuzu", "config", "yuzu")
28+
flatpak_torzu = os.path.join(home_directory, ".var", "app", "onion.torzu_emu.torzu", "config", "yuzu")
2829
steamdeckdir = os.path.join(home_directory, ".config", "yuzu", "qt-config.ini")
2930

3031
self.Globaldir = os.path.join(home_directory, ".local", "share", "yuzu")
@@ -45,6 +46,14 @@ def checkpath(self, mode):
4546
new_path = os.path.dirname(os.path.dirname(flatpak))
4647
self.Globaldir = os.path.join(new_path, "data", "yuzu")
4748

49+
# Check for flatpack version of Torzu
50+
if os.path.exists(flatpak_torzu):
51+
log.info("Detected a Legacy flatpack of Torzu!")
52+
self.configdir = os.path.join(flatpak_torzu, "qt-config.ini")
53+
self.TOTKconfig = os.path.join(flatpak_torzu, "custom")
54+
new_path = os.path.dirname(os.path.dirname(flatpak_torzu))
55+
self.Globaldir = os.path.join(new_path, "data", "yuzu")
56+
4857
config_parser = configparser.ConfigParser()
4958
config_parser.read(self.configdir, encoding="utf-8")
5059
self.nand_dir = os.path.normpath(config_parser.get('Data%20Storage', 'nand_directory', fallback=f'{self.Globaldir}/nand')).replace('"', "")
@@ -172,8 +181,21 @@ def checkpath(self, mode):
172181
self.nand_dir = os.path.join(f"{self.Globaldir}", "bis", "user", "save")
173182
self.load_dir = os.path.join(f"{self.Globaldir}", "mods", "contents", "0100f2C0115b6000")
174183
self.sdmc_dir = os.path.join(f"{self.Globaldir}", "sdcard")
175-
self.Legacydir = os.path.join(home_directory, "AppData", "Roaming", "Ryujinx", "mods", "contents", "0100f2C0115B6000")
184+
self.Legacydir = self.load_dir
176185
return
186+
elif self.os_platform == "Darwin":
187+
if mode == "Ryujinx":
188+
self.Globaldir = os.path.join(home_directory, "Library", "Application Support", "Ryujinx")
189+
self.configdir = None
190+
self.TOTKconfig = None
191+
self.ryujinx_config = os.path.join(self.Globaldir, "Config.json")
192+
self.nand_dir = os.path.join(f"{self.Globaldir}", "bis", "user", "save")
193+
self.sdmc_dir = os.path.join(f"{self.Globaldir}", "sdcard")
194+
self.load_dir = os.path.join(f"{self.Globaldir}", "mods", "contents", "0100f2C0115B6000")
195+
self.Legacydir = self.load_dir
196+
return
197+
198+
177199
# Ensure the path exists.
178200
try:
179201
# attempt to create qt-config.ini directories in case they don't exist. Give error to warn user
@@ -196,6 +218,8 @@ def DetectOS(self, mode):
196218
else:
197219
log.warning("qt-config.ini not found, the script will assume default appdata directories, "
198220
"please reopen Legacy for consistency and make sure TOTK is present..!")
221+
elif self.os_platform == "Darwin":
222+
log.info("Detected a MacOS based SYSTEM!")
199223

200224
def load_Legacy_path(self, config_file):
201225
if self.mode == "Legacy":

src/modules/json.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,23 @@
99
from packaging.version import parse
1010
from modules.download import get_zip_list_and_dict
1111
from modules.logger import *
12+
from modules.macos import macos_path
1213

1314
localconfig = "TOTKOptimizer.ini"
1415
ask_again = "Yes"
16+
17+
if platform.system() == "Darwin":
18+
localconfig = os.path.join(macos_path, localconfig)
19+
1520
def load_json(name, url):
1621
global ask_again
1722
# Check if the .presets folder exists, if not, create it
1823
presets_folder = "json.data"
24+
25+
# Once again, set custom path for MacOS to avoid crash
26+
if platform.system() == "Darwin":
27+
presets_folder = os.path.join(macos_path, presets_folder)
28+
1929
if not os.path.exists(presets_folder):
2030
os.makedirs(presets_folder)
2131

@@ -69,6 +79,9 @@ def fetch_local_json(name):
6979
else:
7080
base_path = os.path.dirname(os.path.abspath(__file__))
7181
base_path = os.path.dirname(base_path)
82+
83+
if platform.system() == "Darwin":
84+
base_path = macos_path
7285

7386
return os.path.join(base_path, f'json.data/{name}')
7487

@@ -127,16 +140,21 @@ def load_values_from_json():
127140
"DFPS_dict": DFPS_dict
128141
}
129142
logging.info(f"Attempting to create api.json.")
130-
if not os.path.exists("json.data"):
131-
os.makedirs("json.data")
143+
json_path = "json.data"
144+
145+
if platform.system() == "Darwin":
146+
json_path = os.path.join(macos_path, json_path)
147+
148+
if not os.path.exists(json_path):
149+
os.makedirs(json_path)
132150
try:
133151
logging.info(f"Creating api.json file..")
134-
with open("json.data/api.json", "w", encoding="utf-8") as json_file:
152+
with open(os.path.join(json_path, "api.json"), "w", encoding="utf-8") as json_file:
135153
json.dump(api_json, json_file, indent=4)
136154
except PermissionError as e:
137155
logging.error(f"Permission error has been detected while "
138156
f"creating api.json, attempting to delete api.json.")
139-
shutil.rmtree("json.data/api.json")
157+
shutil.rmtree(os.path.join(json_path, "api.json"))
140158
# Save Time.
141159
if not time_config.has_section("Time"):
142160
time_config["Time"] = {}

src/modules/logger.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@
33
import platform
44
import psutil
55
import GPUtil
6+
import os
67
from modules.scaling import *
7-
8+
from modules.macos import macos_path
89

910
def start_logger():
10-
logging.basicConfig(filename="logger.txt",
11+
filename = "logger.txt"
12+
13+
# Set custom path for MacOS to avoid crash
14+
if platform.system() == "Darwin":
15+
if not os.path.exists(macos_path):
16+
os.makedirs(macos_path)
17+
filename = os.path.join(macos_path, filename)
18+
19+
logging.basicConfig(filename=filename,
1120
filemode='a',
1221
format='TIME: %(asctime)s,%(msecs)d - %(name)s - %(levelname)s: %(message)s',
1322
datefmt='%H:%M:%S',
1423
level=logging.INFO)
1524
new_logger = logging.getLogger('LOGGER')
1625
logging.getLogger('LOGGER').addHandler(logging.StreamHandler(sys.stdout))
1726
return new_logger
27+
1828
log = start_logger()
1929

2030
try:

src/modules/macos.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import os
2+
3+
home_directory = os.path.expanduser("~")
4+
macos_path = os.path.join(home_directory, ".totk-mods")

0 commit comments

Comments
 (0)