Skip to content

Commit ce5d118

Browse files
committed
Create cert.sys during EmuNAND title installation if not found
1 parent 6d38df9 commit ce5d118

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

src/libWiiPy/nand/emunand.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class EmuNAND:
3434
"""
3535
def __init__(self, emunand_root: str | pathlib.Path, callback: Callable | None = None):
3636
self.emunand_root = pathlib.Path(emunand_root)
37-
self.log = callback if callback is not None else None
37+
self.log = callback if callback is not None else lambda x: None
3838

3939
self.import_dir = self.emunand_root.joinpath("import")
4040
self.meta_dir = self.emunand_root.joinpath("meta")
@@ -70,12 +70,14 @@ def install_title(self, title: Title, skip_hash=False) -> None:
7070
skip_hash : bool, optional
7171
Skip the hash check and install the title regardless of its hashes. Defaults to false.
7272
"""
73+
self.log(f"[PROGRESS] Starting install of title with Title ID {title.tmd.title_id}...")
7374
# Save the upper and lower portions of the Title ID, because these are used as target install directories.
7475
tid_upper = title.tmd.title_id[:8]
7576
tid_lower = title.tmd.title_id[8:]
7677

7778
# Tickets are installed as <tid_lower>.tik in /ticket/<tid_upper>/
7879
ticket_dir = self.ticket_dir.joinpath(tid_upper)
80+
self.log(f"[PROGRESS] Installing ticket to \"{ticket_dir}\"...")
7981
ticket_dir.mkdir(exist_ok=True)
8082
ticket_dir.joinpath(f"{tid_lower}.tik").write_bytes(title.ticket.dump())
8183

@@ -86,19 +88,25 @@ def install_title(self, title: Title, skip_hash=False) -> None:
8688
title_dir = title_dir.joinpath(tid_lower)
8789
title_dir.mkdir(exist_ok=True)
8890
content_dir = title_dir.joinpath("content")
91+
self.log(f"[PROGRESS] Installing TMD to \"{content_dir}\"...")
8992
if content_dir.exists():
9093
shutil.rmtree(content_dir) # Clear the content directory so old contents aren't left behind.
9194
content_dir.mkdir(exist_ok=True)
9295
content_dir.joinpath("title.tmd").write_bytes(title.tmd.dump())
96+
self.log(f"[PROGRESS] Installing content to \"{content_dir}\"...")
97+
if skip_hash:
98+
self.log("[WARN] Not checking content hashes! Content validity will not be verified.")
9399
for content_file in range(0, title.tmd.num_contents):
94100
if title.tmd.content_records[content_file].content_type == 1:
95101
content_file_name = f"{title.tmd.content_records[content_file].content_id:08X}".lower()
102+
self.log(f"[PROGRESS] Installing content \"{content_file_name}.app\" to \"{content_dir}\"... ")
96103
content_dir.joinpath(f"{content_file_name}.app").write_bytes(
97104
title.get_content_by_index(content_file, skip_hash=skip_hash))
98105
title_dir.joinpath("data").mkdir(exist_ok=True) # Empty directory used for save data for the title.
99106

100107
# Shared contents need to be installed to /shared1/, with incremental names determined by /shared1/content.map.
101108
content_map_path = self.shared1_dir.joinpath("content.map")
109+
self.log(f"[PROGRESS] Installing shared content to \"{self.shared1_dir}\"...")
102110
content_map = _SharedContentMap()
103111
existing_hashes = []
104112
if content_map_path.exists():
@@ -108,7 +116,10 @@ def install_title(self, title: Title, skip_hash=False) -> None:
108116
for content_file in range(0, title.tmd.num_contents):
109117
if title.tmd.content_records[content_file].content_type == 32769:
110118
if title.tmd.content_records[content_file].content_hash not in existing_hashes:
119+
self.log(f"[PROGRESS] Adding shared content hash to content.map...")
111120
content_file_name = content_map.add_content(title.tmd.content_records[content_file].content_hash)
121+
self.log(f"[PROGRESS] Installing shared content \"{content_file_name}.app\" to "
122+
f"\"{self.shared1_dir}\"...")
112123
self.shared1_dir.joinpath(f"{content_file_name}.app").write_bytes(
113124
title.get_content_by_index(content_file, skip_hash=skip_hash))
114125
self.shared1_dir.joinpath("content.map").write_bytes(content_map.dump())
@@ -120,19 +131,34 @@ def install_title(self, title: Title, skip_hash=False) -> None:
120131
meta_dir = self.meta_dir.joinpath(tid_upper)
121132
meta_dir.mkdir(exist_ok=True)
122133
meta_dir = meta_dir.joinpath(tid_lower)
134+
self.log(f"[PROGRESS] Installing meta data to \"{meta_dir}\"...")
123135
meta_dir.mkdir(exist_ok=True)
124136
meta_dir.joinpath("title.met").write_bytes(title.wad.get_meta_data())
125137

126138
# Ensure we have a uid.sys file created.
127139
uid_sys_path = self.sys_dir.joinpath("uid.sys")
128140
uid_sys = _UidSys()
129141
if not uid_sys_path.exists():
142+
self.log("[WARN] uid.sys does not exist! Creating it with the default entry.")
130143
uid_sys.create()
131144
else:
132145
uid_sys.load(uid_sys_path.read_bytes())
146+
self.log("[PROGRESS] Adding title to uid.sys and assigning a new UID...")
133147
uid_sys.add(title.tmd.title_id)
134148
uid_sys_path.write_bytes(uid_sys.dump())
135149

150+
# Check for a cert.sys and initialize it using the certs in the WAD if it doesn't exist.
151+
cert_sys_path = self.sys_dir.joinpath("cert.sys")
152+
if not cert_sys_path.exists():
153+
self.log("[WARN] cert.sys does not exist! Creating it using certs from the installed title...")
154+
cert_sys_data = b''
155+
cert_sys_data += title.cert_chain.ticket_cert.dump()
156+
cert_sys_data += title.cert_chain.ca_cert.dump()
157+
cert_sys_data += title.cert_chain.tmd_cert.dump()
158+
cert_sys_path.write_bytes(cert_sys_data)
159+
160+
self.log("[PROGRESS] Completed title installation.")
161+
136162
def uninstall_title(self, tid: str) -> None:
137163
"""
138164
Uninstall the Title with the specified Title ID from the EmuNAND. This will leave shared contents unmodified.

src/libWiiPy/title/nus.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,12 @@ def download_tmd(title_id: str, title_version: int | None = None, wiiu_endpoint:
134134
else:
135135
raise Exception("A connection could not be made to the NUS endpoint. The NUS may be unavailable.")
136136
# Handle a 404 if the TID/version doesn't exist.
137-
if response.status_code != 200:
137+
if response.status_code == 404:
138138
raise ValueError("The requested Title ID or TMD version does not exist. Please check the Title ID and Title"
139139
" version and then try again.")
140+
elif response.status_code != 200:
141+
raise Exception(f"An unknown error occurred while downloading the TMD. "
142+
f"Got HTTP status code: {response.status_code}")
140143
total_size = int(response.headers["Content-Length"])
141144
progress(0, total_size)
142145
# Stream the TMD's data in chunks so that we can post updates to the callback function (assuming one was supplied).
@@ -198,9 +201,12 @@ def download_ticket(title_id: str, wiiu_endpoint: bool = False, endpoint_overrid
198201
"override is valid.")
199202
else:
200203
raise Exception("A connection could not be made to the NUS endpoint. The NUS may be unavailable.")
201-
if response.status_code != 200:
204+
if response.status_code == 404:
202205
raise ValueError("The requested Title ID does not exist, or refers to a non-free title. Tickets can only"
203206
" be downloaded for titles that are free on the NUS.")
207+
elif response.status_code != 200:
208+
raise Exception(f"An unknown error occurred while downloading the Ticket. "
209+
f"Got HTTP status code: {response.status_code}")
204210
total_size = int(response.headers["Content-Length"])
205211
progress(0, total_size)
206212
# Stream the Ticket's data just like with the TMD.
@@ -316,10 +322,13 @@ def download_content(title_id: str, content_id: int, wiiu_endpoint: bool = False
316322
"override is valid.")
317323
else:
318324
raise Exception("A connection could not be made to the NUS endpoint. The NUS may be unavailable.")
319-
if response.status_code != 200:
325+
if response.status_code == 404:
320326
raise ValueError("The requested Title ID does not exist, or an invalid Content ID is present in the"
321327
" content records provided.\n Failed while downloading Content ID: 000000" +
322328
content_id_hex)
329+
elif response.status_code != 200:
330+
raise Exception(f"An unknown error occurred while downloading the content. "
331+
f"Got HTTP status code: {response.status_code}")
323332
total_size = int(response.headers["Content-Length"])
324333
progress(0, total_size)
325334
# Stream the content just like the TMD/Ticket.

0 commit comments

Comments
 (0)