From e4d32e991066b356352adaad502665f6b78dbd21 Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 12 Aug 2020 00:52:05 +0200 Subject: [PATCH] wip --- .gitignore | 2 + .../de/mas/wiiu/jnus/DecryptionService.java | 56 ++-- .../de/mas/wiiu/jnus/ExtractionService.java | 2 +- src/main/java/de/mas/wiiu/jnus/NUSTitle.java | 23 +- .../java/de/mas/wiiu/jnus/NUSTitleLoader.java | 19 +- .../de/mas/wiiu/jnus/NUSTitleLoaderFST.java | 4 +- .../mas/wiiu/jnus/NUSTitleLoaderRemote.java | 3 +- src/main/java/de/mas/wiiu/jnus/WUDLoader.java | 67 +++-- .../java/de/mas/wiiu/jnus/WumadLoader.java | 3 +- .../entities/{content => TMD}/Content.java | 8 +- .../{content => TMD}/ContentInfo.java | 4 +- .../{TMD.java => TMD/TitleMetaData.java} | 18 +- .../jnus/entities/content/ContentFSTInfo.java | 122 -------- .../de/mas/wiiu/jnus/entities/fst/FST.java | 266 ++++++++++++------ .../mas/wiiu/jnus/entities/fst/FSTEntry.java | 200 ------------- .../wiiu/jnus/entities/fst/FSTService.java | 110 -------- .../wiiu/jnus/entities/fst/header/Header.java | 45 +++ .../fst/nodeentry/DirectoryEntry.java | 109 +++++++ .../entities/fst/nodeentry/EntryType.java | 71 +++++ .../entities/fst/nodeentry/FileEntry.java | 60 ++++ .../entities/fst/nodeentry/NodeEntries.java | 123 ++++++++ .../entities/fst/nodeentry/NodeEntry.java | 141 ++++++++++ .../entities/fst/nodeentry/Permission.java | 13 + .../entities/fst/nodeentry/RealFileEntry.java | 16 ++ .../entities/fst/nodeentry/RootEntry.java | 32 +++ .../fst/nodeentry/VirtualFileEntry.java | 30 ++ .../fst/sectionentry/SectionEntries.java | 31 ++ .../fst/sectionentry/SectionEntry.java | 50 ++++ .../entities/fst/stringtable/StringEntry.java | 15 + .../entities/fst/stringtable/StringTable.java | 92 ++++++ .../DefaultNUSDataProcessor.java | 24 +- .../FSTDataProviderNUSTitle.java | 49 ++-- .../FSTDataProviderWUDDataPartition.java | 47 ++-- .../FSTDataProviderWumadDataPartition.java | 20 +- .../implementations/NUSDataProviderFST.java | 31 +- .../implementations/NUSDataProviderLocal.java | 2 +- .../NUSDataProviderLocalBackup.java | 4 +- .../NUSDataProviderRemote.java | 20 +- .../implementations/NUSDataProviderWUD.java | 55 ++-- .../implementations/NUSDataProviderWoomy.java | 2 +- .../implementations/NUSDataProviderWumad.java | 26 +- .../wud/GamePartitionHeader.java | 103 ------- .../jnus/implementations/wud/WiiUDisc.java | 28 ++ .../wud/content/WiiUContentsInformation.java | 42 +++ .../wud/content/WiiUDiscContentsHeader.java | 64 +++++ .../content/partitions/WiiUDataPartition.java | 17 ++ .../content/partitions/WiiUGMPartition.java | 18 ++ .../wud/content/partitions/WiiUPartition.java | 89 ++++++ .../content/partitions/WiiUPartitions.java | 136 +++++++++ .../partitions/volumes/H3HashArray.java | 23 ++ .../partitions/volumes/H3HashArrayList.java | 33 +++ .../partitions/volumes/VolumeHeader.java | 132 +++++++++ .../wud/header/WiiUDiscHeader.java | 49 ++++ .../wud/header/WiiUDiscID.java | 59 ++++ .../wud/header/WiiUManufactorDiscID.java | 33 +++ .../wud/parser/WUDDataPartition.java | 30 -- .../wud/parser/WUDGamePartition.java | 46 --- .../implementations/wud/parser/WUDInfo.java | 40 --- .../wud/parser/WUDInfoParser.java | 233 --------------- .../wud/parser/WUDPartition.java | 25 -- .../wud/reader/WUDDiscReader.java | 3 +- .../wud/wumad/WumadDataPartition.java | 4 +- .../wud/wumad/WumadGamePartition.java | 13 +- .../wud/wumad/WumadParser.java | 27 +- .../jnus/interfaces/ContentDecryptor.java | 20 +- .../jnus/interfaces/ContentEncryptor.java | 3 +- .../wiiu/jnus/interfaces/FSTDataProvider.java | 35 +-- .../jnus/interfaces/NUSDataProcessor.java | 8 +- .../wiiu/jnus/interfaces/NUSDataProvider.java | 4 +- .../mas/wiiu/jnus/interfaces/TriFunction.java | 4 +- .../de/mas/wiiu/jnus/utils/ByteUtils.java | 2 +- .../wiiu/jnus/utils/DataProviderUtils.java | 2 +- .../java/de/mas/wiiu/jnus/utils/FSTUtils.java | 66 ++--- .../java/de/mas/wiiu/jnus/utils/Utils.java | 2 - .../jnus/utils/blocksize/AddressInBlocks.java | 13 + .../utils/blocksize/AddressInDiscBlocks.java | 11 + .../blocksize/AddressInVolumeBlocks.java | 11 + .../wiiu/jnus/utils/blocksize/BlockSize.java | 20 ++ .../jnus/utils/blocksize/DiscBlockSize.java | 9 + .../jnus/utils/blocksize/SectionAddress.java | 11 + .../utils/blocksize/SectionBlockSize.java | 9 + .../jnus/utils/blocksize/SizeInBlocks.java | 13 + .../utils/blocksize/SizeInVolumeBlocks.java | 9 + .../jnus/utils/blocksize/VolumeBlockSize.java | 7 + .../utils/cryptography/AESEncryption.java | 2 +- .../utils/cryptography/NUSDecryption.java | 4 +- .../utils/cryptography/NUSEncryption.java | 22 +- src/test/java/TestCases.java | 2 +- src/test/resources/out_dec/00000000.app.bak | Bin 32768 -> 0 bytes 89 files changed, 2244 insertions(+), 1307 deletions(-) rename src/main/java/de/mas/wiiu/jnus/entities/{content => TMD}/Content.java (97%) rename src/main/java/de/mas/wiiu/jnus/entities/{content => TMD}/ContentInfo.java (95%) rename src/main/java/de/mas/wiiu/jnus/entities/{TMD.java => TMD/TitleMetaData.java} (95%) delete mode 100644 src/main/java/de/mas/wiiu/jnus/entities/content/ContentFSTInfo.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/FSTEntry.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/header/Header.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/DirectoryEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/EntryType.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/FileEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntries.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/Permission.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RealFileEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RootEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/VirtualFileEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntries.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringEntry.java create mode 100644 src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringTable.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/GamePartitionHeader.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/WiiUDisc.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUContentsInformation.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUDiscContentsHeader.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUDataPartition.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUGMPartition.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartition.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartitions.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArray.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArrayList.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/VolumeHeader.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscHeader.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscID.java create mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUManufactorDiscID.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDDataPartition.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java delete mode 100644 src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartition.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInBlocks.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInDiscBlocks.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInVolumeBlocks.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/BlockSize.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/DiscBlockSize.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionAddress.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionBlockSize.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInBlocks.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInVolumeBlocks.java create mode 100644 src/main/java/de/mas/wiiu/jnus/utils/blocksize/VolumeBlockSize.java delete mode 100644 src/test/resources/out_dec/00000000.app.bak diff --git a/.gitignore b/.gitignore index 8a2376c..0453a4f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ /target/ .settings/ .classpath +test.1 +test.2 diff --git a/src/main/java/de/mas/wiiu/jnus/DecryptionService.java b/src/main/java/de/mas/wiiu/jnus/DecryptionService.java index 585ad09..aa768ce 100644 --- a/src/main/java/de/mas/wiiu/jnus/DecryptionService.java +++ b/src/main/java/de/mas/wiiu/jnus/DecryptionService.java @@ -25,7 +25,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; import de.mas.wiiu.jnus.utils.CheckSumWrongException; import de.mas.wiiu.jnus.utils.FSTUtils; @@ -49,7 +50,7 @@ public final class DecryptionService { this.dataProvider = dataProvider; } - public void decryptFSTEntryTo(boolean useFullPath, FSTEntry entry, String outputPath, boolean skipExistingFile) { + public void decryptFSTEntryTo(boolean useFullPath, NodeEntry entry, String outputPath, boolean skipExistingFile) { try { decryptFSTEntryToAsync(useFullPath, entry, outputPath, skipExistingFile).get(); } catch (InterruptedException | ExecutionException e) { @@ -57,26 +58,26 @@ public final class DecryptionService { } } - public CompletableFuture decryptFSTEntryToAsync(boolean useFullPath, FSTEntry entry, String outputPath, boolean skipExistingFile) { + public CompletableFuture decryptFSTEntryToAsync(boolean useFullPath, NodeEntry entry, String outputPath, boolean skipExistingFile) { return CompletableFuture.runAsync(() -> { try { - if (entry.isNotInPackage()) { + if (entry.isLink()) { return; } - log.info("Decrypting " + entry.getFilename()); + log.info("Decrypting " + entry.getName()); - String targetFilePath = new StringBuilder().append(outputPath).append("/").append(entry.getFilename()).toString(); + String targetFilePath = new StringBuilder().append(outputPath).append("/").append(entry.getName()).toString(); String fullPath = new StringBuilder().append(outputPath).toString(); if (useFullPath) { - targetFilePath = new StringBuilder().append(outputPath).append(entry.getFullPath()).toString(); fullPath = new StringBuilder().append(outputPath).append(entry.getPath()).toString(); - if (entry.isDir()) { // If the entry is a directory. Create it and return. + targetFilePath = new StringBuilder().append(outputPath).append(entry.getFullPath()).toString(); + if (entry.isDirectory()) { // If the entry is a directory. Create it and return. Utils.createDir(targetFilePath); return; } - } else if (entry.isDir()) { + } else if (entry.isDirectory()) { return; } @@ -87,16 +88,16 @@ public final class DecryptionService { if (skipExistingFile) { File targetFile = new File(targetFilePath); if (targetFile.exists()) { - if (entry.isDir()) { + if (entry.isDirectory()) { return; } - if (targetFile.length() == entry.getFileSize()) { + if (targetFile.length() == ((FileEntry) entry).getSize()) { - log.info("File already exists: " + entry.getFilename()); + log.info("File already exists: " + entry.getName()); return; } else { - log.info("File already exists but the filesize doesn't match: " + entry.getFilename()); + log.info("File already exists but the filesize doesn't match: " + entry.getName()); } } } @@ -104,25 +105,31 @@ public final class DecryptionService { File target = new File(targetFilePath); // to avoid having fragmented files. - FileUtils.FileAsOutputStreamWrapper(target, entry.getFileSize(), newOutputStream -> decryptFSTEntryToStream(entry, newOutputStream)); + FileUtils.FileAsOutputStreamWrapper(target, ((FileEntry) entry).getSize(), + newOutputStream -> decryptFSTEntryToStream((FileEntry) entry, newOutputStream)); } catch (Exception ex) { throw new CompletionException(ex); } }); } - public void decryptFSTEntryToStream(FSTEntry entry, OutputStream outputStream) throws IOException { - dataProvider.readFileToStream(outputStream, entry); + public void decryptFSTEntryToStream(NodeEntry entry, OutputStream outputStream) throws IOException { + if (!entry.isFile() || entry.isLink()) { + return; + } + dataProvider.readFileToStream(outputStream, (FileEntry) entry); } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Decrypt FSTEntry to OutputStream // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! public void decryptFSTEntryTo(String entryFullPath, OutputStream outputStream) throws IOException, CheckSumWrongException { - FSTEntry entry = FSTUtils.getFSTEntryByFullPath(dataProvider.getRoot(), entryFullPath) + NodeEntry entry = FSTUtils.getFSTEntryByFullPath(dataProvider.getRoot(), entryFullPath) .orElseThrow(() -> new FileNotFoundException("File not found: " + entryFullPath)); - decryptFSTEntryToStream(entry, outputStream); + if (entry.isFile()) { + decryptFSTEntryToStream(entry, outputStream); + } } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -132,10 +139,13 @@ public final class DecryptionService { public void decryptFSTEntryTo(boolean fullPath, String entryFullPath, String outputFolder, boolean skipExistingFiles) throws IOException, CheckSumWrongException { - FSTEntry entry = FSTUtils.getFSTEntryByFullPath(dataProvider.getRoot(), entryFullPath) + NodeEntry entry = FSTUtils.getFSTEntryByFullPath(dataProvider.getRoot(), entryFullPath) .orElseThrow(() -> new FileNotFoundException("File not found: " + entryFullPath)); - decryptFSTEntryTo(fullPath, entry, outputFolder, skipExistingFiles); + if (entry.isFile()) { + decryptFSTEntryTo(fullPath, entry, outputFolder, skipExistingFiles); + } + } // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -156,11 +166,11 @@ public final class DecryptionService { decryptFSTEntryListTo(fullPath, FSTUtils.getFSTEntriesByRegEx(dataProvider.getRoot(), regEx), outputFolder, skipExisting); } - public void decryptFSTEntryListTo(List list, String outputFolder, boolean skipExisting) throws IOException, CheckSumWrongException { + public void decryptFSTEntryListTo(List list, String outputFolder, boolean skipExisting) throws IOException, CheckSumWrongException { decryptFSTEntryListTo(true, list, outputFolder, skipExisting); } - public void decryptFSTEntryListTo(boolean fullPath, List list, String outputFolder, boolean skipExisting) + public void decryptFSTEntryListTo(boolean fullPath, List list, String outputFolder, boolean skipExisting) throws IOException, CheckSumWrongException { if (parallelizable && Settings.ALLOW_PARALLELISATION) { try { @@ -175,7 +185,7 @@ public final class DecryptionService { } } - public CompletableFuture decryptFSTEntryListToAsync(boolean fullPath, List list, String outputFolder, boolean skipExisting) + public CompletableFuture decryptFSTEntryListToAsync(boolean fullPath, List list, String outputFolder, boolean skipExisting) throws IOException, CheckSumWrongException { return CompletableFuture .allOf(list.stream().map(entry -> decryptFSTEntryToAsync(fullPath, entry, outputFolder, skipExisting)).toArray(CompletableFuture[]::new)); diff --git a/src/main/java/de/mas/wiiu/jnus/ExtractionService.java b/src/main/java/de/mas/wiiu/jnus/ExtractionService.java index 785f826..6734d2e 100644 --- a/src/main/java/de/mas/wiiu/jnus/ExtractionService.java +++ b/src/main/java/de/mas/wiiu/jnus/ExtractionService.java @@ -28,7 +28,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutionException; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.interfaces.Parallelizable; import de.mas.wiiu.jnus.utils.DataProviderUtils; diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitle.java b/src/main/java/de/mas/wiiu/jnus/NUSTitle.java index 2b23566..359fe02 100644 --- a/src/main/java/de/mas/wiiu/jnus/NUSTitle.java +++ b/src/main/java/de/mas/wiiu/jnus/NUSTitle.java @@ -22,10 +22,11 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -import de.mas.wiiu.jnus.entities.TMD; import de.mas.wiiu.jnus.entities.Ticket; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; +import de.mas.wiiu.jnus.entities.TMD.TitleMetaData; import de.mas.wiiu.jnus.interfaces.NUSDataProcessor; import de.mas.wiiu.jnus.utils.FSTUtils; import lombok.Getter; @@ -35,38 +36,38 @@ public class NUSTitle { @Getter @Setter private Optional FST = Optional.empty(); @Getter @Setter private Optional ticket; - @Getter private final TMD TMD; + @Getter private final TitleMetaData TMD; @Getter private final NUSDataProcessor dataProcessor; - private NUSTitle(TMD tmd, NUSDataProcessor dataProcessor) { + private NUSTitle(TitleMetaData tmd, NUSDataProcessor dataProcessor) { this.TMD = tmd; this.dataProcessor = dataProcessor; } - public static NUSTitle create(TMD tmd, NUSDataProcessor dataProcessor, Optional ticket, Optional fst) { + public static NUSTitle create(TitleMetaData tmd, NUSDataProcessor dataProcessor, Optional ticket, Optional fst) { NUSTitle result = new NUSTitle(tmd, dataProcessor); result.setTicket(ticket); result.setFST(fst); return result; } - public Stream getAllFSTEntriesAsStream() { + public Stream getAllFSTEntriesAsStream() { if (!FST.isPresent()) { return Stream.empty(); } - return FSTUtils.getAllFSTEntryChildrenAsStream(FST.get().getRoot()); + return FSTUtils.getAllFSTEntryChildrenAsStream(FST.get().getRootEntry()); } - public List getFSTEntriesByRegEx(String regEx) { + public List getFSTEntriesByRegEx(String regEx) { return getFSTEntriesByRegEx(regEx, true); } - public List getFSTEntriesByRegEx(String regEx, boolean onlyInPackage) { + public List getFSTEntriesByRegEx(String regEx, boolean onlyInPackage) { if (!FST.isPresent()) { return new ArrayList<>(); } - return FSTUtils.getFSTEntriesByRegEx(FST.get().getRoot(), regEx, onlyInPackage); + return FSTUtils.getFSTEntriesByRegEx(FST.get().getRootEntry(), regEx, onlyInPackage); } public void cleanup() throws IOException { diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java index defa7de..2065600 100644 --- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java +++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java @@ -23,11 +23,12 @@ import java.text.ParseException; import java.util.Optional; import java.util.function.Supplier; -import de.mas.wiiu.jnus.entities.TMD; import de.mas.wiiu.jnus.entities.Ticket; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; +import de.mas.wiiu.jnus.entities.TMD.Content; +import de.mas.wiiu.jnus.entities.TMD.TitleMetaData; import de.mas.wiiu.jnus.implementations.FSTDataProviderNUSTitle; import de.mas.wiiu.jnus.interfaces.ContentDecryptor; import de.mas.wiiu.jnus.interfaces.ContentEncryptor; @@ -48,7 +49,7 @@ public class NUSTitleLoader { throws IOException, ParseException { NUSDataProvider dataProvider = dataProviderFunction.get(); - TMD tmd = TMD.parseTMD(dataProvider.getRawTMD().orElseThrow(() -> new FileNotFoundException("No TMD data found"))); + TitleMetaData tmd = TitleMetaData.parseTMD(dataProvider.getRawTMD().orElseThrow(() -> new FileNotFoundException("No TMD data found"))); if (config.isNoDecryption()) { NUSTitle result = NUSTitle.create(tmd, dataProcessorFunction.apply(dataProvider, Optional.empty(), Optional.empty()), Optional.empty(), @@ -89,8 +90,10 @@ public class NUSTitleLoader { NUSTitle result = NUSTitle.create(tmd, dpp, ticket, Optional.empty()); FSTDataProvider dp = new FSTDataProviderNUSTitle(result); - for (FSTEntry children : dp.getRoot().getChildren()) { - dp.readFile(children); + for (NodeEntry child : dp.getRoot().getFileChildren()) { + if (!child.isLink()) { + dp.readFile((FileEntry) child); + } } return result; @@ -99,7 +102,7 @@ public class NUSTitleLoader { Content fstContent = tmd.getContentByIndex(0); byte[] fstBytes = dpp.readPlainDecryptedContent(fstContent, true); - FST fst = FST.parseFST(fstBytes); + FST fst = FST.parseData(fstBytes); // The dataprovider may need the FST to calculate the offset of a content // on the partition. diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java index 4da0616..8f1520c 100644 --- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java +++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java @@ -19,7 +19,7 @@ package de.mas.wiiu.jnus; import java.io.IOException; import java.text.ParseException; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor; import de.mas.wiiu.jnus.implementations.NUSDataProviderFST; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; @@ -33,7 +33,7 @@ public final class NUSTitleLoaderFST { return loadNUSTitle(dataProvider, dataProvider.getRoot(), commonKey); } - public static NUSTitle loadNUSTitle(FSTDataProvider dataProvider, FSTEntry base, byte[] commonKey) throws IOException, ParseException { + public static NUSTitle loadNUSTitle(FSTDataProvider dataProvider, DirectoryEntry base, byte[] commonKey) throws IOException, ParseException { NUSTitleConfig config = new NUSTitleConfig(); config.setCommonKey(commonKey); diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java index 0c3e03f..b88e5c3 100644 --- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java +++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java @@ -22,6 +22,7 @@ import java.text.ParseException; import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor; import de.mas.wiiu.jnus.implementations.NUSDataProviderRemote; +import de.mas.wiiu.jnus.utils.download.NUSDownloadService; public final class NUSTitleLoaderRemote { @@ -54,7 +55,7 @@ public final class NUSTitleLoaderRemote { throw new IOException("Ticket was null and no commonKey was given"); } - return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderRemote(version, titleID), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd)); + return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderRemote(version, titleID, NUSDownloadService.getDefaultInstance()), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd)); } } diff --git a/src/main/java/de/mas/wiiu/jnus/WUDLoader.java b/src/main/java/de/mas/wiiu/jnus/WUDLoader.java index 036d66a..db69ec2 100644 --- a/src/main/java/de/mas/wiiu/jnus/WUDLoader.java +++ b/src/main/java/de/mas/wiiu/jnus/WUDLoader.java @@ -23,19 +23,23 @@ import java.nio.file.Files; import java.text.ParseException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor; import de.mas.wiiu.jnus.implementations.FSTDataProviderNUSTitle; import de.mas.wiiu.jnus.implementations.FSTDataProviderWUDDataPartition; import de.mas.wiiu.jnus.implementations.NUSDataProviderWUD; import de.mas.wiiu.jnus.implementations.wud.WUDImage; -import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition; -import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo; -import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfoParser; +import de.mas.wiiu.jnus.implementations.wud.WiiUDisc; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.WiiUDataPartition; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.WiiUGMPartition; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; +import de.mas.wiiu.jnus.utils.Utils; import lombok.NonNull; import lombok.val; +import lombok.var; public final class WUDLoader { @@ -43,77 +47,88 @@ public final class WUDLoader { super(); } - public static WUDInfo load(String WUDPath) throws IOException, ParseException { + public static WiiUDisc load(String WUDPath) throws IOException, ParseException { return load(WUDPath, (byte[]) null); } - public static WUDInfo load(String WUDPath, File key) throws IOException, ParseException { + public static WiiUDisc load(String WUDPath, File key) throws IOException, ParseException { byte[] data = Files.readAllBytes(key.toPath()); return load(WUDPath, data); } - public static WUDInfo loadDev(String WUDPath) throws IOException, ParseException { + public static WiiUDisc loadDev(String WUDPath) throws IOException, ParseException { return load(WUDPath, null, true); } - public static WUDInfo load(String WUDPath, byte[] titleKey) throws IOException, ParseException { + public static WiiUDisc load(String WUDPath, byte[] titleKey) throws IOException, ParseException { return load(WUDPath, titleKey, false); } - public static WUDInfo load(String WUDPath, byte[] titleKey, boolean forceNoKey) throws IOException, ParseException { - byte[] usedTitleKey = titleKey; + public static WiiUDisc load(String WUDPath, byte[] titleKey, boolean forceNoKey) throws IOException, ParseException { + Optional usedTitleKey = Optional.empty(); + if (titleKey != null) { + usedTitleKey = Optional.of(titleKey); + } File wudFile = new File(WUDPath); if (!wudFile.exists()) { throw new FileNotFoundException(wudFile.getAbsolutePath() + " was not found"); } WUDImage image = new WUDImage(wudFile); - if (usedTitleKey == null && !forceNoKey) { + if (titleKey == null && !forceNoKey) { File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME); if (!keyFile.exists()) { throw new FileNotFoundException(keyFile.getAbsolutePath() + " does not exist and no title key was provided."); } - usedTitleKey = Files.readAllBytes(keyFile.toPath()); + usedTitleKey = Optional.of(Files.readAllBytes(keyFile.toPath())); } - WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), usedTitleKey); + WiiUDisc wiiUDisc = WiiUDisc.parseData(image.getWUDDiscReader(), usedTitleKey); - return wudInfo; + return wiiUDisc; } - public static List getGamePartionsAsNUSTitles(@NonNull WUDInfo wudInfo, byte[] commonKey) throws IOException, ParseException { + public static List getGamePartionsAsNUSTitles(@NonNull WiiUDisc disc, byte[] commonKey) throws IOException, ParseException { List result = new ArrayList<>(); - for (val gamePartition : wudInfo.getGamePartitions()) { - result.add(convertGamePartitionToNUSTitle(gamePartition, wudInfo.getWUDDiscReader(), commonKey)); + List gamePartitions = disc.getHeader().getContentsInformation().getPartitions().stream().filter(p -> p instanceof WiiUGMPartition) + .map(p -> (WiiUGMPartition) p).collect(Collectors.toList()); + + for (val gamePartition : gamePartitions) { + result.add(convertGamePartitionToNUSTitle(gamePartition, disc.getReader().get(), commonKey)); } return result; } - public static NUSTitle convertGamePartitionToNUSTitle(WUDGamePartition gamePartition, WUDDiscReader discReader, byte[] commonKey) + public static NUSTitle convertGamePartitionToNUSTitle(WiiUGMPartition gamePartition, WUDDiscReader discReader, byte[] commonKey) throws IOException, ParseException { final NUSTitleConfig config = new NUSTitleConfig(); config.setCommonKey(commonKey); - gamePartition.getTmd(); - return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWUD(gamePartition, discReader), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd)); + return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWUD(gamePartition, discReader), + (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd)); } - public static List getPartitonsAsFSTDataProvider(@NonNull WUDInfo wudInfo, byte[] commonKey) throws IOException, ParseException { + public static List getPartitonsAsFSTDataProvider(@NonNull WiiUDisc disc, byte[] commonKey) throws IOException, ParseException { List result = new ArrayList<>(); - for (val gamePartition : wudInfo.getGamePartitions()) { - NUSTitle t = convertGamePartitionToNUSTitle(gamePartition, wudInfo.getWUDDiscReader(), commonKey); + List gamePartitions = disc.getHeader().getContentsInformation().getPartitions().stream().filter(p -> p instanceof WiiUGMPartition) + .map(p -> (WiiUGMPartition) p).collect(Collectors.toList()); + + for (val gamePartition : gamePartitions) { + NUSTitle t = convertGamePartitionToNUSTitle(gamePartition, disc.getReader().get(), commonKey); FSTDataProviderNUSTitle res = new FSTDataProviderNUSTitle(t); - res.setName(gamePartition.getPartitionName()); + res.setName(gamePartition.getVolumeID()); result.add(res); } - for (val partition : wudInfo.getDataPartitions()) { - result.add(new FSTDataProviderWUDDataPartition(partition, wudInfo.getWUDDiscReader(), wudInfo.getTitleKey())); + List dataParitions = disc.getHeader().getContentsInformation().getPartitions().stream().filter(p -> p instanceof WiiUDataPartition) + .map(p -> ((WiiUDataPartition) p)).collect(Collectors.toList()); + + for (val partition : dataParitions) { + result.add(new FSTDataProviderWUDDataPartition(partition, disc.getReader().get(), disc.getDiscKey())); } return result; - } } diff --git a/src/main/java/de/mas/wiiu/jnus/WumadLoader.java b/src/main/java/de/mas/wiiu/jnus/WumadLoader.java index d65bc27..9b11ef8 100644 --- a/src/main/java/de/mas/wiiu/jnus/WumadLoader.java +++ b/src/main/java/de/mas/wiiu/jnus/WumadLoader.java @@ -41,7 +41,8 @@ public class WumadLoader { final NUSTitleConfig config = new NUSTitleConfig(); config.setCommonKey(commonKey); gamePartition.getTmd(); - return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWumad(gamePartition, wudmadFile), (dp, cd, ce) -> new DefaultNUSDataProcessor(dp, cd)); + return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWumad(gamePartition, wudmadFile), + (dp, cd, ce) -> new DefaultNUSDataProcessor(dp, cd)); } public static List getPartitonsAsFSTDataProvider(@NonNull WumadInfo wumadInfo, byte[] commonKey) throws IOException, ParseException { diff --git a/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java b/src/main/java/de/mas/wiiu/jnus/entities/TMD/Content.java similarity index 97% rename from src/main/java/de/mas/wiiu/jnus/entities/content/Content.java rename to src/main/java/de/mas/wiiu/jnus/entities/TMD/Content.java index 9fdc5fb..dd7031a 100644 --- a/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java +++ b/src/main/java/de/mas/wiiu/jnus/entities/TMD/Content.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ -package de.mas.wiiu.jnus.entities.content; +package de.mas.wiiu.jnus.entities.TMD; import java.nio.ByteBuffer; import java.text.ParseException; @@ -37,7 +37,7 @@ public class Content implements Comparable { public static final short CONTENT_FLAG_UNKWN1 = 0x4000; public static final short CONTENT_HASHED = 0x0002; public static final short CONTENT_ENCRYPTED = 0x0001; - + public static final int CONTENT_SIZE = 0x30; @Getter private final int ID; @@ -63,7 +63,7 @@ public class Content implements Comparable { * @return content object * @throws ParseException */ - public static Content parseContent(byte[] input) throws ParseException { + public static Content parseData(byte[] input) throws ParseException { if (input == null || input.length != CONTENT_SIZE) { log.info("Error: invalid Content byte[] input"); throw new ParseException("Error: invalid Content byte[] input", 0); @@ -180,8 +180,6 @@ public class Content implements Comparable { private long encryptedFileSize; private byte[] SHA2Hash; - - private ContentFSTInfo contentFSTInfo; } @Override diff --git a/src/main/java/de/mas/wiiu/jnus/entities/content/ContentInfo.java b/src/main/java/de/mas/wiiu/jnus/entities/TMD/ContentInfo.java similarity index 95% rename from src/main/java/de/mas/wiiu/jnus/entities/content/ContentInfo.java rename to src/main/java/de/mas/wiiu/jnus/entities/TMD/ContentInfo.java index 2b455c2..41771e9 100644 --- a/src/main/java/de/mas/wiiu/jnus/entities/content/ContentInfo.java +++ b/src/main/java/de/mas/wiiu/jnus/entities/TMD/ContentInfo.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ -package de.mas.wiiu.jnus.entities.content; +package de.mas.wiiu.jnus.entities.TMD; import java.nio.ByteBuffer; import java.text.ParseException; @@ -69,7 +69,7 @@ public class ContentInfo { * @return ContentFSTInfo object * @throws ParseException */ - public static ContentInfo parseContentInfo(byte[] input) throws ParseException { + public static ContentInfo parseData(byte[] input) throws ParseException { if (input == null || input.length != CONTENT_INFO_SIZE) { log.info("Error: invalid ContentInfo byte[] input"); throw new ParseException("Error: invalid ContentInfo byte[] input", 0); diff --git a/src/main/java/de/mas/wiiu/jnus/entities/TMD.java b/src/main/java/de/mas/wiiu/jnus/entities/TMD/TitleMetaData.java similarity index 95% rename from src/main/java/de/mas/wiiu/jnus/entities/TMD.java rename to src/main/java/de/mas/wiiu/jnus/entities/TMD/TitleMetaData.java index 9b13fe2..869a3d1 100644 --- a/src/main/java/de/mas/wiiu/jnus/entities/TMD.java +++ b/src/main/java/de/mas/wiiu/jnus/entities/TMD/TitleMetaData.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ -package de.mas.wiiu.jnus.entities; +package de.mas.wiiu.jnus.entities.TMD; import java.io.File; import java.io.IOException; @@ -27,14 +27,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.content.ContentInfo; import lombok.Data; import lombok.Getter; import lombok.extern.java.Log; @Log -public final class TMD { +public final class TitleMetaData { private static final int SIGNATURE_LENGTH = 0x100; private static final int ISSUER_LENGTH = 0x40; private static final int RESERVED_LENGTH = 0x3E; @@ -78,7 +76,7 @@ public final class TMD { private final Map contentToIndex = new HashMap<>(); private final Map contentToID = new HashMap<>(); - private TMD(TMDParam param) { + private TitleMetaData(TMDParam param) { super(); this.signatureType = param.getSignatureType(); this.signature = param.getSignature(); @@ -101,7 +99,7 @@ public final class TMD { this.cert2 = param.getCert2(); } - public static TMD parseTMD(File tmd) throws IOException, ParseException { + public static TitleMetaData parseTMD(File tmd) throws IOException, ParseException { if (tmd == null || !tmd.exists()) { log.info("TMD input file null or doesn't exist."); throw new IOException("TMD input file null or doesn't exist."); @@ -109,7 +107,7 @@ public final class TMD { return parseTMD(Files.readAllBytes(tmd.toPath())); } - public static TMD parseTMD(byte[] input) throws ParseException { + public static TitleMetaData parseTMD(byte[] input) throws ParseException { if (input == null || input.length == 0) { throw new ParseException("Invalid TMD file.", 0); } @@ -171,7 +169,7 @@ public final class TMD { for (int i = 0; i < CONTENT_INFO_ARRAY_SIZE; i++) { byte[] contentInfo = new byte[ContentInfo.CONTENT_INFO_SIZE]; buffer.get(contentInfo, 0, ContentInfo.CONTENT_INFO_SIZE); - contentInfos[i] = ContentInfo.parseContentInfo(contentInfo); + contentInfos[i] = ContentInfo.parseData(contentInfo); } List contentList = new ArrayList<>(); @@ -180,7 +178,7 @@ public final class TMD { buffer.position(CONTENT_OFFSET + (Content.CONTENT_SIZE * i)); byte[] content = new byte[Content.CONTENT_SIZE]; buffer.get(content, 0, Content.CONTENT_SIZE); - Content c = Content.parseContent(content); + Content c = Content.parseData(content); contentList.add(c); } @@ -213,7 +211,7 @@ public final class TMD { param.setCert1(cert1); param.setCert2(cert2); - TMD result = new TMD(param); + TitleMetaData result = new TitleMetaData(param); for (Content c : contentList) { result.setContentToIndex(c.getIndex(), c); diff --git a/src/main/java/de/mas/wiiu/jnus/entities/content/ContentFSTInfo.java b/src/main/java/de/mas/wiiu/jnus/entities/content/ContentFSTInfo.java deleted file mode 100644 index a80de8f..0000000 --- a/src/main/java/de/mas/wiiu/jnus/entities/content/ContentFSTInfo.java +++ /dev/null @@ -1,122 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.entities.content; - -import java.nio.ByteBuffer; -import java.text.ParseException; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.extern.java.Log; - -@EqualsAndHashCode -/** - * Representation on an Object of the first section of an FST. - * - * @author Maschell - * - */ -@Log -public final class ContentFSTInfo { - @Getter private final long offsetSector; - @Getter private final long sizeSector; - @Getter private final long ownerTitleID; - @Getter private final int groupID; - @Getter private final byte unkown; - - private static int SECTOR_SIZE = 0x8000; - - private ContentFSTInfo(ContentFSTInfoParam param) { - this.offsetSector = param.getOffsetSector(); - this.sizeSector = param.getSizeSector(); - this.ownerTitleID = param.getOwnerTitleID(); - this.groupID = param.getGroupID(); - this.unkown = param.getUnkown(); - } - - /** - * Creates a new ContentFSTInfo object given be the raw byte data - * - * @param input - * 0x20 byte of data from the FST (starting at 0x20) - * @return ContentFSTInfo object - * @throws ParseException - */ - public static ContentFSTInfo parseContentFST(byte[] input) throws ParseException { - if (input == null || input.length != 0x20) { - log.info("Error: invalid ContentFSTInfo byte[] input"); - throw new ParseException("Failed to parse ContentFSTInfo", 0); - } - ContentFSTInfoParam param = new ContentFSTInfoParam(); - ByteBuffer buffer = ByteBuffer.allocate(input.length); - buffer.put(input); - - buffer.position(0); - int offset = buffer.getInt(); - int size = buffer.getInt(); - long ownerTitleID = buffer.getLong(); - int groupID = buffer.getInt(); - byte unkown = buffer.get(); - - param.setOffsetSector(offset); - param.setSizeSector(size); - param.setOwnerTitleID(ownerTitleID); - param.setGroupID(groupID); - param.setUnkown(unkown); - - return new ContentFSTInfo(param); - } - - /** - * Returns the offset of of the Content in the partition - * - * @return offset of the content in the partition in bytes - */ - public long getOffset() { - long result = (getOffsetSector() * SECTOR_SIZE) - SECTOR_SIZE; - if (result < 0) { - return 0; - } - return result; - } - - /** - * Returns the size in bytes, not in sectors - * - * @return size in bytes - */ - public int getSize() { - return (int) (getSizeSector() * SECTOR_SIZE); - } - - @Override - public String toString() { - return "ContentFSTInfo [offset=" + String.format("%08X", offsetSector) + ", size=" + String.format("%08X", sizeSector) + ", ownerTitleID=" - + String.format("%016X", ownerTitleID) + ", groupID=" + String.format("%08X", groupID) + ", unkown=" + unkown + "]"; - } - - @Data - private static class ContentFSTInfoParam { - private long offsetSector; - private long sizeSector; - private long ownerTitleID; - private int groupID; - private byte unkown; - } - -} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java index 2f1d8c0..dedafb4 100644 --- a/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java @@ -1,98 +1,206 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.entities.fst; +package de.mas.wiiu.jnus.entities.FST; -import java.text.ParseException; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; -import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; -import de.mas.wiiu.jnus.utils.ByteUtils; -import lombok.Getter; +import de.mas.wiiu.jnus.entities.FST.header.Header; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType; +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntries; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.Permission; +import de.mas.wiiu.jnus.entities.FST.nodeentry.RootEntry; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.Utils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.SectionBlockSize; +import de.mas.wiiu.jnus.utils.blocksize.SizeInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.VolumeBlockSize; +import lombok.Data; +import lombok.val; -/** - * Represents the FST - * - * @author Maschell - * - */ -public final class FST { - @Getter private final FSTEntry root = FSTEntry.getRootFSTEntry(); +@Data +public class FST { + private Header header; + private SectionEntries sectionEntries; + private StringTable stringTable; + private NodeEntries nodeEntries; - @Getter private final int sectorSize; - @Getter private final int contentCount; - - @Getter private final Map contentFSTInfos = new HashMap<>(); - - private FST(int unknown, int contentCount) { - this.sectorSize = unknown; - this.contentCount = contentCount; + public static FST parseData(byte[] data) throws IOException { + return parseData(data, 0, new VolumeBlockSize(1)); } - /** - * Creates a FST by the given raw byte data - * - * @param fstData - * raw decrypted FST data - * @return - * @throws ParseException - */ - public static FST parseFST(byte[] fstData) throws ParseException { - if (!Arrays.equals(Arrays.copyOfRange(fstData, 0, 3), new byte[] { 0x46, 0x53, 0x54 })) { - throw new ParseException("Failed to parse FST", 0); + public static FST parseData(byte[] data, int offset, VolumeBlockSize blockSize) throws IOException { + FST fst = new FST(); + + int curOffset = offset; + + fst.header = Header.parseData(Arrays.copyOfRange(data, curOffset, Header.LENGTH), 0); + curOffset += Header.LENGTH; + fst.sectionEntries = SectionEntries.parseData(data, curOffset, fst.header.getNumberOfSections(), blockSize); + curOffset += fst.sectionEntries.getSizeInBytes(); + int lastEntryNumber = RootEntry.parseLastEntryNumber(data, curOffset); + + fst.stringTable = StringTable.parseData(data, curOffset + (lastEntryNumber * 16), lastEntryNumber); + fst.nodeEntries = NodeEntries.parseData(data, curOffset, fst.sectionEntries, fst.stringTable, fst.header.getBlockSize()); + + return fst; + } + + public RootEntry getRootEntry() { + return getNodeEntries().getRootEntry(); + } + + public byte[] getAsBytes() { + byte[] headerData = header.getAsBytes(); + byte[] sectionData = sectionEntries.getAsBytes(); + byte[] stringTableData = stringTable.getAsBytes(); + byte[] nodeEntriesData = nodeEntries.getAsBytes(); + + ByteBuffer buffer = ByteBuffer.allocate(headerData.length + sectionData.length + stringTableData.length + nodeEntriesData.length); + buffer.put(headerData); + buffer.put(sectionData); + buffer.put(nodeEntriesData); + buffer.put(stringTableData); + return buffer.array(); + } + + public static FST createFromFolder(File folder) { + Header header = new Header(); + header.setFSTVersion((short) 0); + header.setHashDisabled((short) 1); + header.setBlockSize(new SectionBlockSize(32)); + + VolumeBlockSize blockSize = new VolumeBlockSize(32768); + + StringTable stringtable = new StringTable(getStringsFromFolder(folder)); + + SectionEntry fstSection = new SectionEntry(0, "FST"); + fstSection.setAddress(new AddressInVolumeBlocks(blockSize, 0L)); + // for some reason this is always 0. We still need to adjust the offset of the next sections properly though. + fstSection.setSize(new SizeInVolumeBlocks(blockSize, 0L)); + fstSection.setHashMode((short) 0); + + SectionEntry codeSection = new SectionEntry(1, "CODE"); + // Add real FST Size. + codeSection.setAddress(new AddressInVolumeBlocks(blockSize, 0L)); + codeSection.setHashMode((short) 1); + + SectionEntry filesSection = new SectionEntry(2, "FILES"); + // Add real FST Size. + filesSection.setAddress(new AddressInVolumeBlocks(blockSize, 0L)); + filesSection.setHashMode((short) 2); + + SectionEntries sections = new SectionEntries(); + + sections.add(fstSection); + sections.add(codeSection); + sections.add(filesSection); + + DirectoryEntry dir = new DirectoryEntry(); + dir.setEntryType(new EntryType(EEntryType.Directory)); + dir.setParent(null); + dir.setEntryNumber(0); + dir.setNameString(stringtable.getEntry("").get()); + dir.setPermission(new Permission(0)); + dir.setSectionEntry(fstSection); + + RootEntry root = new RootEntry(dir); + + // Split files per folder. the /code folder seems to have an own section? + File[] codeFolder = folder.listFiles(f -> f.isDirectory() && "code".contentEquals(f.getName())); + if (codeFolder.length > 0) { + NodeEntry res = NodeEntries.createFromFolder(codeFolder[0], header.getBlockSize(), stringtable, codeSection, new Permission(0)); + root.addChild(res); + // res.printToStringRecursive(); + codeSection.setSize(calculateSectionSizeByFSTEntries((int) blockSize.getBlockSize(), res)); } - int sectorSize = ByteUtils.getIntFromBytes(fstData, 0x04); - int contentCount = ByteUtils.getIntFromBytes(fstData, 0x08); + File[] otherFolder = folder.listFiles(f -> f.isDirectory() && !"code".contentEquals(f.getName())); + for (val curFolder : otherFolder) { + NodeEntry res = NodeEntries.createFromFolder(curFolder, header.getBlockSize(), stringtable, filesSection, new Permission(0)); + root.addChild(res); + // res.printToStringRecursive(); + filesSection.setSize(calculateSectionSizeByFSTEntries((int) blockSize.getBlockSize(), res)); + } - FST result = new FST(sectorSize, contentCount); - - int contentfst_offset = 0x20; - int contentfst_size = 0x20 * contentCount; + setEntryNumbers(root, 0); - int fst_offset = contentfst_offset + contentfst_size; + // root.printToStringRecursive(); - int fileCount = ByteUtils.getIntFromBytes(fstData, fst_offset + 0x08); - int fst_size = fileCount * 0x10; + NodeEntries nodeEntries = new NodeEntries(root); - int nameOff = fst_offset + fst_size; - int nameSize = fstData.length - nameOff; - - // Get list with null-terminated Strings. Ends with \0\0. - for (int i = nameOff; i < fstData.length - 1; i++) { - if (fstData[i] == 0 && fstData[i + 1] == 0) { - nameSize = i - nameOff; + header.setNumberOfSections(sections.size()); + + FST fst = new FST(); + fst.setHeader(header); + fst.setSectionEntries(sections); + fst.setStringTable(stringtable); + fst.setNodeEntries(nodeEntries); + + // Fix FST section length + // int length = fst.getAsBytes().length; + // fstSection.setSize(new VolumeLbaSize(blockSize, (length / blockSize.getBlockSize())+1)); + + long sectionAddressInBlocks = 2; + for (SectionEntry s : sections) { + if (s.getSectionNumber() == 0) { + continue; + } + s.setAddress(new AddressInVolumeBlocks(blockSize, sectionAddressInBlocks)); + sectionAddressInBlocks += s.getSize().getValue(); + } + + return fst; + } + + private static SizeInVolumeBlocks calculateSectionSizeByFSTEntries(int blockSize, NodeEntry input) { + // calculate size of section. + Optional lastEntry = input.stream().filter(e -> e instanceof FileEntry).map(f -> (FileEntry) f) + .max((a, b) -> Long.compare(a.getOffset(), b.getOffset())); + if (!lastEntry.isPresent()) { + throw new IllegalArgumentException("WTF?"); + } + long sectionSize = lastEntry.get().getOffset() + lastEntry.get().getSize(); + sectionSize = Utils.align(sectionSize, blockSize); + int sectionSizeInBlocks = (int) (sectionSize / blockSize); + sectionSizeInBlocks = sectionSize % blockSize == 0 ? sectionSizeInBlocks : sectionSizeInBlocks + 1; + return new SizeInVolumeBlocks(new VolumeBlockSize(blockSize), (long) sectionSizeInBlocks); + } + + public static int setEntryNumbers(NodeEntry entry, int curEntryNumber) { + entry.setEntryNumber(curEntryNumber); + curEntryNumber++; + if (entry instanceof DirectoryEntry) { + for (NodeEntry child : ((DirectoryEntry) entry).getChildren()) { + curEntryNumber = setEntryNumbers(child, curEntryNumber); + } + ((DirectoryEntry) entry).setLastEntryNumber(curEntryNumber); + ((DirectoryEntry) entry).setParentEntryNumber(entry.getEntryNumber()); + } + return curEntryNumber; + + } + + public static List getStringsFromFolder(File input) { + List result = new LinkedList<>(); + + for (File f : input.listFiles()) { + result.add(f.getName()); + if (f.isDirectory()) { + result.addAll(getStringsFromFolder(f)); } } - Map contentFSTInfos = result.getContentFSTInfos(); - for (int i = 0; i < contentCount; i++) { - byte contentFST[] = Arrays.copyOfRange(fstData, contentfst_offset + (i * 0x20), contentfst_offset + ((i + 1) * 0x20)); - contentFSTInfos.put(i, ContentFSTInfo.parseContentFST(contentFST)); - } - - byte fstSection[] = Arrays.copyOfRange(fstData, fst_offset, fst_offset + fst_size); - byte nameSection[] = Arrays.copyOfRange(fstData, nameOff, nameOff + nameSize); - - FSTEntry root = result.getRoot(); - - FSTService.parseFST(root, fstSection, nameSection, sectorSize); - return result; } } diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTEntry.java deleted file mode 100644 index da356e0..0000000 --- a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTEntry.java +++ /dev/null @@ -1,200 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.entities.fst; - -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; - -import de.mas.wiiu.jnus.entities.content.Content; -import lombok.Data; -import lombok.Getter; -import lombok.NonNull; - -/** - * Represents one FST Entry - * - * @author Maschell - * - */ -public class FSTEntry { - public static final byte FSTEntry_DIR = (byte) 0x01; - public static final byte FSTEntry_notInNUS = (byte) 0x80; - - private String filename = null; - private final Supplier filenameSupplier; - - @Getter private final Optional parent; - - @Getter private final List children = new ArrayList<>(); - - @Getter private final short flags; - - @Getter private final long fileSize; - @Getter private final long fileOffset; - - @Getter private final boolean isDir; - @Getter private final boolean isRoot; - @Getter private final boolean isNotInPackage; - - @Getter private final short contentIndex; - - protected FSTEntry(FSTEntryParam fstParam) { - this.filenameSupplier = fstParam.getFileNameSupplier(); - this.flags = fstParam.getFlags(); - this.parent = fstParam.getParent(); - - this.fileSize = fstParam.getFileSize(); - this.fileOffset = fstParam.getFileOffset(); - - this.isDir = fstParam.isDir(); - this.isRoot = fstParam.isRoot(); - this.isNotInPackage = fstParam.isNotInPackage(); - this.contentIndex = fstParam.getContentIndex(); - } - - /** - * Creates and returns a new FST Entry - * - * @return - */ - public static FSTEntry getRootFSTEntry() { - FSTEntryParam param = new FSTEntryParam(); - param.setRoot(true); - param.setDir(true); - return new FSTEntry(param); - } - - public static FSTEntry createFSTEntry(@NonNull FSTEntry parent, @NonNull String filename, @NonNull Content content) { - FSTEntryParam param = new FSTEntryParam(); - param.setFileNameSupplier(() -> filename); - param.setFileSize(content.getDecryptedFileSize()); - param.setDir(false); - param.setParent(Optional.of(parent)); - return new FSTEntry(param); - } - - public String getFilename() { - if (filename == null) { - filename = filenameSupplier.get(); - } - return filename; - } - - public String getFullPath() { - return getPath() + getFilename(); - } - - private StringBuilder getPathInternal() { - if (parent.isPresent()) { - FSTEntry par = parent.get(); - return par.getPathInternal().append(par.getFilename()).append('/'); - } - return new StringBuilder(); - } - - public String getPath() { - return getPathInternal().toString(); - } - - public int getEntryCount() { - int count = 1; - for (FSTEntry entry : getChildren()) { - count += entry.getEntryCount(); - } - return count; - } - - public List getDirChildren() { - return getDirChildren(false); - } - - public List getDirChildren(boolean all) { - List result = new ArrayList<>(); - for (FSTEntry child : getChildren()) { - if (child.isDir() && (all || !child.isNotInPackage())) { - result.add(child); - } - } - return result; - } - - public List getFileChildren() { - return getFileChildren(false); - } - - public List getFileChildren(boolean all) { - List result = new ArrayList<>(); - for (FSTEntry child : getChildren()) { - if ((all && !child.isDir() || !child.isDir())) { - result.add(child); - } - } - return result; - } - - public void printRecursive(int space) { - printRecursive(System.out, space); - } - - public void printRecursive(PrintStream out, int space) { - for (int i = 0; i < space; i++) { - out.print(" "); - } - out.print(getFilename()); - if (isNotInPackage()) { - out.print(" (not in package)"); - } - out.println(); - for (FSTEntry child : getDirChildren(true)) { - child.printRecursive(space + 5); - } - for (FSTEntry child : getFileChildren(true)) { - child.printRecursive(space + 5); - } - } - - @Override - public String toString() { - return "FSTEntry [filename=" + getFilename() + ", path=" + getPath() + ", flags=" + flags + ", filesize=" + fileSize + ", fileoffset=" + fileOffset - + ", isDir=" + isDir + ", isRoot=" + isRoot + ", notInPackage=" + isNotInPackage + "]"; - } - - @Data - protected static class FSTEntryParam { - private Supplier fileNameSupplier = () -> ""; - private Optional parent = Optional.empty(); - - private short flags; - - private long fileSize = 0; - private long fileOffset = 0; - - private boolean isDir = false; - private boolean isRoot = false; - private boolean notInPackage = false; - - private short contentIndex = 0; - } - - public void addChildren(FSTEntry entry) { - this.getChildren().add(entry); - } - -} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java deleted file mode 100644 index 52de2f1..0000000 --- a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.entities.fst; - -import java.text.ParseException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import de.mas.wiiu.jnus.entities.fst.FSTEntry.FSTEntryParam; -import de.mas.wiiu.jnus.utils.ByteUtils; - -public final class FSTService { - - private FSTService() { - } - - public static void parseFST(FSTEntry rootEntry, byte[] fstSection, byte[] namesSection, int sectorSize) throws ParseException { - int totalEntries = ByteUtils.getIntFromBytes(fstSection, 0x08); - - final Map data = new HashMap<>(); - - for (int i = 1; i < totalEntries; i++) { - data.put(i, Arrays.copyOfRange(fstSection, i * 0x10, (i + 1) * 0x10)); - } - - parseData(1, totalEntries, rootEntry, data, namesSection, sectorSize); - - } - - private static int parseData(int i, int end, FSTEntry parent, Map data, byte[] namesSection, int sectorSize) throws ParseException { - while (i < end) { - byte[] curEntry = data.get(i); - - FSTEntryParam entryParam = new FSTEntry.FSTEntryParam(); - - entryParam.setParent(Optional.of(parent)); - - long fileOffset = ByteUtils.getIntFromBytes(curEntry, 0x04); - long fileSize = ByteUtils.getUnsingedIntFromBytes(curEntry, 0x08); - short flags = ByteUtils.getShortFromBytes(curEntry, 0x0C); - short contentIndex = ByteUtils.getShortFromBytes(curEntry, 0x0E); - - if ((curEntry[0] & FSTEntry.FSTEntry_notInNUS) == FSTEntry.FSTEntry_notInNUS) { - entryParam.setNotInPackage(true); - } - if ((curEntry[0] & FSTEntry.FSTEntry_DIR) == FSTEntry.FSTEntry_DIR) { - entryParam.setDir(true); - } else { - entryParam.setFileOffset(fileOffset * sectorSize); - entryParam.setFileSize(fileSize); - } - - entryParam.setFlags(flags); - entryParam.setContentIndex(contentIndex); - - final int nameOffset = getNameOffset(curEntry); - entryParam.setFileNameSupplier(() -> getName(nameOffset, namesSection)); - - FSTEntry entry = new FSTEntry(entryParam); - - parent.addChildren(entry); - - if (entryParam.isDir()) { - i = parseData(i + 1, (int) fileSize, entry, data, namesSection, sectorSize); - } else { - i++; - } - } - return i; - - } - - private static int getNameOffset(byte[] curEntry) { - // Its a 24bit number. We overwrite the first byte, then we can read it as an Integer. - // But at first we make a copy. - byte[] entryData = Arrays.copyOf(curEntry, curEntry.length); - entryData[0] = 0; - return ByteUtils.getIntFromBytes(entryData, 0); - } - - public static String getName(byte[] data, byte[] namesSection) { - return getName(getNameOffset(data), namesSection); - } - - public static String getName(int nameOffset, byte[] namesSection) { - int j = 0; - - while ((nameOffset + j) < namesSection.length && namesSection[nameOffset + j] != 0) { - j++; - } - return (new String(Arrays.copyOfRange(namesSection, nameOffset, nameOffset + j))); - } - -} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/header/Header.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/header/Header.java new file mode 100644 index 0000000..fb39cb9 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/header/Header.java @@ -0,0 +1,45 @@ +package de.mas.wiiu.jnus.entities.FST.header; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; + +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.SectionBlockSize; +import lombok.Data; + +@Data +public class Header { + public static String MAGIC = "FST"; + public static int LENGTH = 32; + private short FSTVersion; + private SectionBlockSize blockSize; + private long numberOfSections; + private short hashDisabled; + + public static Header parseData(byte[] data, long offset) throws IOException { + Header header = new Header(); + + byte[] strRaw = Arrays.copyOfRange(data, 0, 3); + String compareMagic = new String(strRaw, Charset.forName("ISO-8859-1")); + if (!MAGIC.equals(compareMagic)) { + throw new IOException("FST Header magic was wrong"); + } + header.FSTVersion = ByteUtils.getByteFromBytes(data, 3); + header.blockSize = new SectionBlockSize(ByteUtils.getUnsingedIntFromBytes(data, 4) & 0xFFFFFFFF); + header.numberOfSections = ByteUtils.getUnsingedIntFromBytes(data, 8) & 0xFFFFFFFF; + header.hashDisabled = ByteUtils.getByteFromBytes(data, 12); + + return header; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.put(MAGIC.getBytes()); + buffer.putInt(4, (int) blockSize.getBlockSize()); + buffer.putInt(8, (int) numberOfSections); + buffer.put(12, (byte) hashDisabled); + return buffer.array(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/DirectoryEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/DirectoryEntry.java new file mode 100644 index 0000000..4904d1d --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/DirectoryEntry.java @@ -0,0 +1,109 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.io.PrintStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.ByteUtils; +import lombok.Getter; +import lombok.Setter; +import lombok.val; + +public class DirectoryEntry extends NodeEntry { + @Getter @Setter protected int parentEntryNumber; + + @Getter @Setter protected int lastEntryNumber; + + @Getter private final List children = new ArrayList<>(); + + static DirectoryEntry parseData(byte[] data, int offset, NodeEntryParam param, SectionEntries sectionEntries, StringTable stringTable) { + + DirectoryEntry directoryEntry = new DirectoryEntry(); + directoryEntry.entryNumber = param.entryNumber; + directoryEntry.parent = param.parent; + directoryEntry.entryType = param.type; + directoryEntry.nameString = stringTable.getStringEntry(param.uint24); + + directoryEntry.parentEntryNumber = (int) (ByteUtils.getUnsingedIntFromBytes(data, offset + 4) & 0xFFFFFFFF); + directoryEntry.lastEntryNumber = (int) (ByteUtils.getUnsingedIntFromBytes(data, offset + 8) & 0xFFFFFFFF); + + directoryEntry.permission = param.permission; + + if (param.sectionNumber > sectionEntries.size()) { + throw new IllegalArgumentException("FST_M_Error_Broken_NodeEntry"); + } + directoryEntry.sectionEntry = sectionEntries.get(param.sectionNumber); + + return directoryEntry; + } + + @Override + public void printRecursive(PrintStream out, int space) { + super.printRecursive(out, space); + for (val child : children) { + child.printRecursive(out, space + 5); + } + } + + @Override + public void printFullPathRecursive(PrintStream out) { + super.printFullPathRecursive(out); + for (val child : children) { + child.printFullPathRecursive(out); + } + } + + @Override + public void printToStringRecursive(PrintStream out) { + super.printToStringRecursive(out); + for (val child : children) { + child.printToStringRecursive(out); + } + } + + public void addChild(NodeEntry entry) { + if (entry == null) { + throw new IllegalArgumentException("entry was empty"); + } + children.add(entry); + } + + public List getFileChildren() { + return getChildren().stream().filter(e -> e.isFile()).map(e -> (FileEntry) e).collect(Collectors.toList()); + } + + public List getDirChildren() { + return getChildren().stream().filter(e -> e.isDirectory()).map(e -> (DirectoryEntry) e).collect(Collectors.toList()); + } + + @Override + public Stream stream() { + return Stream.concat(Stream.of(this), getChildren().stream().flatMap(e -> e.stream())); + } + + @Override + public String toString() { + return "DirectoryEntry [entryNumber=" + entryNumber + ", entryType=" + entryType + ", permission=" + permission + ", nameString=" + nameString + "]"; + } + + @Override + protected byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(NodeEntry.LENGTH); + byte type = (byte) (entryType.getAsValue() & 0xFF); + int typeAndName = (nameString.getAddress() & 0x00FFFFFF) | (type << 24); + buffer.putInt(0, typeAndName); + + buffer.putInt(4, parentEntryNumber); + buffer.putInt(8, lastEntryNumber); + + buffer.putShort(12, (short) permission.getValue()); + buffer.putShort(14, (short) sectionEntry.getSectionNumber()); + + return buffer.array(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/EntryType.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/EntryType.java new file mode 100644 index 0000000..9126013 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/EntryType.java @@ -0,0 +1,71 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.util.Collections; +import java.util.Set; +import java.util.TreeSet; + +import lombok.Getter; + +public class EntryType { + private final Set entryTypes = new TreeSet<>(); + + public EntryType(EEntryType type) { + this.entryTypes.add(type); + } + + public EntryType(EEntryType... types) { + Collections.addAll(entryTypes, types); + } + + @Override + public String toString() { + return "EntryType [entryTypes=" + entryTypes + "]"; + } + + public EntryType(int type) { + for (EEntryType v : EEntryType.values()) { + if ((v.getValue() & type) == v.getValue()) { + entryTypes.add(v); + } + } + + if (entryTypes.contains(EEntryType.Directory)) { + entryTypes.remove(EEntryType.File); + } + + } + + public boolean has(EEntryType val) { + return entryTypes.contains(val); + } + + public enum EEntryType { + File(0), Directory(1), Link(0x80); + + @Getter private int value; + + EEntryType(int val) { + this.value = val; + } + + public static EEntryType findByValue(int val) { + for (EEntryType v : values()) { + if (v.getValue() == val) { + return v; + } + } + return null; + } + + } + + public int getAsValue() { + int val = 0; + + for (EEntryType e : entryTypes) { + val |= e.getValue(); + } + return val; + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/FileEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/FileEntry.java new file mode 100644 index 0000000..e63caf5 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/FileEntry.java @@ -0,0 +1,60 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.nio.ByteBuffer; + +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.SectionAddress; +import de.mas.wiiu.jnus.utils.blocksize.SectionBlockSize; +import lombok.Getter; + +public class FileEntry extends NodeEntry { + protected SectionAddress address = SectionAddress.empty(); + @Getter protected long size; + + public static NodeEntry parseData(byte[] data, int offset, NodeEntryParam param, SectionEntries sectionEntries, StringTable stringTable, + SectionBlockSize blockSize) { + FileEntry entry = new FileEntry(); + + entry.entryNumber = param.entryNumber; + entry.parent = param.parent; + entry.entryType = param.type; + entry.nameString = stringTable.getStringEntry(param.uint24); + + entry.address = new SectionAddress(blockSize, ByteUtils.getUnsingedIntFromBytes(data, offset + 4) & 0xFFFFFFFF); + entry.size = ByteUtils.getUnsingedIntFromBytes(data, offset + 8) & 0xFFFFFFFF; + + entry.permission = param.permission; + entry.sectionEntry = sectionEntries.get(param.sectionNumber); + + return entry; + } + + public long getOffset() { + return address.getAddressInBytes(); + } + + @Override + public String toString() { + return "FileEntry [address=" + String.format("0x%08X", address.getAddressInBytes()) + ", size=" + String.format("0x%08X", size) + ", entryNumber=" + + entryNumber + ", entryType=" + entryType + ", permission=" + permission + ", nameString=" + nameString + "]"; + } + + @Override + protected byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(NodeEntry.LENGTH); + byte type = (byte) (entryType.getAsValue() & 0xFF); + int typeAndName = (nameString.getAddress() & 0x00FFFFFF) | (type << 24); + buffer.putInt(0, typeAndName); + + buffer.putInt(4, (int) address.getValue()); + buffer.putInt(8, (int) size); + + buffer.putShort(12, (short) permission.getValue()); + buffer.putShort(14, (short) sectionEntry.getSectionNumber()); + + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntries.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntries.java new file mode 100644 index 0000000..749d6cb --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntries.java @@ -0,0 +1,123 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.Utils; +import de.mas.wiiu.jnus.utils.blocksize.SectionAddress; +import de.mas.wiiu.jnus.utils.blocksize.SectionBlockSize; +import lombok.Getter; +import lombok.val; + +public class NodeEntries { + + @Getter private final RootEntry rootEntry; + + public NodeEntries(RootEntry rootEntry) { + this.rootEntry = rootEntry; + } + + private static NodeEntry DeserializeImpl(byte[] data, int offset, DirectoryEntry parent, int entryNumber, SectionEntries sectionEntries, + StringTable stringTable, SectionBlockSize blockSize) { + NodeEntry nodeEntry = NodeEntry.AutoDeserialize(data, offset, parent, entryNumber, sectionEntries, stringTable, blockSize); + if (nodeEntry instanceof DirectoryEntry) { + DirectoryEntry dirNode = (DirectoryEntry) nodeEntry; + long curEntryNumber = dirNode.getEntryNumber() + 1; + while (curEntryNumber < dirNode.getLastEntryNumber()) { + NodeEntry entry = NodeEntries.DeserializeImpl(data, offset + ((int) curEntryNumber - (int) dirNode.getEntryNumber()) * NodeEntry.LENGTH, + dirNode, (int) curEntryNumber, sectionEntries, stringTable, blockSize); + dirNode.addChild(entry); + if (entry instanceof DirectoryEntry) { + curEntryNumber = ((DirectoryEntry) entry).getLastEntryNumber(); + } else { + curEntryNumber++; + } + } + } + return nodeEntry; + } + + public static NodeEntries parseData(byte[] data, int offset, SectionEntries sectionEntries, StringTable stringTable, SectionBlockSize blockSize) { + NodeEntry rootEntry = NodeEntries.DeserializeImpl(data, offset, (DirectoryEntry) null, 0, sectionEntries, stringTable, blockSize); + if (rootEntry instanceof RootEntry) { + return new NodeEntries((RootEntry) rootEntry); + } + throw new IllegalArgumentException("FST_NOT_ROOT_ENTRY"); + + } + + public static NodeEntry createFromFolder(File codeFolder, SectionBlockSize blockSize, StringTable stringtable, SectionEntry section, + Permission permission) { + + NodeEntry cur = createFromFile(codeFolder, null, stringtable, section, permission); + + val tmpList = cur.stream().filter(f -> f.isFile()).collect(Collectors.toList()); + + // calcuate offsets + SectionAddress curAddress = new SectionAddress(blockSize, 0L); + for (val e : tmpList) { + ((FileEntry) e).address = new SectionAddress(curAddress.getBlockSize(), curAddress.getValue()); + val size = ((FileEntry) e).getSize(); + int alignedSize = (int) Utils.align(size, (int) blockSize.getBlockSize()); + int fstBlockSize = (int) blockSize.getBlockSize(); + int offsetDiffInBlocks = alignedSize / fstBlockSize; + long newOffset = alignedSize % fstBlockSize == 0 ? offsetDiffInBlocks : offsetDiffInBlocks + 1; + curAddress = new SectionAddress(blockSize, newOffset + curAddress.getValue()); + } + + return cur; + } + + private static NodeEntry createFromFile(File inputFile, DirectoryEntry parent, StringTable stringtable, SectionEntry section, Permission permission) { + + StringEntry name = stringtable.getEntry(inputFile.getName()).orElseThrow(() -> new IllegalArgumentException("Failed to find String")); + + NodeEntry result = null; + if (inputFile.isDirectory()) { + DirectoryEntry curDir = new DirectoryEntry(); + for (File curFile : inputFile.listFiles()) { + NodeEntry child = createFromFile(curFile, curDir, stringtable, section, permission); + curDir.addChild(child); + } + curDir.setEntryType(new EntryType(EEntryType.Directory)); + result = curDir; + } else { + result = new RealFileEntry(inputFile); + result.setEntryType(new EntryType(EEntryType.File)); + } + + result.setParent(parent); + result.setPermission(permission); + result.setNameString(name); + result.setSectionEntry(section); + return result; + } + + public long getSizeInBytes() { + return NodeEntry.LENGTH * rootEntry.getLastEntryNumber(); + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate((int) getSizeInBytes()); + for (NodeEntry e : rootEntry.stream().collect(Collectors.toList())) { + buffer.put(e.getAsBytes()); + } + return buffer.array(); + } + + public Stream streamBy(SectionEntry e) { + return rootEntry.stream().filter(f -> f.getSectionEntry().equals(e)); + } + + public List getBy(SectionEntry e) { + return rootEntry.stream().filter(f -> f.getSectionEntry().equals(e)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntry.java new file mode 100644 index 0000000..99f7d0d --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/NodeEntry.java @@ -0,0 +1,141 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.stream.Stream; + +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.SectionBlockSize; +import lombok.Data; + +@Data +public abstract class NodeEntry { + public static final int LENGTH = 16; + + protected int entryNumber; + protected EntryType entryType; + protected Permission permission; + protected StringEntry nameString; + protected SectionEntry sectionEntry; + protected DirectoryEntry parent; + + public static NodeEntry AutoDeserialize(byte[] data, int offset, DirectoryEntry parent, int entryNumber, SectionEntries sectionEntries, + StringTable stringTable, SectionBlockSize blockSize) { + + byte[] curEntryData = data; + int curOffset = (int) offset; + if (offset > 0) { + curEntryData = Arrays.copyOfRange(curEntryData, curOffset, curOffset + NodeEntry.LENGTH); + curOffset = 0; + } + + NodeEntryParam param = new NodeEntryParam(); + param.permission = new Permission((int) (ByteUtils.getShortFromBytes(data, offset + 12) & 0xFFFF)); + param.sectionNumber = (int) (ByteUtils.getShortFromBytes(data, offset + 14) & 0xFFFF); + param.entryNumber = entryNumber; + param.parent = parent; + param.type = new EntryType(ByteUtils.getByteFromBytes(curEntryData, curOffset)); + + byte[] entryData = Arrays.copyOfRange(curEntryData, curOffset, curOffset + 4); + entryData[0] = 0; + param.uint24 = ByteUtils.getIntFromBytes(entryData, 0); + + if (param.type.has(EEntryType.Directory) && param.uint24 == 0) { // Root + return (NodeEntry) RootEntry.parseData(curEntryData, 0, param, sectionEntries, stringTable); + } else if (param.type.has(EEntryType.Directory)) { + return (NodeEntry) DirectoryEntry.parseData(curEntryData, 0, param, sectionEntries, stringTable); + } else if (param.type.has(EEntryType.File)) { + return (NodeEntry) FileEntry.parseData(curEntryData, 0, param, sectionEntries, stringTable, blockSize); + } + + throw new IllegalArgumentException("FST_UNKNOWN_NODE_TYPE"); + } + + public String getName() { + return nameString.toString(); + } + + protected StringBuilder getFullPathInternal() { + if (parent != null) { + return parent.getFullPathInternal().append('/').append(getName()); + } + return new StringBuilder(getName()); + } + + public String getFullPath() { + return getFullPathInternal().toString(); + } + + public String getPath() { + if (parent != null) { + return parent.getFullPath(); + } + return "/"; + } + + public void printRecursive(int space) { + printRecursive(System.out, space); + } + + public void printRecursive(PrintStream out, int space) { + for (int i = 0; i < space; i++) { + out.print(" "); + } + out.print(getName()); + out.println(); + } + + public void printPathRecursive() { + printFullPathRecursive(System.out); + } + + public void printFullPathRecursive(PrintStream out) { + out.println(getFullPath() +" " + getSectionEntry().getSectionNumber()); + } + + public void printToStringRecursive() { + printToStringRecursive(System.out); + } + + public void printToStringRecursive(PrintStream out) { + out.println(toString()); + } + + public boolean isDirectory() { + return entryType.has(EEntryType.Directory); + } + + public boolean isFile() { + return entryType.has(EEntryType.File); + } + + public boolean isLink() { + return entryType.has(EEntryType.Link); + } + + @Override + public String toString() { + return "NodeEntry [entryNumber=" + entryNumber + ", entryType=" + entryType + ", permission=" + permission + ", nameString=" + nameString + "]"; + } + + @Data + protected static class NodeEntryParam { + public DirectoryEntry parent; + public int entryNumber; + public int sectionNumber; + public Permission permission; + protected EntryType type; + protected int uint24; + } + + public Stream stream() { + return Stream.of(this); + } + + protected abstract byte[] getAsBytes(); +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/Permission.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/Permission.java new file mode 100644 index 0000000..b8c6398 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/Permission.java @@ -0,0 +1,13 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import lombok.Data; + +@Data +public class Permission { + private int value; + + public Permission(int value) { + this.value = value; + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RealFileEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RealFileEntry.java new file mode 100644 index 0000000..1c4de17 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RealFileEntry.java @@ -0,0 +1,16 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import java.io.File; + +import lombok.Getter; + +public class RealFileEntry extends FileEntry { + @Getter private final File fd; + + public RealFileEntry(File inputFile) { + super(); + this.fd = inputFile; + this.size = inputFile.length(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RootEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RootEntry.java new file mode 100644 index 0000000..6085dbd --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/RootEntry.java @@ -0,0 +1,32 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntries; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.utils.ByteUtils; + +public class RootEntry extends DirectoryEntry { + public RootEntry(DirectoryEntry input) { + if (!input.entryType.has(EEntryType.Directory) || input.entryNumber != 0) { + throw new IllegalArgumentException("Input is no root entry."); + } + + this.entryNumber = input.entryNumber; + this.parent = input.parent; + this.nameString = input.nameString; + this.entryType = input.entryType; + + this.parentEntryNumber = input.parentEntryNumber; + this.lastEntryNumber = input.lastEntryNumber; + this.permission = input.permission; + this.sectionEntry = input.sectionEntry; + } + + public static int parseLastEntryNumber(byte[] data, long offset) { + return (int) (ByteUtils.getUnsingedIntFromBytes(data, (int) (offset + 8)) & 0xFFFFFFFF); + } + + public static RootEntry parseData(byte[] data, int offset, NodeEntryParam param, SectionEntries sectionEntries, StringTable stringTable) { + return new RootEntry(DirectoryEntry.parseData(data, offset, param, sectionEntries, stringTable)); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/VirtualFileEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/VirtualFileEntry.java new file mode 100644 index 0000000..ecf78cd --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/nodeentry/VirtualFileEntry.java @@ -0,0 +1,30 @@ +package de.mas.wiiu.jnus.entities.FST.nodeentry; + +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringEntry; + +public class VirtualFileEntry extends FileEntry { + private long offset; + + public static FileEntry create(DirectoryEntry parent, long offset, long size, StringEntry name, Permission permission, SectionEntry section) { + VirtualFileEntry entry = new VirtualFileEntry(); + + entry.entryNumber = 0; + entry.parent = parent; + entry.entryType = new EntryType(EEntryType.File); + entry.nameString = name; + + entry.offset = offset; + entry.size = size; + entry.permission = permission; + entry.sectionEntry = section; + + return entry; + } + + @Override + public long getOffset() { + return offset; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntries.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntries.java new file mode 100644 index 0000000..e5a52a2 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntries.java @@ -0,0 +1,31 @@ +package de.mas.wiiu.jnus.entities.FST.sectionentry; + +import java.nio.ByteBuffer; +import java.util.LinkedList; + +import de.mas.wiiu.jnus.utils.blocksize.VolumeBlockSize; + +public class SectionEntries extends LinkedList { + private static final long serialVersionUID = 1L; + + public static SectionEntries parseData(byte[] data, long offset, long numberOfSections, VolumeBlockSize blockSize) { + SectionEntries sectionEntries = new SectionEntries(); + for (int i = 0; i < numberOfSections; i++) { + sectionEntries.add(SectionEntry.parseData(data, offset + (i * 32), "Section: " + i, i, blockSize)); + } + + return sectionEntries; + } + + public long getSizeInBytes() { + return size() * SectionEntry.LENGTH; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate((int) getSizeInBytes()); + for (SectionEntry entry : this) { + buffer.put(entry.getAsBytes()); + } + return buffer.array(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntry.java new file mode 100644 index 0000000..e36c9d0 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/sectionentry/SectionEntry.java @@ -0,0 +1,50 @@ +package de.mas.wiiu.jnus.entities.FST.sectionentry; + +import java.nio.ByteBuffer; + +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.SizeInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.VolumeBlockSize; +import lombok.Data; + +@Data +public class SectionEntry { + public static final int LENGTH = 32; + private final int sectionNumber; + private final String name; + private AddressInVolumeBlocks address; + private SizeInVolumeBlocks size = new SizeInVolumeBlocks(new VolumeBlockSize(0), (long) 0); + private long ownerID; + private long groupID; + private short hashMode; + + public static SectionEntry parseData(byte[] data, long offset, String sectionName, int sectionNumber, VolumeBlockSize blockSize) { + SectionEntry sectionEntry = new SectionEntry(sectionNumber, sectionName); + + sectionEntry.address = new AddressInVolumeBlocks(blockSize, ByteUtils.getUnsingedIntFromBytes(data, (int) offset) & 0xFFFFFFFF); + sectionEntry.size = new SizeInVolumeBlocks(blockSize, ByteUtils.getUnsingedIntFromBytes(data, (int) offset + 4) & 0xFFFFFFFF); + sectionEntry.ownerID = ByteUtils.getLongFromBytes(data, (int) offset + 8); + sectionEntry.groupID = ByteUtils.getUnsingedIntFromBytes(data, (int) offset + 16) & 0xFFFFFFFF; + sectionEntry.hashMode = ByteUtils.getByteFromBytes(data, (int) offset + 20); + + return sectionEntry; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.putInt(0, (int) address.getValue()); + buffer.putInt(4, (int) size.getValue()); + buffer.putLong(8, ownerID); + buffer.putInt(16, (int) groupID); + buffer.put(20, (byte) hashMode); + return buffer.array(); + } + + @Override + public String toString() { + return "SectionEntry [sectionNumber=" + sectionNumber + ", name=" + name + ", address=" + (address.getAddressInBytes() / 1024.0) + " KiB , size=" + + (size.getSizeInBytes() / 1024.0) + " KiB , ownerID=" + String.format("%016X", ownerID) + ", groupID=" + String.format("%04X", groupID) + + ", hashMode=" + hashMode + "]"; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringEntry.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringEntry.java new file mode 100644 index 0000000..86353b5 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringEntry.java @@ -0,0 +1,15 @@ +package de.mas.wiiu.jnus.entities.FST.stringtable; + +import lombok.Data; + +@Data +public class StringEntry { + private final StringTable stringTable; + private final int address; + + @Override + public String toString() { + return stringTable.getByAddress(address); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringTable.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringTable.java new file mode 100644 index 0000000..caed756 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/stringtable/StringTable.java @@ -0,0 +1,92 @@ +package de.mas.wiiu.jnus.entities.FST.stringtable; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public class StringTable { + private final Map stringEntries = new HashMap<>(); + private final Map strings = new HashMap<>(); + + public StringTable(String... strings) { + this(Arrays.asList(strings)); + } + + public StringTable(Collection strings) { + int curLength = 0; + this.strings.put(curLength, ""); + this.stringEntries.put(curLength, new StringEntry(this, curLength)); + curLength = 1; + for (String s : strings) { + this.stringEntries.put(curLength, new StringEntry(this, curLength)); + this.strings.put(curLength, s); + curLength += s.length() + 1; + } + } + + private StringTable() { + + } + + public static StringTable parseData(byte[] data, long offset, long stringsCount) { + StringTable stringTable = new StringTable(); + long curOffset = offset; + int i; + for (i = 0; curOffset < data.length && i < stringsCount; ++curOffset) { + if (data[(int) curOffset] == (byte) 0) { + ++i; + } + } + if (i < stringsCount) { + throw new IllegalArgumentException("StringTable is broken"); + } + + String[] strArray = new String(Arrays.copyOfRange(data, (int) offset, (int) (curOffset))).split("\0"); + int curLength = 0; + for (i = 0; i < strArray.length; i++) { + stringTable.stringEntries.put(curLength, new StringEntry(stringTable, curLength)); + stringTable.strings.put(curLength, strArray[i]); + curLength += strArray[i].length() + 1; + } + + return stringTable; + } + + public String getByAddress(int address) { + return strings.get(address); + } + + public StringEntry getStringEntry(int address) { + StringEntry entry = stringEntries.get(address); + if (entry == null) { + throw new IllegalArgumentException("Failed to find string entry for address: " + address); + } + return entry; + } + + public long getSize() { + int capacity = 1; // root entry + for (String s : this.strings.values()) { + capacity += s.length() + 1; + } + return capacity; + } + + public Optional getEntry(String str) { + return stringEntries.entrySet().stream().filter(e -> e.getValue().toString().equals(str)).map(e -> e.getValue()).findFirst(); + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate((int) getSize()); + for (Entry s : this.strings.entrySet()) { + buffer.position(s.getKey()); + buffer.put(s.getValue().getBytes()); + } + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java b/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java index 436119f..cbe19e2 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java @@ -9,7 +9,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Optional; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.ContentDecryptor; import de.mas.wiiu.jnus.interfaces.NUSDataProcessor; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; @@ -52,10 +52,10 @@ public class DefaultNUSDataProcessor implements NUSDataProcessor { } catch (Exception e) { in.throwException(e); try { - out.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } } }).start(); @@ -139,11 +139,11 @@ public class DefaultNUSDataProcessor implements NUSDataProcessor { } catch (Exception e) { in.throwException(e); try { - in.close(); - } catch (IOException e1) { - // TODO Auto-generated catch block - e1.printStackTrace(); - } + in.close(); + } catch (IOException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } } }).start(); @@ -156,7 +156,7 @@ public class DefaultNUSDataProcessor implements NUSDataProcessor { long payloadOffset = offset; long streamOffset = payloadOffset; long streamFilesize = 0; - + streamOffset = (payloadOffset / 0xFC00) * 0x10000; long offsetInBlock = payloadOffset - ((streamOffset / 0x10000) * 0xFC00); if (offsetInBlock + size < 0xFC00) { @@ -182,7 +182,7 @@ public class DefaultNUSDataProcessor implements NUSDataProcessor { throw new IOException(e); } } else { - InputStream in = readDecryptedContentAsStream(c, offset, size); + InputStream in = readDecryptedContentAsStream(c, offset, Utils.align(size, 16)); byte[] hash = null; if (forceCheckHash) { diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java index b7eecf2..4705b55 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java @@ -21,8 +21,16 @@ import java.io.OutputStream; import java.util.stream.Collectors; import de.mas.wiiu.jnus.NUSTitle; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType; +import de.mas.wiiu.jnus.entities.FST.nodeentry.EntryType.EEntryType; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.Permission; +import de.mas.wiiu.jnus.entities.FST.nodeentry.RootEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.VirtualFileEntry; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.FST.stringtable.StringTable; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; import de.mas.wiiu.jnus.interfaces.HasNUSTitle; import de.mas.wiiu.jnus.interfaces.NUSDataProcessor; @@ -34,7 +42,7 @@ import lombok.extern.java.Log; public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle { private final NUSDataProcessor dataProcessor; private final NUSTitle title; - private final FSTEntry rootEntry; + private final RootEntry rootEntry; @Getter @Setter private String name; public FSTDataProviderNUSTitle(NUSTitle title) throws IOException { @@ -43,36 +51,45 @@ public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle { this.name = String.format("%016X", title.getTMD().getTitleID()); if (title.getFST().isPresent()) { - rootEntry = title.getFST().get().getRoot(); + rootEntry = title.getFST().get().getRootEntry(); } else if (title.getTMD().getContentCount() == 1) { // If the tmd has only one content file, it has not FST. We have to create our own FST. Content c = title.getTMD().getAllContents().values().stream().collect(Collectors.toList()).get(0); - FSTEntry root = FSTEntry.getRootFSTEntry(); - root.addChildren(FSTEntry.createFSTEntry(root, "data.bin", c)); + + StringTable stringtable = new StringTable("", "data.bin"); + SectionEntry dummySection = new SectionEntry(0, "dummy"); + DirectoryEntry dir = new DirectoryEntry(); + dir.setEntryType(new EntryType(EEntryType.Directory)); + dir.setParent(null); + dir.setEntryNumber(0); + dir.setNameString(stringtable.getEntry("").get()); + dir.setPermission(new Permission(0)); + dir.setSectionEntry(dummySection); + + RootEntry root = new RootEntry(dir); + + root.addChild(VirtualFileEntry.create(root, 0, c.getDecryptedFileSize(), stringtable.getEntry("data.bin").get(), new Permission(0), dummySection)); rootEntry = root; } else { throw new IOException("No FST root entry was found"); } } - + @Override - public FSTEntry getRoot() { + public RootEntry getRoot() { return rootEntry; } @Override - public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException { - if (entry.isNotInPackage()) { - if (entry.isNotInPackage()) { - log.info("Decryption not possible because the FSTEntry is not in this package"); - } + public long readFileToStream(OutputStream out, FileEntry entry, long offset, long size) throws IOException { + if (entry.isLink()) { + log.info("Decryption not possible because the FSTEntry is not in this package"); out.close(); return -1; } + Content c = title.getTMD().getContentByIndex(entry.getSectionEntry().getSectionNumber()); - Content c = title.getTMD().getContentByIndex(entry.getContentIndex()); - - return dataProcessor.readPlainDecryptedContentToStream(out, c, offset + entry.getFileOffset(), size, size == entry.getFileSize()); + return dataProcessor.readPlainDecryptedContentToStream(out, c, offset + entry.getOffset(), size, size == entry.getSize()); } @Override diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java index c2fc82f..8ef27bb 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java @@ -19,52 +19,53 @@ package de.mas.wiiu.jnus.implementations; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Optional; -import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; -import de.mas.wiiu.jnus.implementations.wud.parser.WUDDataPartition; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.RootEntry; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.WiiUDataPartition; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; -import de.mas.wiiu.jnus.utils.FSTUtils; public class FSTDataProviderWUDDataPartition implements FSTDataProvider { - private final WUDDataPartition partition; + private final WiiUDataPartition partition; private final WUDDiscReader discReader; - private final byte[] titleKey; + private final Optional discKey; - public FSTDataProviderWUDDataPartition(WUDDataPartition partition, WUDDiscReader discReader, byte[] titleKey) { + public FSTDataProviderWUDDataPartition(WiiUDataPartition partition, WUDDiscReader discReader, Optional discKey) { this.partition = partition; this.discReader = discReader; - this.titleKey = titleKey; + this.discKey = discKey; } @Override public String getName() { - return partition.getPartitionName(); + return partition.getVolumeID(); } @Override - public FSTEntry getRoot() { - return partition.getFST().getRoot(); + public RootEntry getRoot() { + return partition.getFst().getRootEntry(); } @Override - public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException { - ContentFSTInfo info = FSTUtils.getFSTInfoForContent(partition.getFST(), entry.getContentIndex()) - .orElseThrow(() -> new IOException("Failed to find FSTInfo")); - if (titleKey == null) { - return discReader.readEncryptedToStream(out, partition.getPartitionOffset() + info.getOffset() + entry.getFileOffset() + offset, size); + public long readFileToStream(OutputStream out, FileEntry entry, long offset, long size) throws IOException { + SectionEntry info = entry.getSectionEntry(); + if (!discKey.isPresent()) { + return discReader.readEncryptedToStream(out, + partition.getSectionOffsetOnDefaultPartition() + info.getAddress().getAddressInBytes() + entry.getOffset() + offset, size); } - return discReader.readDecryptedToOutputStream(out, partition.getPartitionOffset() + info.getOffset(), entry.getFileOffset() + offset, size, titleKey, - null, false); + return discReader.readDecryptedToOutputStream(out, partition.getSectionOffsetOnDefaultPartition() + info.getAddress().getAddressInBytes(), + entry.getOffset() + offset, size, discKey.get(), null, false); } @Override - public InputStream readFileAsStream(FSTEntry entry, long offset, long size) throws IOException { - if (titleKey == null) { - ContentFSTInfo info = FSTUtils.getFSTInfoForContent(partition.getFST(), entry.getContentIndex()) - .orElseThrow(() -> new IOException("Failed to find FSTInfo")); - return discReader.readEncryptedToStream(partition.getPartitionOffset() + info.getOffset() + entry.getFileOffset() + offset, size); + public InputStream readFileAsStream(FileEntry entry, long offset, long size) throws IOException { + if (!discKey.isPresent()) { + SectionEntry info = entry.getSectionEntry(); + return discReader.readEncryptedToStream( + partition.getSectionOffsetOnDefaultPartition() + info.getAddress().getAddressInBytes() + entry.getOffset() + offset, size); } return FSTDataProvider.super.readFileAsStream(entry, offset, size); diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java index 23dc7ad..0d52df5 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java @@ -7,7 +7,8 @@ import java.io.OutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.RootEntry; import de.mas.wiiu.jnus.implementations.wud.wumad.WumadDataPartition; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; import de.mas.wiiu.jnus.utils.StreamUtils; @@ -27,24 +28,23 @@ public class FSTDataProviderWumadDataPartition implements FSTDataProvider { } @Override - public FSTEntry getRoot() { - return dataPartition.getFST().getRoot(); + public RootEntry getRoot() { + return dataPartition.getFST().getRootEntry(); } @Override - public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException { + public long readFileToStream(OutputStream out, FileEntry entry, long offset, long size) throws IOException { return StreamUtils.saveInputStreamToOutputStream(readFileAsStream(entry, offset, size), out, size); } @Override - public InputStream readFileAsStream(FSTEntry entry, long offset, long size) throws IOException { - ZipEntry zipEntry = zipFile.stream() - .filter(e -> e.getName().equals(String.format("p%s.s%04d.00000000.app", dataPartition.getPartitionName(), entry.getContentIndex()))) - .findFirst() - .orElseThrow(() -> new FileNotFoundException()); + public InputStream readFileAsStream(FileEntry entry, long offset, long size) throws IOException { + ZipEntry zipEntry = zipFile.stream().filter( + e -> e.getName().equals(String.format("p%s.s%04d.00000000.app", dataPartition.getPartitionName(), entry.getSectionEntry().getSectionNumber()))) + .findFirst().orElseThrow(() -> new FileNotFoundException()); InputStream in = zipFile.getInputStream(zipEntry); - StreamUtils.skipExactly(in, offset + entry.getFileOffset()); + StreamUtils.skipExactly(in, offset + entry.getOffset()); return in; } diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java index b700db8..faa12cf 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java @@ -24,8 +24,10 @@ import java.util.Map; import java.util.Optional; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.ContentDecryptor; import de.mas.wiiu.jnus.interfaces.FSTDataProvider; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; @@ -33,9 +35,9 @@ import de.mas.wiiu.jnus.utils.FSTUtils; public class NUSDataProviderFST implements NUSDataProvider { private final FSTDataProvider fstDataProvider; - private final FSTEntry base; + private final DirectoryEntry base; - public NUSDataProviderFST(FSTDataProvider fstDataProvider, FSTEntry base) { + public NUSDataProviderFST(FSTDataProvider fstDataProvider, DirectoryEntry base) { this.base = base; this.fstDataProvider = fstDataProvider; } @@ -47,9 +49,12 @@ public class NUSDataProviderFST implements NUSDataProvider { @Override public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException { String filename = content.getFilename(); - Optional contentFileOpt = FSTUtils.getChildOfDirectory(base, filename); - FSTEntry contentFile = contentFileOpt.orElseThrow(() -> new FileNotFoundException(filename + " was not found.")); - return fstDataProvider.readFileAsStream(contentFile, offset, size); + Optional contentFileOpt = FSTUtils.getChildOfDirectory(base, filename); + NodeEntry contentFile = contentFileOpt.orElseThrow(() -> new FileNotFoundException(filename + " was not found.")); + if (!contentFile.isFile() || contentFile.isLink()) { + throw new IOException("Requested file is not a file or is a link"); + } + return fstDataProvider.readFileAsStream((FileEntry) contentFile, offset, size); } Map> h3Hashes = new HashMap<>(); @@ -64,12 +69,14 @@ public class NUSDataProviderFST implements NUSDataProvider { return res; } - private Optional readFileByFilename(FSTEntry base, String filename) throws IOException { - Optional entryOpt = FSTUtils.getChildOfDirectory(base, filename); + private Optional readFileByFilename(DirectoryEntry base, String filename) throws IOException { + Optional entryOpt = FSTUtils.getChildOfDirectory(base, filename); if (entryOpt.isPresent()) { - - FSTEntry entry = entryOpt.get(); - return Optional.of(fstDataProvider.readFile(entry)); + NodeEntry entry = entryOpt.get(); + if (!entry.isFile() || entry.isLink()) { + return Optional.empty(); + } + return Optional.of(fstDataProvider.readFile((FileEntry) entry)); } return Optional.empty(); } diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java index 03034c1..a0d3908 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java @@ -25,7 +25,7 @@ import java.nio.file.Files; import java.util.Optional; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.utils.FileUtils; import de.mas.wiiu.jnus.utils.StreamUtils; diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java index 0417bb2..419a492 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java @@ -25,7 +25,7 @@ import java.nio.file.Files; import java.util.Optional; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.utils.StreamUtils; import lombok.Getter; @@ -40,7 +40,7 @@ public class NUSDataProviderLocalBackup implements NUSDataProvider { public NUSDataProviderLocalBackup(String localPath, short version) { this.localPath = localPath; - this.titleVersion = version; + this.titleVersion = version; } private String getFilePathOnDisk(Content c) { diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java index 8872a45..f9d81ca 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Optional; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.interfaces.Parallelizable; import de.mas.wiiu.jnus.utils.download.NUSDownloadService; @@ -32,15 +32,16 @@ import lombok.Getter; public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable { @Getter private final int version; @Getter private final long titleID; + private final NUSDownloadService downloadService; - public NUSDataProviderRemote(int version, long titleID) { + public NUSDataProviderRemote(int version, long titleID, NUSDownloadService downloadService) { this.version = version; this.titleID = titleID; + this.downloadService = downloadService; } @Override public InputStream readRawContentAsStream(Content content, long fileOffsetBlock, long size) throws IOException { - NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance(); return downloadService.getInputStreamForURL(getRemoteURL(content), fileOffsetBlock, size); } @@ -68,13 +69,11 @@ public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable { return resOpt; } - Optional tmdCache = null; + Optional tmdCache = Optional.empty(); @Override public Optional getRawTMD() throws IOException { - if (tmdCache == null) { - NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance(); - + if (!tmdCache.isPresent()) { long titleID = getTitleID(); int version = getVersion(); @@ -88,17 +87,16 @@ public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable { return tmdCache; } - Optional ticketCache = null; + Optional ticketCache = Optional.empty(); @Override public Optional getRawTicket() throws IOException { - if (ticketCache == null) { - NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance(); + if (!ticketCache.isPresent()) { byte[] res = downloadService.downloadTicketToByteArray(titleID); if (res == null || res.length == 0) { return Optional.empty(); } - ticketCache = Optional.of(res); + ticketCache = Optional.of(res); } return ticketCache; } diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java index 1f5f43b..4f04d53 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java @@ -18,41 +18,54 @@ package de.mas.wiiu.jnus.implementations; import java.io.IOException; import java.io.InputStream; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Optional; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; -import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.entities.TMD.Content; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.WiiUGMPartition; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import de.mas.wiiu.jnus.utils.FSTUtils; +import de.mas.wiiu.jnus.utils.HashUtil; +import de.mas.wiiu.jnus.utils.Utils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.SizeInVolumeBlocks; import lombok.Getter; +import lombok.var; import lombok.extern.java.Log; @Log public class NUSDataProviderWUD implements NUSDataProvider { - @Getter private final WUDGamePartition gamePartition; + @Getter private final WiiUGMPartition gamePartition; @Getter private final WUDDiscReader discReader; @Getter private FST fst; - public NUSDataProviderWUD(WUDGamePartition gamePartition, WUDDiscReader discReader) { + public NUSDataProviderWUD(WiiUGMPartition gamePartition, WUDDiscReader discReader) { this.gamePartition = gamePartition; this.discReader = discReader; } @Override public void setFST(FST fst) { + // We need to set the correct blocksizes + var blockSize = gamePartition.getVolumes().values().iterator().next().getBlockSize(); + for (SectionEntry e : fst.getSectionEntries()) { + e.setAddress(new AddressInVolumeBlocks(blockSize, e.getAddress().getValue())); + e.setSize(new SizeInVolumeBlocks(blockSize, e.getSize().getValue())); + } this.fst = fst; } public long getOffsetInWUD(Content content) throws IOException { - if (content.getIndex() == 0) { // Index 0 is the FST which is at the beginning of the partion; - return getGamePartition().getPartitionOffset(); + if (content.getIndex() == 0) { // Index 0 is the FST which is at the beginning of the partition; + var vh = getGamePartition().getVolumes().values().iterator().next(); + return getGamePartition().getSectionOffsetOnDefaultPartition() + vh.getFSTAddress().getAddressInBytes(); } - ContentFSTInfo info = FSTUtils.getFSTInfoForContent(fst, content.getIndex()).orElseThrow(() -> new IOException("Failed to find FSTInfo")); - return getGamePartition().getPartitionOffset() + info.getOffset(); + SectionEntry info = FSTUtils.getSectionEntryForIndex(fst, content.getIndex()).orElseThrow(() -> new IOException("Failed to find FSTInfo")); + return getGamePartition().getSectionOffsetOnDefaultPartition() + info.getAddress().getAddressInBytes(); } @Override @@ -60,16 +73,22 @@ public class NUSDataProviderWUD implements NUSDataProvider { WUDDiscReader discReader = getDiscReader(); long offset = getOffsetInWUD(content) + fileOffsetBlock; - return discReader.readEncryptedToStream(offset, size); + return discReader.readEncryptedToStream(offset, Utils.align(size, 16)); } @Override public Optional getContentH3Hash(Content content) throws IOException { - if (!getGamePartitionHeader().isCalculatedHashes()) { - log.info("Calculating h3 hashes"); - getGamePartitionHeader().calculateHashes(getGamePartition().getTmd().getAllContents()); + byte[] hash = getGamePartition().getVolumes().values().iterator().next().getH3HashArrayList().get(content.getIndex()).getH3HashArray(); + // Checking the hash of the h3 file. + try { + if (!Arrays.equals(HashUtil.hashSHA1(hash), content.getSHA2Hash())) { + log.warning("h3 incorrect from WUD"); + } + } catch (NoSuchAlgorithmException e) { + log.warning(e.getMessage()); } - return getGamePartitionHeader().getH3Hash(content); + + return Optional.of(hash); } @Override @@ -87,10 +106,6 @@ public class NUSDataProviderWUD implements NUSDataProvider { return Optional.of(getGamePartition().getRawCert()); } - public GamePartitionHeader getGamePartitionHeader() { - return getGamePartition().getPartitionHeader(); - } - @Override public void cleanup() { // We don't need it diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java index ba2117c..3774a35 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java @@ -24,7 +24,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipException; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo; import de.mas.wiiu.jnus.implementations.woomy.WoomyZipFile; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java index 01caa73..79c4e7f 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java @@ -19,7 +19,8 @@ package de.mas.wiiu.jnus.implementations; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.text.ParseException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; @@ -28,13 +29,14 @@ import java.util.Optional; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import de.mas.wiiu.jnus.entities.TMD; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.implementations.wud.wumad.WumadGamePartition; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; +import de.mas.wiiu.jnus.utils.HashUtil; import de.mas.wiiu.jnus.utils.StreamUtils; +import lombok.extern.java.Log; +@Log public class NUSDataProviderWumad implements NUSDataProvider { private final ZipFile wumad; private final WumadGamePartition partition; @@ -72,15 +74,17 @@ public class NUSDataProviderWumad implements NUSDataProvider { @Override public Optional getContentH3Hash(Content content) throws IOException { - GamePartitionHeader partitionHeader = partition.getPartitionHeader(); - if (!partitionHeader.isCalculatedHashes()) { - try { - partitionHeader.calculateHashes(TMD.parseTMD(getRawTMD().get()).getAllContents()); - } catch (ParseException e) { - throw new IOException(e); + byte[] hash = partition.getPartitionHeader().getH3HashArrayList().get(content.getIndex()).getH3HashArray(); + // Checking the hash of the h3 file. + try { + if (!Arrays.equals(HashUtil.hashSHA1(hash), content.getSHA2Hash())) { + log.warning("h3 incorrect from WUD"); } + } catch (NoSuchAlgorithmException e) { + log.warning(e.getMessage()); } - return partitionHeader.getH3Hash(content); + + return Optional.of(hash); } @Override diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/GamePartitionHeader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/GamePartitionHeader.java deleted file mode 100644 index ee886d5..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/GamePartitionHeader.java +++ /dev/null @@ -1,103 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud; - -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.utils.ByteUtils; -import de.mas.wiiu.jnus.utils.HashUtil; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.java.Log; - -@Log -public final class GamePartitionHeader { - @Getter @Setter private boolean calculatedHashes = false; - @Getter private final HashMap h3Hashes = new HashMap<>(); - @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE) private byte[] rawData; - - private GamePartitionHeader() { - } - - // TODO: real processing. Currently we are ignoring everything except the hashes - public static GamePartitionHeader parseHeader(byte[] header) { - GamePartitionHeader result = new GamePartitionHeader(); - result.setRawData(header); - return result; - } - - public void addH3Hashes(short index, byte[] hash) { - getH3Hashes().put(index, hash); - } - - public Optional getH3Hash(Content content) { - if (content == null) { - log.info("Can't find h3 hash, given content is null."); - return Optional.empty(); - } - - return Optional.of(getH3Hashes().get(content.getIndex())); - } - - public void calculateHashes(Map allContents) { - byte[] header = getRawData(); - - // Calculating offset for the hashes - int cnt = ByteUtils.getIntFromBytes(header, 0x10); - int start_offset = 0x40 + cnt * 0x04; - - int offset = 0; - - // We have to make sure, that the list is ordered by index - List contents = new ArrayList<>(allContents.values()); - Collections.sort(contents, (o1, o2) -> Short.compare(o1.getIndex(), o2.getIndex())); - - for (Content c : allContents.values()) { - if (!c.isHashed() || !c.isEncrypted()) { - continue; - } - - // The encrypted content are splitted in 0x10000 chunk. For each 0x1000 chunk we need one entry in the h3 - int cnt_hashes = (int) (c.getEncryptedFileSize() / 0x10000 / 0x1000) + 1; - - byte[] hash = Arrays.copyOfRange(header, start_offset + offset * 0x14, start_offset + (offset + cnt_hashes) * 0x14); - - // Checking the hash of the h3 file. - try { - if (!Arrays.equals(HashUtil.hashSHA1(hash), c.getSHA2Hash())) { - log.info("h3 incorrect from WUD"); - } - } catch (NoSuchAlgorithmException e) { - log.warning(e.getMessage()); - } - - addH3Hashes(c.getIndex(), hash); - offset += cnt_hashes; - } - - setCalculatedHashes(true); - } -} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/WiiUDisc.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/WiiUDisc.java new file mode 100644 index 0000000..f202419 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/WiiUDisc.java @@ -0,0 +1,28 @@ +package de.mas.wiiu.jnus.implementations.wud; + +import java.io.IOException; +import java.util.Optional; + +import de.mas.wiiu.jnus.implementations.wud.header.WiiUDiscHeader; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import lombok.Data; + +@Data +public class WiiUDisc { + public final static long DISC_SIZE = 25025314816L; + private WiiUDiscHeader header = new WiiUDiscHeader(); + private Optional reader; + private Optional discKey; + + public static WiiUDisc parseData(WUDDiscReader reader, Optional discKey) throws IOException { + WiiUDisc disc = new WiiUDisc(); + disc.setReader(Optional.of(reader)); + disc.setHeader(WiiUDiscHeader.parseData(reader, discKey)); + disc.setDiscKey(discKey); + return disc; + } + + public byte[] getAsBytes() throws IOException { + return header.getAsBytes(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUContentsInformation.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUContentsInformation.java new file mode 100644 index 0000000..00cd00c --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUContentsInformation.java @@ -0,0 +1,42 @@ +package de.mas.wiiu.jnus.implementations.wud.content; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Optional; + +import de.mas.wiiu.jnus.implementations.wud.content.partitions.WiiUPartitions; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import lombok.Data; + +@Data +public class WiiUContentsInformation { + + public static final int LENGTH = 32768; + private WiiUDiscContentsHeader discContentHeader = new WiiUDiscContentsHeader(); + private WiiUPartitions partitions = new WiiUPartitions(); + + public static WiiUContentsInformation parseData(WUDDiscReader reader, Optional discKey, long offset) throws IOException { + + WiiUContentsInformation contentsInformation = new WiiUContentsInformation(); + long curOffset = offset; + WiiUDiscContentsHeader discContentHeader = WiiUDiscContentsHeader.parseData(reader, discKey, curOffset); + contentsInformation.setDiscContentHeader(discContentHeader); + curOffset += WiiUDiscContentsHeader.LENGTH; + contentsInformation.setPartitions( + WiiUPartitions.parseData(reader, discKey, curOffset, discContentHeader.getNumberOfPartition(), discContentHeader.getBlockSize())); + curOffset += WiiUPartitions.LENGTH; + + if (curOffset - offset != LENGTH) { + throw new IOException("Length mismatch. " + (curOffset - offset) + " " + LENGTH); + } + + return contentsInformation; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.put(discContentHeader.getAsBytes()); + buffer.put(partitions.getAsBytes()); + return buffer.array(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUDiscContentsHeader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUDiscContentsHeader.java new file mode 100644 index 0000000..f1bbab9 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/WiiUDiscContentsHeader.java @@ -0,0 +1,64 @@ +package de.mas.wiiu.jnus.implementations.wud.content; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Optional; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.Utils; +import de.mas.wiiu.jnus.utils.blocksize.DiscBlockSize; +import lombok.Data; + +@Data +public class WiiUDiscContentsHeader { + public static final int LENGTH = 2048; + public static final int MAGIC = 0xCCA6E67B; + + private DiscBlockSize blockSize = new DiscBlockSize(32768); + private int numberOfPartition = 0; + private byte[] tocHash = new byte[20]; + + public static WiiUDiscContentsHeader parseData(WUDDiscReader reader, Optional discKey, long offset) throws IOException { + byte[] rawBytes = new byte[0]; + if (!discKey.isPresent()) { + rawBytes = reader.readEncryptedToByteArray(offset, 0, LENGTH); + } else { + rawBytes = reader.readDecryptedToByteArray(offset, 0, LENGTH, discKey.get(), null, true); + } + + if (rawBytes.length != LENGTH) { + throw new IOException("Failed to read WiiUDiscContentsHeader"); + } + + ByteBuffer buffer = ByteBuffer.wrap(rawBytes); + byte[] magicCompare = new byte[4]; + buffer.get(magicCompare); + if (!Arrays.equals(magicCompare, ByteUtils.getBytesFromInt(MAGIC))) { + throw new IOException("WiiUDiscContentsHeader MAGIC mismatch."); + } + + WiiUDiscContentsHeader contentsHeader = new WiiUDiscContentsHeader(); + contentsHeader.setBlockSize(new DiscBlockSize(ByteUtils.getUnsingedIntFromBytes(rawBytes, 4) & 0xFFFFFFFF)); + contentsHeader.setTocHash(Arrays.copyOfRange(rawBytes, 8, 28)); + contentsHeader.setNumberOfPartition(ByteUtils.getIntFromBytes(rawBytes, 28)); + return contentsHeader; + } + + @Override + public String toString() { + return "WiiUDiscContentsHeader [blockSize=" + blockSize + ", numberOfPartition=" + numberOfPartition + ", tocHash=" + Utils.ByteArrayToString(tocHash) + + "]"; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.put(ByteUtils.getBytesFromInt(MAGIC)); + buffer.putInt((int) blockSize.getBlockSize()); + buffer.put(tocHash); + buffer.putInt(numberOfPartition); + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUDataPartition.java new file mode 100644 index 0000000..6eae68e --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUDataPartition.java @@ -0,0 +1,17 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions; + +import de.mas.wiiu.jnus.entities.FST.FST; +import lombok.Getter; + +public class WiiUDataPartition extends WiiUPartition { + + @Getter private final FST fst; + + public WiiUDataPartition(WiiUPartition partition, FST fst) { + this.setFileSystemDescriptor(partition.getFileSystemDescriptor()); + this.setVolumeID(partition.getVolumeID()); + this.getVolumes().putAll(partition.getVolumes()); + this.fst = fst; + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUGMPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUGMPartition.java new file mode 100644 index 0000000..5adb3ae --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUGMPartition.java @@ -0,0 +1,18 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions; + +import lombok.Getter; + +public class WiiUGMPartition extends WiiUPartition { + @Getter private final byte[] rawTicket; + @Getter private final byte[] rawTMD; + @Getter private final byte[] rawCert; + + public WiiUGMPartition(WiiUPartition partition, byte[] rawTIK, byte[] rawTMD, byte[] rawCert) { + this.setFileSystemDescriptor(partition.getFileSystemDescriptor()); + this.setVolumeID(partition.getVolumeID()); + this.getVolumes().putAll(partition.getVolumes()); + this.rawCert = rawCert; + this.rawTMD = rawTMD; + this.rawTicket = rawTIK; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartition.java new file mode 100644 index 0000000..2129b37 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartition.java @@ -0,0 +1,89 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes.VolumeHeader; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInDiscBlocks; +import de.mas.wiiu.jnus.utils.blocksize.DiscBlockSize; +import lombok.Data; +import lombok.val; + +@Data +public class WiiUPartition { + public static final int LENGTH = 128; + private final Map volumes = new HashMap<>(); + private String volumeID; + private short fileSystemDescriptor; + + public static WiiUPartition parseData(WUDDiscReader reader, Optional discKey, long offset, DiscBlockSize blockSize) throws IOException { + byte[] rawBytes = new byte[0]; + if (!discKey.isPresent()) { + rawBytes = Arrays.copyOfRange(reader.readEncryptedToByteArray(offset, 0, LENGTH), 0, LENGTH); + } else { + // Hacky solution for not knowing the correct IV. + rawBytes = Arrays.copyOfRange(reader.readDecryptedToByteArray(offset - 0x10, 0, LENGTH, discKey.get(), null, true), 0x10, LENGTH + 0x10); + } + + if (rawBytes.length != LENGTH) { + throw new IOException("Failed to read WiiUPartition"); + } + + ByteBuffer buffer = ByteBuffer.wrap(rawBytes); + + byte[] strRaw = Arrays.copyOfRange(rawBytes, 0, 31); + for (int i = 0; i < strRaw.length; i++) { + if (strRaw[i] == '\0') { + strRaw = Arrays.copyOf(strRaw, i); + break; + } + } + + String volumeID = new String(strRaw, Charset.forName("ISO-8859-1")); + buffer.position(31); + int num = (int) buffer.get(); + + WiiUPartition partition = new WiiUPartition(); + for (int i = 0; i < num; i++) { + AddressInDiscBlocks discLbaAddress = new AddressInDiscBlocks(blockSize, + ByteUtils.getUnsingedIntFromBytes(rawBytes, 32 + (int) (i * 4)) & 0xFFFFFFFF); + VolumeHeader vh = VolumeHeader.parseData(reader, discLbaAddress.getAddressInBytes()); + partition.volumes.put(discLbaAddress, vh); + } + buffer.position(64); + short fileSystemDescriptor = buffer.getShort(); + + partition.setVolumeID(volumeID); + partition.setFileSystemDescriptor(fileSystemDescriptor); + + return partition; + } + + public long getSectionOffsetOnDefaultPartition() throws IOException { + if (getVolumes().size() != 1) { + throw new IOException("We have more or less than 1 volume header."); + } + return getVolumes().keySet().iterator().next().getAddressInBytes(); + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.put(volumeID.getBytes()); + buffer.position(31); + buffer.put((byte) volumes.size()); + for (val address : volumes.entrySet()) { + buffer.putInt((int) address.getKey().getValue()); + } + buffer.position(64); + buffer.putShort(fileSystemDescriptor); + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartitions.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartitions.java new file mode 100644 index 0000000..36584af --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/WiiUPartitions.java @@ -0,0 +1,136 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes.VolumeHeader; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.FSTUtils; +import de.mas.wiiu.jnus.utils.Utils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInDiscBlocks; +import de.mas.wiiu.jnus.utils.blocksize.DiscBlockSize; +import lombok.val; + +public class WiiUPartitions extends LinkedList { + private static final long serialVersionUID = -8822482411628791495L; + + public static final long LENGTH = 30720; + + public static final String WUD_TMD_FILENAME = "title.tmd"; + public static final String WUD_TICKET_FILENAME = "title.tik"; + public static final String WUD_CERT_FILENAME = "title.cert"; + + public static WiiUPartitions parseData(WUDDiscReader reader, Optional discKey, long curOffset, long numberOfPartitions, DiscBlockSize blockSize) + throws IOException { + WiiUPartitions partitions = new WiiUPartitions(); + + List tmp = new LinkedList<>(); + + for (int i = 0; i < numberOfPartitions; i++) { + tmp.add(WiiUPartition.parseData(reader, discKey, curOffset + (i * 128), blockSize)); + } + + Optional siPartitionOpt = tmp.stream().filter(e -> e.getVolumeID().startsWith("SI")).findFirst(); + + if (siPartitionOpt.isPresent()) { + WiiUPartition siPartition = siPartitionOpt.get(); + for (val entry : siPartition.getVolumes().entrySet()) { + val volumeAddress = entry.getKey(); + val volumeAddressInBytes = volumeAddress.getAddressInBytes(); + val volumeHeader = entry.getValue(); + byte[] fst = null; + if (!discKey.isPresent()) { + fst = reader.readEncryptedToByteArray(volumeAddressInBytes + volumeHeader.getFSTAddress().getAddressInBytes(), 0, + volumeHeader.getFSTSize()); + } else { + fst = reader.readDecryptedToByteArray(volumeAddressInBytes + volumeHeader.getFSTAddress().getAddressInBytes(), 0, volumeHeader.getFSTSize(), + discKey.get(), null, true); + } + + FST siFST = FST.parseData(fst, 0, volumeHeader.getBlockSize()); + + for (val child : siFST.getRootEntry().getDirChildren()) { + byte[] rawTIK = getFSTEntryAsByte(child.getFullPath() + '/' + WUD_TICKET_FILENAME, siFST, volumeAddress, volumeHeader, reader, discKey); + byte[] rawTMD = getFSTEntryAsByte(child.getFullPath() + '/' + WUD_TMD_FILENAME, siFST, volumeAddress, volumeHeader, reader, discKey); + byte[] rawCert = getFSTEntryAsByte(child.getFullPath() + '/' + WUD_CERT_FILENAME, siFST, volumeAddress, volumeHeader, reader, discKey); + + String partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08)); + WiiUPartition partition = tmp.stream().filter(p -> p.getVolumeID().startsWith(partitionName)).findAny() + .orElseThrow(() -> new IOException("Failed to find partition starting with " + partitionName)); + + WiiUGMPartition gmPartition = new WiiUGMPartition(partition, rawTIK, rawTMD, rawCert); + partitions.add(gmPartition); + } + } + + } + val nonGMPartitions = tmp.stream().filter(e -> !e.getVolumeID().startsWith("GM")).collect(Collectors.toList()); + for (val partition : nonGMPartitions) { + if (partition.getVolumes().size() != 1) { + throw new IOException("We can't handle more than one partion address yet."); + } + val volumeAddress = partition.getVolumes().keySet().iterator().next(); + VolumeHeader vh = VolumeHeader.parseData(reader, volumeAddress.getAddressInBytes()); + byte[] rawFST = null; + if (!discKey.isPresent()) { + rawFST = reader.readEncryptedToByteArray(volumeAddress.getAddressInBytes() + vh.getFSTAddress().getAddressInBytes(), 0, vh.getFSTSize()); + } else { + rawFST = reader.readDecryptedToByteArray(volumeAddress.getAddressInBytes() + vh.getFSTAddress().getAddressInBytes(), 0, vh.getFSTSize(), + discKey.get(), null, true); + } + + FST fst = FST.parseData(rawFST, 0, vh.getBlockSize()); + partitions.add(new WiiUDataPartition(partition, fst)); + } + + return partitions; + } + + private static byte[] getFSTEntryAsByte(String filePath, FST fst, AddressInDiscBlocks volumeAddress, VolumeHeader vh, WUDDiscReader discReader, + Optional discKey) throws IOException { + FileEntry entry = FSTUtils.getEntryByFullPath(fst.getRootEntry(), filePath).orElseThrow(() -> new FileNotFoundException(filePath + " was not found.")); + + SectionEntry info = entry.getSectionEntry(); + + long sectionOffsetOnDisc = volumeAddress.getAddressInBytes() + info.getAddress().getAddressInBytes(); + + if (!discKey.isPresent()) { + return discReader.readEncryptedToByteArray(sectionOffsetOnDisc, entry.getOffset(), (int) entry.getSize()); + } + + // Calculating the IV + ByteBuffer byteBuffer = ByteBuffer.allocate(0x10); + byteBuffer.position(0x08); + byte[] IV = byteBuffer.putLong(entry.getOffset() >> 16).array(); + + return discReader.readDecryptedToByteArray(sectionOffsetOnDisc, entry.getOffset(), (int) entry.getSize(), discKey.get(), IV, false); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("WiiUPartitions: " + System.lineSeparator()); + for (val p : this) { + sb.append("[" + p + "]" + System.lineSeparator()); + } + return sb.toString(); + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate((int) LENGTH); + for (int i = 0; i < size(); i++) { + buffer.put(get(i).getAsBytes()); + } + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArray.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArray.java new file mode 100644 index 0000000..eade646 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArray.java @@ -0,0 +1,23 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes; + +import java.io.IOException; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.Utils; +import lombok.Data; + +@Data +public class H3HashArray { + public static int LENGTH = 20; + private final byte[] H3HashArray; + + public static H3HashArray parseData(WUDDiscReader reader, long offset, long t_nLength) throws IOException { + return new H3HashArray(reader.readEncryptedToByteArray(offset, 0, t_nLength)); + } + + @Override + public String toString() { + return "H3HashArray [H3HashArray=" + Utils.ByteArrayToString(H3HashArray) + "]"; + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArrayList.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArrayList.java new file mode 100644 index 0000000..810ccaa --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/H3HashArrayList.java @@ -0,0 +1,33 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedList; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.ByteUtils; + +public class H3HashArrayList extends LinkedList { + private static final long serialVersionUID = -7607430979726338763L; + + public static H3HashArrayList parseData(WUDDiscReader reader, long offset, long numberOfH3HashArray, long h3HashArrayListSize) throws IOException { + byte[] h3Data = reader.readEncryptedToByteArray(offset, 0, h3HashArrayListSize); + + return parseData(h3Data, numberOfH3HashArray, h3HashArrayListSize); + } + + public static H3HashArrayList parseData(byte[] h3Data, long numberOfH3HashArray, long h3HashArrayListSize) throws IOException { + H3HashArrayList arrayList = new H3HashArrayList(); + for (int i = 1; i < numberOfH3HashArray; i++) { + long curOffset = ByteUtils.getUnsingedIntFromBytes(Arrays.copyOfRange(h3Data, i * 4, (i + 1) * 4), 0) & 0xFFFFFFFF; + long curEnd = h3HashArrayListSize; + if (i < numberOfH3HashArray - 1) { + // If it's not the last element, the end of our .h3 is the start of the next .h3 + curEnd = ByteUtils.getUnsingedIntFromBytes(Arrays.copyOfRange(h3Data, (i + 1) * 4, (i + 2) * 4), 0) & 0xFFFFFFFF; + } + arrayList.add(new H3HashArray(Arrays.copyOfRange(h3Data, (int) curOffset, (int) curEnd))); + } + + return arrayList; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/VolumeHeader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/VolumeHeader.java new file mode 100644 index 0000000..cebb0f0 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/content/partitions/volumes/VolumeHeader.java @@ -0,0 +1,132 @@ +package de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.Callable; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.ByteUtils; +import de.mas.wiiu.jnus.utils.blocksize.AddressInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.SizeInVolumeBlocks; +import de.mas.wiiu.jnus.utils.blocksize.VolumeBlockSize; +import lombok.Data; + +@Data +public class VolumeHeader { + public static final int MAGIC = 0xCC93A4F5; + + private VolumeBlockSize blockSize; + private SizeInVolumeBlocks volumeSize; + + private long h3HashArrayListSize; + private long numberOfH3HashArray; + + private long FSTSize; + private AddressInVolumeBlocks FSTAddress; + + private int FSTHashMode; + + private short encryptType; + + private short majorVersion; + + private short minorVersion; + + private short expiringMajorVersion; + + private H3HashArrayList h3HashArrayList; + + public static VolumeHeader parseData(WUDDiscReader reader, long offset) throws IOException { + + byte[] rawBytes = new byte[0]; + + rawBytes = reader.readEncryptedToByteArray(offset, 0, 40); + + long h3HashArrayListSize = ByteUtils.getUnsingedIntFromBytes(rawBytes, 12) & 0xFFFFFFFF; + long numberOfH3HashArray = ByteUtils.getUnsingedIntFromBytes(rawBytes, 16) & 0xFFFFFFFF; + + return parseData(rawBytes, () -> H3HashArrayList.parseData(reader, offset + 64, numberOfH3HashArray, h3HashArrayListSize)); + } + + public static VolumeHeader parseData(byte[] rawBytes) throws IOException { + if (rawBytes == null || rawBytes.length < 40) { + throw new IOException("Failed to read VolumeHeader"); + } + + long h3HashArrayListSize = ByteUtils.getUnsingedIntFromBytes(rawBytes, 12) & 0xFFFFFFFF; + long numberOfH3HashArray = ByteUtils.getUnsingedIntFromBytes(rawBytes, 16) & 0xFFFFFFFF; + + return parseData(rawBytes, + () -> H3HashArrayList.parseData(Arrays.copyOfRange(rawBytes, 64, (int) (64 + h3HashArrayListSize)), numberOfH3HashArray, h3HashArrayListSize)); + } + + private static VolumeHeader parseData(byte[] rawBytes, Callable h3HashSupplier) throws IOException { + if (rawBytes.length < 40) { + throw new IOException("Failed to read VolumeHeader"); + } + + VolumeHeader header = new VolumeHeader(); + + ByteBuffer buffer = ByteBuffer.wrap(rawBytes); + byte[] magicCompare = new byte[4]; + buffer.get(magicCompare); + if (!Arrays.equals(magicCompare, ByteUtils.getBytesFromInt(MAGIC))) { + throw new IOException("VolumeHeader MAGIC mismatch."); + } + + header.blockSize = new VolumeBlockSize(ByteUtils.getUnsingedIntFromBytes(rawBytes, 4) & 0xFFFFFFFF); + header.volumeSize = new SizeInVolumeBlocks(header.blockSize, ByteUtils.getUnsingedIntFromBytes(rawBytes, 8) & 0xFFFFFFFF); + header.h3HashArrayListSize = ByteUtils.getUnsingedIntFromBytes(rawBytes, 12) & 0xFFFFFFFF; + header.numberOfH3HashArray = ByteUtils.getUnsingedIntFromBytes(rawBytes, 16) & 0xFFFFFFFF; + header.FSTSize = ByteUtils.getUnsingedIntFromBytes(rawBytes, 20) & 0xFFFFFFFF; + header.FSTAddress = new AddressInVolumeBlocks(header.blockSize, ByteUtils.getUnsingedIntFromBytes(rawBytes, 24) & 0xFFFFFFFF); + header.FSTHashMode = ByteUtils.getByteFromBytes(rawBytes, 36); + header.encryptType = ByteUtils.getByteFromBytes(rawBytes, 37); + header.majorVersion = ByteUtils.getByteFromBytes(rawBytes, 38); + header.minorVersion = ByteUtils.getByteFromBytes(rawBytes, 39); + header.expiringMajorVersion = ByteUtils.getByteFromBytes(rawBytes, 40); + try { + header.h3HashArrayList = h3HashSupplier.call(); + } catch (Exception e) { + if (e instanceof IOException) { + throw (IOException) e; + } else { + // This should never happen. + throw new RuntimeException(e); + } + } + + return header; + } + + @Override + public String toString() { + return "VolumeHeader [blockSize=" + blockSize + ", volumeSize=" + volumeSize + ", h3HashArrayListSize=" + h3HashArrayListSize + ", numberOfH3HashArray=" + + numberOfH3HashArray + ", FSTSize=" + FSTSize + ", FSTAddress=" + FSTAddress + ", FSTHashMode=" + FSTHashMode + ", encryptType=" + encryptType + + ", majorVersion=" + majorVersion + ", minorVersion=" + minorVersion + ", expiringMajorVersion=" + expiringMajorVersion + ", byteSource=" + + ", h3HashArrayList=" + h3HashArrayList + "]"; + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate((int) blockSize.getBlockSize()); + buffer.put(ByteUtils.getBytesFromInt(MAGIC)); + buffer.putInt((int) blockSize.getBlockSize()); + buffer.putInt((int) volumeSize.getValue()); + buffer.putInt((int) h3HashArrayListSize); + buffer.putInt((int) numberOfH3HashArray); + buffer.putInt((int) FSTSize); + buffer.putInt((int) FSTAddress.getValue()); + buffer.put((byte) FSTHashMode); + buffer.put((byte) encryptType); + buffer.put((byte) majorVersion); + buffer.put((byte) minorVersion); + buffer.put((byte) expiringMajorVersion); + buffer.position(64); + if (h3HashArrayListSize > 0 || numberOfH3HashArray > 0) { + throw new IllegalArgumentException("Support for packing with h3 hashes is missing."); + } + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscHeader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscHeader.java new file mode 100644 index 0000000..bbf829c --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscHeader.java @@ -0,0 +1,49 @@ +package de.mas.wiiu.jnus.implementations.wud.header; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Optional; + +import de.mas.wiiu.jnus.implementations.wud.content.WiiUContentsInformation; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import lombok.Data; + +@Data +public class WiiUDiscHeader { + private static final long LENGTH = 131072L; + WiiUManufactorDiscID manufactorDiscID; + WiiUDiscID discID; + WiiUContentsInformation contentsInformation; + + public static WiiUDiscHeader parseData(WUDDiscReader reader, Optional discKey) throws IOException { + WiiUDiscHeader header = new WiiUDiscHeader(); + long curOffset = 0; + header.setManufactorDiscID(WiiUManufactorDiscID.parseData(reader, 0)); + curOffset += WiiUManufactorDiscID.LENGTH; + header.setDiscID(WiiUDiscID.parseData(reader, curOffset)); + curOffset += WiiUDiscID.LENGTH; + header.setContentsInformation(WiiUContentsInformation.parseData(reader, discKey, curOffset)); + + curOffset += WiiUContentsInformation.LENGTH; + + if (curOffset != LENGTH) { + throw new IOException("Length mismatch"); + } + + return header; + } + + @Override + public String toString() { + return "WiiUDiscHeader [manufactorDiscID=" + manufactorDiscID + ", discID=" + discID + ", contentsInformation=" + contentsInformation + "]"; + } + + public byte[] getAsBytes() throws IOException { + ByteBuffer buffer = ByteBuffer.allocate((int) LENGTH); + buffer.put(manufactorDiscID.getAsBytes()); + buffer.put(discID.getAsBytes()); + buffer.put(contentsInformation.getAsBytes()); + return buffer.array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscID.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscID.java new file mode 100644 index 0000000..e03af78 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUDiscID.java @@ -0,0 +1,59 @@ +package de.mas.wiiu.jnus.implementations.wud.header; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.ByteUtils; +import lombok.Data; + +@Data +public class WiiUDiscID { + public static final int MAGIC = 0xCC549EB9; + public static final int LENGTH = 32768; + + private final byte majorVersion; + private final byte minorVersion; + private final String footprint; + + public static WiiUDiscID parseData(WUDDiscReader reader, long offset) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + if (reader.readEncryptedToStream(baos, offset, LENGTH) != LENGTH) { + throw new IOException("Failed to read DiscId"); + } + byte[] rawBytes = baos.toByteArray(); + ByteBuffer buffer = ByteBuffer.wrap(rawBytes); + byte[] magicCompare = new byte[4]; + buffer.get(magicCompare); + if (!Arrays.equals(magicCompare, ByteUtils.getBytesFromInt(MAGIC))) { + throw new IOException("DiscId MAGIC mismatch."); + } + byte majorVersion = buffer.get(); + byte minorVersion = buffer.get(); + + byte[] strRaw = Arrays.copyOfRange(rawBytes, 32, 32 + 64); + for (int i = 0; i < strRaw.length; i++) { + if (strRaw[i] == '\0') { + strRaw = Arrays.copyOf(strRaw, i); + break; + } + } + + String footprint = new String(strRaw, Charset.forName("ISO-8859-1")); + + return new WiiUDiscID(majorVersion, minorVersion, footprint); + } + + public byte[] getAsBytes() { + ByteBuffer buffer = ByteBuffer.allocate(LENGTH); + buffer.put(ByteUtils.getBytesFromInt(MAGIC)); + buffer.put(majorVersion); + buffer.put(minorVersion); + buffer.put(footprint.getBytes()); + return buffer.array(); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUManufactorDiscID.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUManufactorDiscID.java new file mode 100644 index 0000000..0fa7472 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/header/WiiUManufactorDiscID.java @@ -0,0 +1,33 @@ +package de.mas.wiiu.jnus.implementations.wud.header; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.Utils; +import lombok.Data; + +@Data +public class WiiUManufactorDiscID { + public final static long LENGTH = 65536; + private final byte[] data; + + static WiiUManufactorDiscID parseData(WUDDiscReader reader, long offset) throws IOException { + byte[] data = reader.readEncryptedToByteArray(offset, 0, LENGTH); + if (data.length != LENGTH) { + throw new IOException("Failed to read ManufactorDiscID"); + } + + return new WiiUManufactorDiscID(data); + } + + @Override + public String toString() { + return "WiiUManufactorDiscID [data=" + Utils.ByteArrayToString(data) + "]"; + } + + public byte[] getAsBytes() { + return ByteBuffer.wrap(data).array(); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDDataPartition.java deleted file mode 100644 index 92ab1a7..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDDataPartition.java +++ /dev/null @@ -1,30 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud.parser; - -import de.mas.wiiu.jnus.entities.fst.FST; -import lombok.Getter; - -public class WUDDataPartition extends WUDPartition { - @Getter private final FST FST; - - public WUDDataPartition(String partitionName, long partitionOffset, FST curFST) { - super(partitionName, partitionOffset); - this.FST = curFST; - } - -} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java deleted file mode 100644 index 3b41589..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java +++ /dev/null @@ -1,46 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud.parser; - -import java.text.ParseException; - -import de.mas.wiiu.jnus.entities.TMD; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@Data -@EqualsAndHashCode(callSuper = true) -public class WUDGamePartition extends WUDPartition { - - private final GamePartitionHeader partitionHeader; - - private final TMD tmd; - private final byte[] rawTMD; - private final byte[] rawCert; - private final byte[] rawTicket; - - public WUDGamePartition(String partitionName, long partitionOffset, GamePartitionHeader partitionHeader, byte[] rawTMD, byte[] rawCert, byte[] rawTicket) - throws ParseException { - super(partitionName, partitionOffset); - this.partitionHeader = partitionHeader; - this.rawTMD = rawTMD; - this.tmd = TMD.parseTMD(rawTMD); - this.rawCert = rawCert; - this.rawTicket = rawTicket; - } -} \ No newline at end of file diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java deleted file mode 100644 index ab1e3ab..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java +++ /dev/null @@ -1,40 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud.parser; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; -import lombok.Data; -import lombok.Getter; - -@Data -public class WUDInfo { - @Getter private final byte[] titleKey; - private final WUDDiscReader WUDDiscReader; - private final List partitions = new ArrayList<>(); - - public List getGamePartitions() { - return partitions.stream().filter(p -> p instanceof WUDGamePartition).map(p -> (WUDGamePartition) p).collect(Collectors.toList()); - } - - public List getDataPartitions() { - return partitions.stream().filter(p -> p instanceof WUDDataPartition).map(p -> ((WUDDataPartition) p)).collect(Collectors.toList()); - } -} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java deleted file mode 100644 index c35c120..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java +++ /dev/null @@ -1,233 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud.parser; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; -import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; -import de.mas.wiiu.jnus.utils.ByteUtils; -import de.mas.wiiu.jnus.utils.FSTUtils; -import de.mas.wiiu.jnus.utils.Utils; -import lombok.val; -import lombok.extern.java.Log; - -@Log -// TODO: reduce magic numbers -public final class WUDInfoParser { - public static byte[] DECRYPTED_AREA_SIGNATURE = new byte[] { (byte) 0xCC, (byte) 0xA6, (byte) 0xE6, 0x7B }; - public static byte[] PARTITION_START_SIGNATURE = new byte[] { (byte) 0xCC, (byte) 0x93, (byte) 0xA4, (byte) 0xF5 }; - public static byte[] PARTITION_FILE_TABLE_SIGNATURE = new byte[] { 0x46, 0x53, 0x54, 0x00 }; // "FST" - public final static int SECTOR_SIZE = 0x8000; - public final static int PARTITION_TOC_OFFSET = 0x800; - public final static int PARTITION_TOC_ENTRY_SIZE = 0x80; - - public static final String WUD_TMD_FILENAME = "title.tmd"; - public static final String WUD_TICKET_FILENAME = "title.tik"; - public static final String WUD_CERT_FILENAME = "title.cert"; - - private WUDInfoParser() { - // - } - - public static WUDInfo createAndLoad(WUDDiscReader discReader, byte[] titleKey) throws IOException, ParseException { - WUDInfo result = new WUDInfo(titleKey, discReader); - - byte[] PartitionTocBlock = readFromDisc(result, Settings.WIIU_DECRYPTED_AREA_OFFSET, SECTOR_SIZE); - - // verify DiscKey before proceeding - if (!Arrays.equals(Arrays.copyOfRange(PartitionTocBlock, 0, 4), DECRYPTED_AREA_SIGNATURE)) { - // log.info("Decryption of PartitionTocBlock failed"); - throw new ParseException("Decryption of PartitionTocBlock failed", 0); - } - - result.getPartitions().addAll(parsePartitions(result, PartitionTocBlock)); - return result; - } - - private static Collection parsePartitions(WUDInfo wudInfo, byte[] partitionTocBlock) throws IOException, ParseException { - ByteBuffer buffer = ByteBuffer.allocate(partitionTocBlock.length); - - buffer.order(ByteOrder.BIG_ENDIAN); - buffer.put(partitionTocBlock); - buffer.position(0); - - int partitionCount = (int) ByteUtils.getUnsingedIntFromBytes(partitionTocBlock, 0x1C, ByteOrder.BIG_ENDIAN); - - Map internalPartitions = new HashMap<>(); - - // populate partition information from decrypted TOC - for (int i = 0; i < partitionCount; i++) { - int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE)); - byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset + 0x19); - int j = 0; - for (j = 0; j < partitionIdentifier.length; j++) { - if (partitionIdentifier[j] == 0) { - break; - } - } - String partitionName = new String(Arrays.copyOfRange(partitionIdentifier, 0, j)); - - // calculate partition offset from decrypted TOC - long offsetInSector = ByteUtils.getUnsingedIntFromBytes(partitionTocBlock, (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE) + 0x20), - ByteOrder.BIG_ENDIAN); - - long partitionOffset = (offsetInSector * (long) SECTOR_SIZE); - - internalPartitions.put(partitionName, partitionOffset); - } - - Map partitionsResult = new HashMap<>(); - - val siPartitionOpt = internalPartitions.entrySet().stream().filter(e -> e.getKey().startsWith("SI")).findFirst(); - if (siPartitionOpt.isPresent()) { - val siPartitionPair = siPartitionOpt.orElseThrow(() -> new ParseException("SI partition not found.", 0)); - - // siPartition - long siPartitionOffset = siPartitionPair.getValue(); - - byte[] partitionHeaderData = readFromDisc(wudInfo, false, siPartitionOffset, 0x20); - if (!Arrays.equals(Arrays.copyOf(partitionHeaderData, 0x4), PARTITION_START_SIGNATURE)) { - throw new ParseException("Failed to get the partition data of the SI partition.", 0); - } - - long headerSize = ByteUtils.getUnsingedIntFromBytes(partitionHeaderData, 0x04); - - long absoluteFSTOffset = siPartitionOffset + headerSize; - long FSTSize = ByteUtils.getUnsingedIntFromBytes(partitionHeaderData, 0x14); - - byte[] fileTableBlock = readFromDisc(wudInfo, absoluteFSTOffset, FSTSize); - - if (!Arrays.equals(Arrays.copyOfRange(fileTableBlock, 0, 4), WUDInfoParser.PARTITION_FILE_TABLE_SIGNATURE)) { - log.info("FST Decrpytion failed"); - throw new ParseException("Failed to decrypt the FST of the SI partition.", 0); - } - - FST siFST = FST.parseFST(fileTableBlock); - - for (val dirChilden : siFST.getRoot().getDirChildren()) { - // The SI partition contains the tmd, cert and tik for every GM partition. - byte[] rawTIK = getFSTEntryAsByte(dirChilden.getFullPath() + '/' + WUD_TICKET_FILENAME, siPartitionOffset, headerSize, siFST, - wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - byte[] rawTMD = getFSTEntryAsByte(dirChilden.getFullPath() + '/' + WUD_TMD_FILENAME, siPartitionOffset, headerSize, siFST, - wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - byte[] rawCert = getFSTEntryAsByte(dirChilden.getFullPath() + '/' + WUD_CERT_FILENAME, siPartitionOffset, headerSize, siFST, - wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - - String partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08)); - - val curPartitionOpt = internalPartitions.entrySet().stream().filter(e -> e.getKey().startsWith(partitionName)).findFirst(); - val curPartitionPair = curPartitionOpt.orElseThrow(() -> new ParseException("partition not found.", 0)); - long curPartitionOffset = curPartitionPair.getValue(); - - byte[] curPartitionHeaderMeta = readFromDisc(wudInfo, false, curPartitionOffset, 0x20); - - if (!Arrays.equals(Arrays.copyOf(curPartitionHeaderMeta, 0x4), PARTITION_START_SIGNATURE)) { - throw new ParseException("Failed to decrypt the SI partition.", 0); - } - - long curHeaderSize = ByteUtils.getUnsingedIntFromBytes(curPartitionHeaderMeta, 0x04); - - byte[] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(curPartitionOffset, 0, curHeaderSize); - - GamePartitionHeader partitionHeader = GamePartitionHeader.parseHeader(header); - - WUDGamePartition curPartition = new WUDGamePartition(curPartitionPair.getKey(), curPartitionOffset + curHeaderSize, partitionHeader, rawTMD, - rawCert, rawTIK); - - partitionsResult.put(curPartitionPair.getKey(), curPartition); - } - } - - val dataPartitions = internalPartitions.entrySet().stream().filter(e -> !e.getKey().startsWith("GM")).collect(Collectors.toList()); - for (val dataPartition : dataPartitions) { - String curPartionName = dataPartition.getKey(); - long partitionOffset = dataPartition.getValue(); - - byte[] partitionHeaderData = readFromDisc(wudInfo, false, partitionOffset, 0x20); - - if (!Arrays.equals(Arrays.copyOf(partitionHeaderData, 0x4), PARTITION_START_SIGNATURE)) { - throw new ParseException("Failed to decrypt the " + curPartionName + " partition.", 0); - } - - long headerSize = ByteUtils.getUnsingedIntFromBytes(partitionHeaderData, 0x04); - - long absoluteFSTOffset = partitionOffset + headerSize; - long FSTSize = ByteUtils.getUnsingedIntFromBytes(partitionHeaderData, 0x14); - - byte[] curFileTableBlock = readFromDisc(wudInfo, absoluteFSTOffset, FSTSize); - - if (!Arrays.equals(Arrays.copyOfRange(curFileTableBlock, 0, 4), WUDInfoParser.PARTITION_FILE_TABLE_SIGNATURE)) { - throw new IOException("FST Decrpytion failed"); - } - - FST curFST = FST.parseFST(curFileTableBlock); - - WUDDataPartition curDataPartition = new WUDDataPartition(curPartionName, partitionOffset + headerSize, curFST); - - partitionsResult.put(curPartionName, curDataPartition); - } - - return partitionsResult.values(); - } - - private static byte[] readFromDisc(WUDInfo wudInfo, long offset, long size) throws IOException { - return readFromDisc(wudInfo, wudInfo.getTitleKey() != null, offset, size); - } - - private static byte[] readFromDisc(WUDInfo wudInfo, boolean decrypt, long offset, long size) throws IOException { - if (!decrypt) { - return wudInfo.getWUDDiscReader().readEncryptedToByteArray(offset, 0, size); - } else { - return wudInfo.getWUDDiscReader().readDecryptedToByteArray(offset, 0, size, wudInfo.getTitleKey(), null, true); - } - } - - private static byte[] getFSTEntryAsByte(String filePath, long partitionOffset, long headerSize, FST fst, WUDDiscReader discReader, byte[] key) - throws IOException { - FSTEntry entry = FSTUtils.getEntryByFullPath(fst.getRoot(), filePath).orElseThrow(() -> new FileNotFoundException(filePath + " was not found.")); - - ContentFSTInfo info = fst.getContentFSTInfos().get((int) entry.getContentIndex()); - - if (key == null) { - return discReader.readEncryptedToByteArray(headerSize + partitionOffset + (long) info.getOffset(), entry.getFileOffset(), - (int) entry.getFileSize()); - } - - // Calculating the IV - ByteBuffer byteBuffer = ByteBuffer.allocate(0x10); - byteBuffer.position(0x08); - byte[] IV = byteBuffer.putLong(entry.getFileOffset() >> 16).array(); - - return discReader.readDecryptedToByteArray(headerSize + partitionOffset + info.getOffset(), entry.getFileOffset(), (int) entry.getFileSize(), key, IV, - false); - } - -} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartition.java deleted file mode 100644 index 3c7fad0..0000000 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartition.java +++ /dev/null @@ -1,25 +0,0 @@ -/**************************************************************************** - * Copyright (C) 2016-2019 Maschell - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - ****************************************************************************/ -package de.mas.wiiu.jnus.implementations.wud.parser; - -import lombok.Data; - -@Data -public abstract class WUDPartition { - private final String partitionName; - private final long partitionOffset; -} diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java index 3439d13..35b1587 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java @@ -72,8 +72,7 @@ public abstract class WUDDiscReader { return in; } - public InputStream readDecryptedToStream(long offset, long fileOffset, long size, byte[] key, byte[] IV, - boolean useFixedIV) throws IOException { + public InputStream readDecryptedToStream(long offset, long fileOffset, long size, byte[] key, byte[] IV, boolean useFixedIV) throws IOException { PipedOutputStream out = new PipedOutputStream(); PipedInputStreamWithException in = new PipedInputStreamWithException(out, 0x8000); diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadDataPartition.java index e2797d6..55b92a8 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadDataPartition.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadDataPartition.java @@ -1,10 +1,10 @@ package de.mas.wiiu.jnus.implementations.wud.wumad; -import de.mas.wiiu.jnus.entities.fst.FST; +import de.mas.wiiu.jnus.entities.FST.FST; import lombok.Getter; public class WumadDataPartition extends WumadPartition { - + @Getter private final FST FST; public WumadDataPartition(String partitionName, FST fst) { diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadGamePartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadGamePartition.java index 947e2a0..01777fa 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadGamePartition.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadGamePartition.java @@ -2,27 +2,26 @@ package de.mas.wiiu.jnus.implementations.wud.wumad; import java.text.ParseException; -import de.mas.wiiu.jnus.entities.TMD; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; +import de.mas.wiiu.jnus.entities.TMD.TitleMetaData; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes.VolumeHeader; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) public class WumadGamePartition extends WumadPartition { - private final GamePartitionHeader partitionHeader; + private final VolumeHeader partitionHeader; - private final TMD tmd; + private final TitleMetaData tmd; private final byte[] rawTMD; private final byte[] rawCert; private final byte[] rawTicket; - public WumadGamePartition(String partitionName, GamePartitionHeader partitionHeader, byte[] rawTMD, byte[] rawCert, byte[] rawTicket) - throws ParseException { + public WumadGamePartition(String partitionName, VolumeHeader partitionHeader, byte[] rawTMD, byte[] rawCert, byte[] rawTicket) throws ParseException { super(partitionName); this.partitionHeader = partitionHeader; this.rawTMD = rawTMD; - this.tmd = TMD.parseTMD(rawTMD); + this.tmd = TitleMetaData.parseTMD(rawTMD); this.rawCert = rawCert; this.rawTicket = rawTicket; } diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadParser.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadParser.java index 5789a91..93ae91c 100644 --- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadParser.java +++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/wumad/WumadParser.java @@ -34,9 +34,10 @@ import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; -import de.mas.wiiu.jnus.implementations.wud.GamePartitionHeader; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.implementations.wud.content.partitions.volumes.VolumeHeader; import de.mas.wiiu.jnus.utils.FSTUtils; import de.mas.wiiu.jnus.utils.StreamUtils; import lombok.val; @@ -61,15 +62,15 @@ public class WumadParser { Map> allPartitions = zipFile.stream().filter(e -> e.getName().startsWith("p")) .collect(Collectors.groupingBy(e -> e.getName().substring(1, 3))); - Map gamepartitions = new HashMap<>(); + Map gamepartitions = new HashMap<>(); // If we have a SI partition, let parse the FST to get all game partitions. ZipEntry siFST = zipFile.getEntry(SI_FST_FILENAME); if (siFST != null) { byte[] fstBytes = StreamUtils.getBytesFromStream(zipFile.getInputStream(siFST), (int) siFST.getSize()); - FST parsedFST = FST.parseFST(fstBytes); - gamepartitions.putAll(parsedFST.getRoot().getDirChildren().stream().collect(Collectors.toMap(e -> e.getFilename(), e -> e))); + FST parsedFST = FST.parseData(fstBytes); + gamepartitions.putAll(parsedFST.getRootEntry().getDirChildren().stream().collect(Collectors.toMap(e -> e.getName(), e -> e))); } // process all game partitions. Remove the partitions from the "all partitions" list on success. @@ -87,7 +88,7 @@ public class WumadParser { byte[] header = StreamUtils.getBytesFromStream(zipFile.getInputStream(headerEntry), (int) headerEntry.getSize()); - WumadGamePartition curPartition = new WumadGamePartition(e.getKey(), GamePartitionHeader.parseHeader(header), rawTMD, rawCert, rawTIK); + WumadGamePartition curPartition = new WumadGamePartition(e.getKey(), VolumeHeader.parseData(header), rawTMD, rawCert, rawTIK); result.getPartitions().add(curPartition); allPartitions.remove(e.getKey()); @@ -99,7 +100,7 @@ public class WumadParser { byte[] fstBytes = StreamUtils.getBytesFromStream(zipFile.getInputStream(fstEntry), (int) fstEntry.getSize()); - FST parsedFST = FST.parseFST(fstBytes); + FST parsedFST = FST.parseData(fstBytes); WumadDataPartition curPartition = new WumadDataPartition(e.getKey(), parsedFST); @@ -115,16 +116,16 @@ public class WumadParser { return result; } - private static Optional getFSTEntryAsByte(String filePath, FSTEntry dirRoot, ZipFile zipFile, ZipEntry data) throws IOException { - Optional entryOpt = FSTUtils.getEntryByFullPath(dirRoot, filePath); + private static Optional getFSTEntryAsByte(String filePath, DirectoryEntry dirRoot, ZipFile zipFile, ZipEntry data) throws IOException { + Optional entryOpt = FSTUtils.getEntryByFullPath(dirRoot, filePath); if (!entryOpt.isPresent()) { return Optional.empty(); } - FSTEntry entry = entryOpt.get(); + FileEntry entry = entryOpt.get(); InputStream in = zipFile.getInputStream(data); - StreamUtils.skipExactly(in, entry.getFileOffset()); - return Optional.of(StreamUtils.getBytesFromStream(in, (int) entry.getFileSize())); + StreamUtils.skipExactly(in, entry.getOffset()); + return Optional.of(StreamUtils.getBytesFromStream(in, (int) entry.getSize())); } } diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java index f6c808d..456b4c6 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java @@ -8,13 +8,19 @@ public interface ContentDecryptor { /** * - * @param in InputStream of the Encrypted Data with hashed - * @param out OutputStream of the decrypted data with hashes - * @param offset absolute offset in this Content stream - * @param size size of the payload that will be written to the outputstream - * @param payloadOffset relative offset to the start of the inputstream - * @param h3_hashes level 3 hashes of the content file - * @return + * @param in + * InputStream of the Encrypted Data with hashed + * @param out + * OutputStream of the decrypted data with hashes + * @param offset + * absolute offset in this Content stream + * @param size + * size of the payload that will be written to the outputstream + * @param payloadOffset + * relative offset to the start of the inputstream + * @param h3_hashes + * level 3 hashes of the content file + * @return * @throws IOException */ long readDecryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] h3_hashes) throws IOException; diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java index d7f451a..a71321f 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java @@ -10,6 +10,7 @@ public interface ContentEncryptor { long readEncryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset) throws IOException; - long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV, IVCache ivcache) throws IOException; + long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV, IVCache ivcache) + throws IOException; } diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java b/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java index f5fdae8..3f7ee58 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java @@ -22,19 +22,20 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PipedOutputStream; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.RootEntry; import de.mas.wiiu.jnus.utils.PipedInputStreamWithException; public interface FSTDataProvider { public String getName(); - public FSTEntry getRoot(); + public RootEntry getRoot(); - default public byte[] readFile(FSTEntry entry) throws IOException { - return readFile(entry, 0, entry.getFileSize()); + default public byte[] readFile(FileEntry entry) throws IOException { + return readFile(entry, 0, entry.getSize()); } - default public byte[] readFile(FSTEntry entry, long offset, long size) throws IOException { + default public byte[] readFile(FileEntry entry, long offset, long size) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); readFileToStream(out, entry, offset, size); @@ -42,11 +43,11 @@ public interface FSTDataProvider { return out.toByteArray(); } - default public InputStream readFileAsStream(FSTEntry entry) throws IOException { - return readFileAsStream(entry, 0, entry.getFileSize()); + default public InputStream readFileAsStream(FileEntry entry) throws IOException { + return readFileAsStream(entry, 0, entry.getSize()); } - default public InputStream readFileAsStream(FSTEntry entry, long offset, long size) throws IOException { + default public InputStream readFileAsStream(FileEntry entry, long offset, long size) throws IOException { PipedOutputStream out = new PipedOutputStream(); PipedInputStreamWithException in = new PipedInputStreamWithException(out, 0x10000); @@ -57,24 +58,24 @@ public interface FSTDataProvider { } catch (Exception e) { in.throwException(e); try { - out.close(); - } catch (IOException e1) { - e1.printStackTrace(); - } + out.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } } }).start(); return in; } - default public long readFileToStream(OutputStream out, FSTEntry entry) throws IOException { - return readFileToStream(out, entry, 0, entry.getFileSize()); + default public long readFileToStream(OutputStream out, FileEntry entry) throws IOException { + return readFileToStream(out, entry, 0, entry.getSize()); } - default public long readFileToStream(OutputStream out, FSTEntry entry, long offset) throws IOException { - return readFileToStream(out, entry, offset, entry.getFileSize()); + default public long readFileToStream(OutputStream out, FileEntry entry, long offset) throws IOException { + return readFileToStream(out, entry, offset, entry.getSize()); } - public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException; + public long readFileToStream(OutputStream out, FileEntry entry, long offset, long size) throws IOException; } diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java index 3e8922c..c390b38 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java @@ -5,7 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.utils.StreamUtils; public interface NUSDataProcessor { @@ -50,7 +50,7 @@ public interface NUSDataProcessor { default public byte[] readDecryptedContent(Content c, long offset, long size) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); long len = readDecryptedContentToStream(out, c, offset, size); - if(len < 0) { + if (len < 0) { return new byte[0]; } return out.toByteArray(); @@ -71,7 +71,7 @@ public interface NUSDataProcessor { } default public long readDecryptedContentToStream(OutputStream out, Content c, long offset, long size) throws IOException { - InputStream in = readDecryptedContentAsStream(c, offset, size); + InputStream in = readDecryptedContentAsStream(c, offset, size); return StreamUtils.saveInputStreamToOutputStream(in, out, size); } @@ -83,7 +83,7 @@ public interface NUSDataProcessor { ByteArrayOutputStream out = new ByteArrayOutputStream(); long len = readPlainDecryptedContentToStream(out, c, offset, size, forceCheckHash); - if(len < 0) { + if (len < 0) { return new byte[0]; } diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java index f4573be..06b2a8c 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java @@ -21,8 +21,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Optional; -import de.mas.wiiu.jnus.entities.content.Content; -import de.mas.wiiu.jnus.entities.fst.FST; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.utils.StreamUtils; public interface NUSDataProvider { diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java b/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java index b1f7f4b..f512a9c 100644 --- a/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java +++ b/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java @@ -3,14 +3,12 @@ package de.mas.wiiu.jnus.interfaces; import java.util.Objects; import java.util.function.Function; - @FunctionalInterface public interface TriFunction { R apply(A a, B b, C c); - default TriFunction andThen( - Function after) { + default TriFunction andThen(Function after) { Objects.requireNonNull(after); return (A a, B b, C c) -> after.apply(apply(a, b, c)); } diff --git a/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java index 202444a..5ae6d10 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java @@ -99,7 +99,7 @@ public final class ByteUtils { ByteBuffer.allocate(2).putShort(value).get(result); return result; } - + public static short getByteFromBytes(byte[] input, int offset) { ByteBuffer buffer = ByteBuffer.allocate(2).put(Arrays.copyOfRange(input, offset, offset + 1)).order(ByteOrder.BIG_ENDIAN); buffer.position(0); diff --git a/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java index 5aa43aa..1c39d80 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java @@ -24,7 +24,7 @@ import java.util.Arrays; import java.util.Optional; import de.mas.wiiu.jnus.Settings; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProvider; import lombok.NonNull; import lombok.extern.java.Log; diff --git a/src/main/java/de/mas/wiiu/jnus/utils/FSTUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/FSTUtils.java index cc4db68..589c83d 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/FSTUtils.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/FSTUtils.java @@ -25,20 +25,22 @@ import java.util.stream.Stream; import org.apache.commons.io.FilenameUtils; -import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; -import de.mas.wiiu.jnus.entities.fst.FST; -import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.entities.FST.FST; +import de.mas.wiiu.jnus.entities.FST.nodeentry.DirectoryEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.FileEntry; +import de.mas.wiiu.jnus.entities.FST.nodeentry.NodeEntry; +import de.mas.wiiu.jnus.entities.FST.sectionentry.SectionEntry; import lombok.val; public class FSTUtils { - public static Optional getFSTEntryByFullPath(FSTEntry root, String givenFullPath) { + public static Optional getFSTEntryByFullPath(DirectoryEntry root, String givenFullPath) { String fullPath = givenFullPath.replace(File.separator, "/"); if (!fullPath.startsWith("/")) { fullPath = "/" + fullPath; } String dirPath = FilenameUtils.getFullPathNoEndSeparator(fullPath); - Optional pathOpt = Optional.of(root); + Optional pathOpt = Optional.of(root); if (!dirPath.equals("/")) { pathOpt = getFileEntryDir(root, dirPath); } @@ -48,7 +50,7 @@ public class FSTUtils { return pathOpt.flatMap(e -> e.getChildren().stream().filter(c -> c.getFullPath().equals(path)).findAny()); } - public static Optional getFileEntryDir(FSTEntry curEntry, String string) { + public static Optional getFileEntryDir(DirectoryEntry curEntry, String string) { string = string.replace(File.separator, "/"); // We add the "/" at the end so we don't get false results when using the "startWith" function. @@ -71,15 +73,15 @@ public class FSTUtils { return Optional.empty(); } - public static Optional getEntryByFullPath(FSTEntry root, String filePath) { - for (FSTEntry cur : root.getFileChildren()) { + public static Optional getEntryByFullPath(DirectoryEntry root, String filePath) { + for (FileEntry cur : root.getFileChildren()) { if (cur.getFullPath().equals(filePath)) { return Optional.of(cur); } } - for (FSTEntry cur : root.getDirChildren()) { - Optional res = getEntryByFullPath(cur, filePath); + for (DirectoryEntry cur : root.getDirChildren()) { + Optional res = getEntryByFullPath(cur, filePath); if (res.isPresent()) { return res; } @@ -87,75 +89,75 @@ public class FSTUtils { return Optional.empty(); } - public static Optional getChildOfDirectory(FSTEntry root, String filename) { - for (FSTEntry cur : root.getChildren()) { - if (cur.getFilename().equalsIgnoreCase(filename)) { + public static Optional getChildOfDirectory(DirectoryEntry root, String filename) { + for (NodeEntry cur : root.getChildren()) { + if (cur.getName().equalsIgnoreCase(filename)) { return Optional.of(cur); } } return Optional.empty(); } - public static List getFSTEntriesByRegEx(FSTEntry root, String string) { + public static List getFSTEntriesByRegEx(DirectoryEntry root, String string) { return getFSTEntriesByRegEx(root, string, false); } - public static List getFSTEntriesByRegEx(FSTEntry entry, String regEx, boolean allowNotInPackage) { + public static List getFSTEntriesByRegEx(DirectoryEntry entry, String regEx, boolean allowNotInPackage) { Pattern p = Pattern.compile(regEx); return getFSTEntriesByRegExStream(entry, p, allowNotInPackage).collect(Collectors.toList()); } - private static Stream getFSTEntriesByRegExStream(FSTEntry entry, Pattern p, boolean allowNotInPackage) { + private static Stream getFSTEntriesByRegExStream(DirectoryEntry entry, Pattern p, boolean allowNotInPackage) { return entry.getChildren().stream()// - .filter(e -> allowNotInPackage || !e.isNotInPackage()) // + .filter(e -> allowNotInPackage || !e.isLink()) // .flatMap(e -> { - if (!e.isDir()) { + if (!e.isDirectory()) { if (p.matcher(e.getFullPath()).matches()) { - return Stream.of(e); + return Stream.of((FileEntry) e); } else { return Stream.empty(); } } - return getFSTEntriesByRegExStream(e, p, allowNotInPackage); + return getFSTEntriesByRegExStream((DirectoryEntry) e, p, allowNotInPackage); }); } - public static Optional getFSTInfoForContent(FST fst, short contentIndex) { - return fst.getContentFSTInfos().entrySet().stream().filter(e -> e.getKey().shortValue() == contentIndex).map(e -> e.getValue()).findAny(); + public static Optional getSectionEntryForIndex(FST fst, short contentIndex) { + return fst.getSectionEntries().stream().filter(e -> e.getSectionNumber() == contentIndex).findAny(); } - public static List getFSTEntriesByContentIndex(FSTEntry entry, short index) { + public static List getFSTEntriesByContentIndex(DirectoryEntry entry, short index) { return getFSTEntriesByContentIndexAsStream(entry, index).collect(Collectors.toList()); } - public static Stream getFSTEntriesByContentIndexAsStream(FSTEntry entry, short index) { + public static Stream getFSTEntriesByContentIndexAsStream(DirectoryEntry entry, short index) { return entry.getChildren().stream()// .flatMap(e -> { - if (!e.isDir()) { - if (e.getContentIndex() == index) { + if (!e.isDirectory()) { + if (e.getSectionEntry().getSectionNumber() == index) { return Stream.of(e); } return Stream.empty(); } - return getFSTEntriesByContentIndexAsStream(e, index); + return getFSTEntriesByContentIndexAsStream((DirectoryEntry) e, index); }); } /** * Does not include entries that are not in the package.. */ - public static Stream getAllFSTEntryChildrenAsStream(FSTEntry root) { + public static Stream getAllFSTEntryChildrenAsStream(DirectoryEntry root) { return getAllFSTEntryChildrenAsStream(root, false); } - public static Stream getAllFSTEntryChildrenAsStream(FSTEntry root, boolean allowNotInPackage) { + public static Stream getAllFSTEntryChildrenAsStream(DirectoryEntry root, boolean allowNotInPackage) { return root.getChildren().stream() // - .filter(e -> allowNotInPackage || !e.isNotInPackage()) // + .filter(e -> allowNotInPackage || !e.isLink()) // .flatMap(e -> { - if (!e.isDir()) { + if (!e.isDirectory()) { return Stream.of(e); } - return getAllFSTEntryChildrenAsStream(e, allowNotInPackage); + return getAllFSTEntryChildrenAsStream((DirectoryEntry) e, allowNotInPackage); }); } } diff --git a/src/main/java/de/mas/wiiu/jnus/utils/Utils.java b/src/main/java/de/mas/wiiu/jnus/utils/Utils.java index 5cf3879..7256c07 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/Utils.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/Utils.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URL; -import java.net.URLConnection; import java.util.Arrays; import java.util.logging.Level; import java.util.logging.LogManager; @@ -266,5 +265,4 @@ public final class Utils { return null; } - } diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInBlocks.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInBlocks.java new file mode 100644 index 0000000..ea37a2e --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInBlocks.java @@ -0,0 +1,13 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +import lombok.Data; + +@Data +public abstract class AddressInBlocks { + private final T blockSize; + private final long value; + + public long getAddressInBytes() { + return this.getValue() * this.getBlockSize().blockSize; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInDiscBlocks.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInDiscBlocks.java new file mode 100644 index 0000000..7fad5f2 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInDiscBlocks.java @@ -0,0 +1,11 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class AddressInDiscBlocks extends AddressInBlocks { + public AddressInDiscBlocks(DiscBlockSize blockSize, long value) { + super(blockSize, value); + } + + public static AddressInDiscBlocks empty() { + return new AddressInDiscBlocks(new DiscBlockSize(0), (long) 0); + } +} \ No newline at end of file diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInVolumeBlocks.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInVolumeBlocks.java new file mode 100644 index 0000000..cbb3285 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/AddressInVolumeBlocks.java @@ -0,0 +1,11 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class AddressInVolumeBlocks extends AddressInBlocks { + public AddressInVolumeBlocks(VolumeBlockSize blockSize, long value) { + super(blockSize, value); + } + + public static AddressInVolumeBlocks empty() { + return new AddressInVolumeBlocks(new VolumeBlockSize(0), (long) 0); + } +} \ No newline at end of file diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/BlockSize.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/BlockSize.java new file mode 100644 index 0000000..7e45477 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/BlockSize.java @@ -0,0 +1,20 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +import lombok.Getter; + +public abstract class BlockSize { + @Getter protected long blockSize; + + public BlockSize(long blockSize) { + this.blockSize = blockSize; + } + + public BlockSize(BlockSize copy) { + this.blockSize = copy.blockSize; + } + + @Override + public String toString() { + return Long.toString(blockSize); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/DiscBlockSize.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/DiscBlockSize.java new file mode 100644 index 0000000..6cd00e6 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/DiscBlockSize.java @@ -0,0 +1,9 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class DiscBlockSize extends BlockSize { + + public DiscBlockSize(long blockSize) { + super(blockSize); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionAddress.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionAddress.java new file mode 100644 index 0000000..ce92a8c --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionAddress.java @@ -0,0 +1,11 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class SectionAddress extends AddressInBlocks { + public SectionAddress(SectionBlockSize blockSize, long value) { + super(blockSize, value); + } + + public static SectionAddress empty() { + return new SectionAddress(new SectionBlockSize(0), (long) 0); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionBlockSize.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionBlockSize.java new file mode 100644 index 0000000..8debe84 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SectionBlockSize.java @@ -0,0 +1,9 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class SectionBlockSize extends BlockSize { + + public SectionBlockSize(long blockSize) { + super(blockSize); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInBlocks.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInBlocks.java new file mode 100644 index 0000000..a09307f --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInBlocks.java @@ -0,0 +1,13 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +import lombok.Data; + +@Data +public abstract class SizeInBlocks { + private final T blockSize; + private final long value; + + public long getSizeInBytes() { + return this.getValue() * this.getBlockSize().blockSize; + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInVolumeBlocks.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInVolumeBlocks.java new file mode 100644 index 0000000..40ddcbc --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/SizeInVolumeBlocks.java @@ -0,0 +1,9 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class SizeInVolumeBlocks extends SizeInBlocks { + + public SizeInVolumeBlocks(VolumeBlockSize blockSize, long value) { + super(blockSize, value); + } + +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/blocksize/VolumeBlockSize.java b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/VolumeBlockSize.java new file mode 100644 index 0000000..0119484 --- /dev/null +++ b/src/main/java/de/mas/wiiu/jnus/utils/blocksize/VolumeBlockSize.java @@ -0,0 +1,7 @@ +package de.mas.wiiu.jnus.utils.blocksize; + +public class VolumeBlockSize extends BlockSize { + public VolumeBlockSize(long blockSize) { + super(blockSize); + } +} diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java index 9cccf70..cd31c4f 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java @@ -37,7 +37,7 @@ public class AESEncryption { @Getter @Setter private byte[] AESKey; @Getter @Setter private byte[] IV; - + public AESEncryption(byte[] AESKey, byte[] IV) throws NoSuchProviderException { try { cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE"); diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java index eb91b21..e1d3749 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java @@ -73,7 +73,6 @@ public class NUSDecryption extends AESDecryption implements ContentDecryptor { return wrote; } if (inBlockBuffer != BLOCKSIZE) { - throw new IOException("wasn't able to read " + BLOCKSIZE); } @@ -168,8 +167,7 @@ public class NUSDecryption extends AESDecryption implements ContentDecryptor { curReadSize = (int) (toRead + writeOffset); } inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, blockBuffer, overflow, (int) Utils.align(curReadSize, 16)); - - if (inBlockBuffer < 0) { + if (inBlockBuffer <= 0) { break; } diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java index de2a46d..f78d48c 100644 --- a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java +++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java @@ -35,7 +35,7 @@ public class NUSEncryption extends AESEncryption implements ContentEncryptor { } return encrypt(blockBuffer, offset, BLOCKSIZE); } - + @Override public long readEncryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset) throws IOException { int BLOCKSIZE = 0x10000; @@ -61,28 +61,28 @@ public class NUSEncryption extends AESEncryption implements ContentEncryptor { if (inBlockBuffer != buffer_size) { break; } - + long curOffset = Math.max(0, payloadOffset - offset - read); byte[] IV = new byte[16]; if (curOffset < HASHBLOCKSIZE) { byte[] encryptedhashes = encryptFileChunk(Arrays.copyOfRange(decryptedBlockBuffer, 0, HASHBLOCKSIZE), HASHBLOCKSIZE, IV); - + long writeLength = Math.min((encryptedhashes.length - curOffset), (size - written)); - + out.write(encryptedhashes, (int) curOffset, (int) writeLength); written += writeLength; } else { curOffset = curOffset > HASHBLOCKSIZE ? curOffset - HASHBLOCKSIZE : 0; } - if(curOffset < HASHEDBLOCKSIZE) { + if (curOffset < HASHEDBLOCKSIZE) { int iv_start = (block % 16) * 20; IV = Arrays.copyOfRange(decryptedBlockBuffer, iv_start, iv_start + 16); - + byte[] encryptedContent = encryptFileChunk(Arrays.copyOfRange(decryptedBlockBuffer, HASHBLOCKSIZE, HASHEDBLOCKSIZE + HASHBLOCKSIZE), HASHEDBLOCKSIZE, IV); - - long writeLength = Math.min((encryptedContent.length - curOffset), (size - written)); + + long writeLength = Math.min((encryptedContent.length - curOffset), (size - written)); out.write(encryptedContent, (int) curOffset, (int) writeLength); written += writeLength; } @@ -96,8 +96,8 @@ public class NUSEncryption extends AESEncryption implements ContentEncryptor { } @Override - public long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV, - IVCache ivcache) throws IOException { + public long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV, IVCache ivcache) + throws IOException { int BLOCKSIZE = 0x08000; int buffer_size = BLOCKSIZE; @@ -140,7 +140,7 @@ public class NUSEncryption extends AESEncryption implements ContentEncryptor { long writeOffset = Math.max(0, payloadOffset - offset - read); long writeLength = Math.min((output.length - writeOffset), (size - written)); - + out.write(output, (int) writeOffset, (int) writeLength); written += writeLength; } while (written < size); diff --git a/src/test/java/TestCases.java b/src/test/java/TestCases.java index b182d13..24aa82c 100644 --- a/src/test/java/TestCases.java +++ b/src/test/java/TestCases.java @@ -19,7 +19,7 @@ import org.junit.rules.TemporaryFolder; import de.mas.wiiu.jnus.NUSTitle; import de.mas.wiiu.jnus.NUSTitleLoaderLocal; -import de.mas.wiiu.jnus.entities.content.Content; +import de.mas.wiiu.jnus.entities.TMD.Content; import de.mas.wiiu.jnus.interfaces.NUSDataProcessor; import de.mas.wiiu.jnus.utils.HashUtil; import de.mas.wiiu.jnus.utils.Utils; diff --git a/src/test/resources/out_dec/00000000.app.bak b/src/test/resources/out_dec/00000000.app.bak deleted file mode 100644 index 52af55cbe2ab84e2161249f0c63db1c4f7584f4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuyG{Z@6b9gfps}X0)Wp^pFKA+8jG`9WAXan;gu(z`)_^v?f{)}=89SZbpg~J3 zi*J(I%zyTrN#;9gUPf9gX;<>YAJ*K@nM(XBq*z{=?J3QMD6dA8-eNwYTC3I~YSr2K zncl^GEak6KImNtnp{T#)-E&mWlQwJ3kQ#x$q?y=nhq929r& zPjeKxGwSwYvwwDTT^~QSk4`t%?`|g184a`EFpJ&$?PlY=RUd9O8*w;kU0ocHpZZ6) zJ2B{G?dU!X29N96B>TS7fdByl1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 H{-?kvklP`c