diff --git a/cmd/WiiUDownloader/mainwindow.go b/cmd/WiiUDownloader/mainwindow.go index 34d4f14..6af14b5 100644 --- a/cmd/WiiUDownloader/mainwindow.go +++ b/cmd/WiiUDownloader/mainwindow.go @@ -355,6 +355,7 @@ func (mw *MainWindow) onCategoryToggled(button *gtk.ToggleButton) { func (mw *MainWindow) onDecryptContentsMenuItemClicked() { selectedPath, err := dialog.Directory().Title("Select the path to decrypt").Browse() if err != nil { + mw.progressWindow.Window.Close() return } @@ -383,6 +384,7 @@ func (mw *MainWindow) isSelectionInQueue() bool { if !isInQueue.(bool) { allTitlesInQueue = false } + inQueue.Unset() } }) return allTitlesInQueue @@ -469,6 +471,7 @@ func (mw *MainWindow) onAddToQueueClicked() { nameStr, _ := name.GetString() mw.addToQueue(tidStr, nameStr) mw.addToQueueButton.SetLabel("Remove from queue") + name.Unset() } else { mw.removeFromQueue(tidStr) mw.addToQueueButton.SetLabel("Add to queue") @@ -477,6 +480,8 @@ func (mw *MainWindow) onAddToQueueClicked() { queueModel, _ := mw.treeView.GetModel() queueModel.(*gtk.ListStore).SetValue(row, IN_QUEUE_COLUMN, addToQueue) mw.treeView.SetCursor(path, mw.treeView.GetColumn(IN_QUEUE_COLUMN), false) + inQueue.Unset() + tid.Unset() } }) } @@ -497,6 +502,7 @@ func (mw *MainWindow) updateTitlesInQueue() { tidNum, _ := strconv.ParseUint(tidStr, 16, 64) isInQueue := mw.isTitleInQueue(wiiudownloader.TitleEntry{TitleID: tidNum}) storeRef.SetValue(iter, IN_QUEUE_COLUMN, isInQueue) + tid.Unset() } } if !storeRef.IterNext(iter) { diff --git a/downloader.go b/downloader.go index e3e5b5c..38ffea7 100644 --- a/downloader.go +++ b/downloader.go @@ -2,6 +2,8 @@ package wiiudownloader import ( "bytes" + "crypto/aes" + "crypto/cipher" "encoding/binary" "encoding/hex" "fmt" @@ -93,11 +95,12 @@ func downloadFile(progressWindow *ProgressWindow, client *grab.Client, url strin resp := client.Do(req) - progressWindow.label.SetText(path.Base(outputPath)) + filePath := path.Base(outputPath) go func(err *error) { for !resp.IsComplete() { glib.IdleAdd(func() { + progressWindow.label.SetText(filePath) progressWindow.bar.SetFraction(resp.Progress()) progressWindow.percentLabel.SetText(fmt.Sprintf("%.0f%%", 100*resp.Progress())) }) @@ -113,6 +116,9 @@ func downloadFile(progressWindow *ProgressWindow, client *grab.Client, url strin }(&err) for { + for gtk.EventsPending() { + gtk.MainIteration() + } if done { break } @@ -131,7 +137,7 @@ func DownloadTitle(titleID string, outputDirectory string, doDecryption bool, pr }) outputDir := strings.TrimRight(outputDirectory, "/\\") baseURL := fmt.Sprintf("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%s", titleID) - titleKeyBytes, err := hex.DecodeString(titleID) + titleIDBytes, err := hex.DecodeString(titleID) if err != nil { return err } @@ -210,29 +216,50 @@ func DownloadTitle(titleID string, outputDirectory string, doDecryption bool, pr defer certFile.Close() fmt.Printf("[Info] Certificate saved to ./%v \n", certPath) + c, err := aes.NewCipher(commonKey) + if err != nil { + return fmt.Errorf("failed to create AES cipher: %w", err) + } + + decryptedTitleKey := make([]byte, len(encryptedTitleKey)) + cbc := cipher.NewCBCDecrypter(c, append(titleIDBytes, make([]byte, 8)...)) + cbc.CryptBlocks(decryptedTitleKey, encryptedTitleKey) + + cipherHashTree, err := aes.NewCipher(decryptedTitleKey) + if err != nil { + return fmt.Errorf("failed to create AES cipher: %w", err) + } + + var id uint32 + tmdDataReader := bytes.NewReader(tmdData) + for i := 0; i < int(contentCount); i++ { offset := 2820 + (48 * i) - var id uint32 - if err := binary.Read(bytes.NewReader(tmdData[offset:offset+4]), binary.BigEndian, &id); err != nil { + tmdDataReader.Seek(int64(offset), 0) + if err := binary.Read(tmdDataReader, binary.BigEndian, &id); err != nil { return err } - appPath := filepath.Join(outputDir, fmt.Sprintf("%08X.app", id)) + filePath := filepath.Join(outputDir, fmt.Sprintf("%08X.app", id)) downloadURL = fmt.Sprintf("%s/%08X", baseURL, id) - if err := downloadFile(progressWindow, client, downloadURL, appPath); err != nil { + if err := downloadFile(progressWindow, client, downloadURL, filePath); err != nil { return err } if tmdData[offset+7]&0x2 == 2 { - h3Path := filepath.Join(outputDir, fmt.Sprintf("%08X.h3", id)) + filePath = filepath.Join(outputDir, fmt.Sprintf("%08X.h3", id)) downloadURL = fmt.Sprintf("%s/%08X.h3", baseURL, id) - if err := downloadFile(progressWindow, client, downloadURL, h3Path); err != nil { + if err := downloadFile(progressWindow, client, downloadURL, filePath); err != nil { return err } var content contentInfo content.Hash = tmdData[offset+16 : offset+0x14] content.ID = fmt.Sprintf("%08X", id) - binary.Read(bytes.NewReader(tmdData[offset+8:offset+15]), binary.BigEndian, &content.Size) - if err := checkContentHashes(outputDirectory, encryptedTitleKey, titleKeyBytes, content); err != nil { + tmdDataReader.Seek(int64(offset+8), 0) + if err := binary.Read(tmdDataReader, binary.BigEndian, &content.Size); err != nil { + return err + } + if err := checkContentHashes(outputDirectory, content, &cipherHashTree); err != nil { + fmt.Println(err) return err } } diff --git a/hash.go b/hash.go index 7437745..e5fd1d9 100644 --- a/hash.go +++ b/hash.go @@ -11,16 +11,8 @@ import ( var commonKey = []byte{0xD7, 0xB0, 0x04, 0x02, 0x65, 0x9B, 0xA2, 0xAB, 0xD2, 0xCB, 0x0D, 0xB2, 0x7F, 0xA2, 0xB6, 0x56} -func checkContentHashes(path string, encryptedTitleKey []byte, titleID []byte, content contentInfo) error { - c, err := aes.NewCipher(commonKey) - if err != nil { - return fmt.Errorf("failed to create AES cipher: %w", err) - } - - decryptedTitleKey := make([]byte, len(encryptedTitleKey)) - cbc := cipher.NewCBCDecrypter(c, append(titleID, make([]byte, 8)...)) - cbc.CryptBlocks(decryptedTitleKey, encryptedTitleKey) - +func checkContentHashes(path string, content contentInfo, cipherHashTree *cipher.Block) error { + cHashTree := *cipherHashTree h3Data, err := os.ReadFile(fmt.Sprintf("%s/%s.h3", path, content.ID)) if err != nil { return fmt.Errorf("failed to read H3 hash tree file: %w", err) @@ -29,6 +21,7 @@ func checkContentHashes(path string, encryptedTitleKey []byte, titleID []byte, c if err != nil { return fmt.Errorf("failed to open encrypted file: %w", err) } + defer encryptedFile.Close() h3Hash := sha1.Sum(h3Data) if !reflect.DeepEqual(h3Hash[:8], content.Hash[:8]) { @@ -36,22 +29,18 @@ func checkContentHashes(path string, encryptedTitleKey []byte, titleID []byte, c } chunkCount := int(content.Size / 0x10000) - decryptedContent := make([]byte, content.Size) + decryptedContent := make([]byte, 0x400) h0HashNum := 0 h1HashNum := 0 h2HashNum := 0 h3HashNum := 0 + buffer := make([]byte, 0x400) + iv := make([]byte, aes.BlockSize) for chunkNum := 0; chunkNum < chunkCount; chunkNum++ { - cipherHashTree, err := aes.NewCipher(decryptedTitleKey) - if err != nil { - return fmt.Errorf("failed to create AES cipher: %w", err) - } - hashTree := cipher.NewCBCDecrypter(cipherHashTree, make([]byte, aes.BlockSize)) - buffer := make([]byte, 0x400) encryptedFile.Read(buffer) - hashTree.CryptBlocks(decryptedContent, buffer) + cipher.NewCBCDecrypter(cHashTree, iv).CryptBlocks(decryptedContent, buffer) h0Hashes := decryptedContent[0:0x140] h1Hashes := decryptedContent[0x140:0x280]