@@ -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.
0 commit comments