diff --git a/decryption.go b/decryption.go index 34f85c9..f73d359 100644 --- a/decryption.go +++ b/decryption.go @@ -15,6 +15,8 @@ import ( "reflect" ) +var commonKey = []byte{0xD7, 0xB0, 0x04, 0x02, 0x65, 0x9B, 0xA2, 0xAB, 0xD2, 0xCB, 0x0D, 0xB2, 0x7F, 0xA2, 0xB6, 0x56} + const ( BLOCK_SIZE = 0x8000 BLOCK_SIZE_HASHED = 0x10000 diff --git a/downloader.go b/downloader.go index eb088b7..9a8eba5 100644 --- a/downloader.go +++ b/downloader.go @@ -3,10 +3,7 @@ package wiiudownloader import ( "bytes" "context" - "crypto/aes" - "crypto/cipher" "encoding/binary" - "encoding/hex" "fmt" "io" "net/http" @@ -54,7 +51,7 @@ func downloadFile(ctx context.Context, progressReporter ProgressReporter, client req.Header.Set("User-Agent", "WiiUDownloader") req.Header.Set("Connection", "Keep-Alive") - req.Header.Set("Accept-Encoding", "") + req.Header.Set("Accept-Encoding", "gzip") resp, err := client.Do(req) if err != nil { @@ -82,37 +79,35 @@ func downloadFile(ctx context.Context, progressReporter ProgressReporter, client ticker := time.NewTicker(250 * time.Millisecond) defer ticker.Stop() - Loop: for { + n, err := resp.Body.Read(buffer) + if err != nil && err != io.EOF { + if doRetries && attempt < maxRetries { + time.Sleep(retryDelay) + break + } + return fmt.Errorf("download error after %d attempts: %+v", attempt, err) + } + + if n == 0 { + break + } + + _, err = file.Write(buffer[:n]) + if err != nil { + if doRetries && attempt < maxRetries { + time.Sleep(retryDelay) + break + } + return fmt.Errorf("write error after %d attempts: %+v", attempt, err) + } + + downloaded += int64(n) select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: progressReporter.UpdateDownloadProgress(downloaded, calculateDownloadSpeed(downloaded, startTime, time.Now()), filePath) - default: - n, err := resp.Body.Read(buffer) - if err != nil && err != io.EOF { - if doRetries && attempt < maxRetries { - time.Sleep(retryDelay) - break - } - return fmt.Errorf("download error after %d attempts: %+v", attempt, err) - } - - if n == 0 { - break Loop - } - - _, err = file.Write(buffer[:n]) - if err != nil { - if doRetries && attempt < maxRetries { - time.Sleep(retryDelay) - break - } - return fmt.Errorf("write error after %d attempts: %+v", attempt, err) - } - - downloaded += int64(n) } } break @@ -129,10 +124,6 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d outputDir := strings.TrimRight(outputDirectory, "/\\") baseURL := fmt.Sprintf("http://ccs.cdn.c.shop.nintendowifi.net/ccs/download/%s", titleID) - titleIDBytes, err := hex.DecodeString(titleID) - if err != nil { - return err - } if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { return err @@ -171,11 +162,6 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d return err } } - tikData, err := os.ReadFile(tikPath) - if err != nil { - return err - } - encryptedTitleKey := tikData[0x1BF : 0x1BF+0x10] var contentCount uint16 if err := binary.Read(bytes.NewReader(tmdData[478:480]), binary.BigEndian, &contentCount); err != nil { @@ -217,20 +203,6 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d defer certFile.Close() logger.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 content Content tmdDataReader := bytes.NewReader(tmdData) @@ -257,14 +229,6 @@ func DownloadTitle(cancelCtx context.Context, titleID, outputDirectory string, d } return err } - content.Hash = tmdData[offset+16 : offset+0x14] - content.Size = contentSizes[i] - if err := checkContentHashes(outputDirectory, content, cipherHashTree); err != nil { - if progressReporter.Cancelled() { - break - } - return err - } } if progressReporter.Cancelled() { break diff --git a/hash.go b/hash.go deleted file mode 100644 index d571c6b..0000000 --- a/hash.go +++ /dev/null @@ -1,83 +0,0 @@ -package wiiudownloader - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/sha1" - "errors" - "fmt" - "os" - "path/filepath" - "reflect" -) - -var commonKey = []byte{0xD7, 0xB0, 0x04, 0x02, 0x65, 0x9B, 0xA2, 0xAB, 0xD2, 0xCB, 0x0D, 0xB2, 0x7F, 0xA2, 0xB6, 0x56} - -func checkContentHashes(path string, content Content, cipherHashTree cipher.Block) error { - h3Data, err := os.ReadFile(filepath.Join(path, fmt.Sprintf("%08X.h3", content.ID))) - if err != nil { - return fmt.Errorf("failed to read H3 hash tree file: %w", err) - } - encryptedFile, err := os.Open(filepath.Join(path, fmt.Sprintf("%08X.app", content.ID))) - 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]) { - return errors.New("h3 Hash mismatch") - } - - chunkCount := int(content.Size / 0x10000) - 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++ { - encryptedFile.Read(buffer) - cipher.NewCBCDecrypter(cipherHashTree, iv).CryptBlocks(decryptedContent, buffer) - - h0Hashes := decryptedContent[0:0x140] - h1Hashes := decryptedContent[0x140:0x280] - h2Hashes := decryptedContent[0x280:0x3c0] - - h1Hash := h1Hashes[(h1HashNum * 0x14):((h1HashNum + 1) * 0x14)] - h2Hash := h2Hashes[(h2HashNum * 0x14):((h2HashNum + 1) * 0x14)] - h3Hash := h3Data[(h3HashNum * 0x14):((h3HashNum + 1) * 0x14)] - - h0HashesHash := sha1.Sum(h0Hashes) - h1HashesHash := sha1.Sum(h1Hashes) - h2HashesHash := sha1.Sum(h2Hashes) - - if !reflect.DeepEqual(h0HashesHash[:], h1Hash) { - return errors.New("h0 Hashes Hash mismatch") - } - if !reflect.DeepEqual(h1HashesHash[:], h2Hash) { - return errors.New("h1 Hashes Hash mismatch") - } - if !reflect.DeepEqual(h2HashesHash[:], h3Hash) { - return errors.New("h2 Hashes Hash mismatch") - } - encryptedFile.Seek(0xFC00, 1) - h0HashNum++ - if h0HashNum >= 16 { - h0HashNum = 0 - h1HashNum++ - } - if h1HashNum >= 16 { - h1HashNum = 0 - h2HashNum++ - } - if h2HashNum >= 16 { - h2HashNum = 0 - h3HashNum++ - } - } - return nil -}