Misc fixes, start rewriting RomFS parts to make it in line with AesCtrBufferedInputStream
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									c878992cb6
								
							
						
					
					
						commit
						f058905e2f
					
				
					 20 changed files with 619 additions and 758 deletions
				
			
		|  | @ -1,12 +1,12 @@ | ||||||
| # libKonogonka | # libKonogonka | ||||||
| 
 | 
 | ||||||
| [](https://ci.redrise.ru/desu/libKonogonka) |  [](https://ci.redrise.ru/desu/libKonogonka) | ||||||
| 
 | 
 | ||||||
| Library to work with NS-specific files / filesystem images. Dedicated back end part of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka)) | Library to work with NS-specific files / filesystem images. Ex-backend of [konogonka](https://github.com/developersu/konogonka) ([independent src location](https://git.redrise.ru/desu/konogonka)) | ||||||
| 
 | 
 | ||||||
| ### License | ### License | ||||||
| 
 | 
 | ||||||
| [GNU General Public License v3+](https://git.redrise.ru/desu/libKonogonka/LICENSE) | [GNU General Public License v3 or higher](https://git.redrise.ru/desu/libKonogonka/LICENSE) | ||||||
| 
 | 
 | ||||||
| ### Used libraries & resources | ### Used libraries & resources | ||||||
| * [Bouncy Castle](https://www.bouncycastle.org/) for Java. | * [Bouncy Castle](https://www.bouncycastle.org/) for Java. | ||||||
|  |  | ||||||
|  | @ -36,14 +36,62 @@ public class RainbowDump { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public static void hexDumpUTF8(byte[] byteArray){ |     public static void hexDumpUTF8(byte[] byteArray){ | ||||||
|  |         if (byteArray == null || byteArray.length == 0) | ||||||
|  |             return; | ||||||
|  | 
 | ||||||
|  |         int k = 0; | ||||||
|  |         System.out.printf("%s%08x  %s", ANSI_BLUE, 0, ANSI_RESET); | ||||||
|  |         for (int i = 0; i < byteArray.length; i++) { | ||||||
|  |             if (k == 8) | ||||||
|  |                 System.out.print("  "); | ||||||
|  |             if (k == 16){ | ||||||
|  |                 System.out.print(ANSI_GREEN+"| "+ANSI_RESET); | ||||||
|  |                 printChars(byteArray, i); | ||||||
|  |                 System.out.println(); | ||||||
|  |                 System.out.printf("%s%08x  %s", ANSI_BLUE, i, ANSI_RESET); | ||||||
|  |                 k = 0; | ||||||
|  |             } | ||||||
|  |             System.out.printf("%02x ", byteArray[i]); | ||||||
|  |             k++; | ||||||
|  |         } | ||||||
|  |         int paddingSize = 16 - (byteArray.length % 16); | ||||||
|  |         if (paddingSize != 16) { | ||||||
|  |             for (int i = 0; i < paddingSize; i++) { | ||||||
|  |                 System.out.print("   "); | ||||||
|  |             } | ||||||
|  |             if (paddingSize > 7) { | ||||||
|  |                 System.out.print("  "); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         System.out.print(ANSI_GREEN+"| "+ANSI_RESET); | ||||||
|  |         printChars(byteArray, byteArray.length); | ||||||
|  |         System.out.println(); | ||||||
|  |         System.out.print(ANSI_RESET+new String(byteArray, StandardCharsets.UTF_8)+"\n"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static void printChars(byte[] byteArray, int pointer){ | ||||||
|  |         for (int j = pointer-16; j < pointer; j++){ | ||||||
|  |             if ((byteArray[j] > 21) && (byteArray[j] < 126)) // man ascii | ||||||
|  |                 System.out.print((char) byteArray[j]); | ||||||
|  |             else if (byteArray[j] == 0x0a) | ||||||
|  |                 System.out.print("↲"); //"" | ||||||
|  |             else if (byteArray[j] == 0x0d) | ||||||
|  |                 System.out.print("←"); // "␍" | ||||||
|  |             else | ||||||
|  |                 System.out.print("."); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static void hexDumpUTF8Legacy(byte[] byteArray){ | ||||||
|         if (byteArray == null || byteArray.length == 0) |         if (byteArray == null || byteArray.length == 0) | ||||||
|             return; |             return; | ||||||
|         System.out.print(ANSI_BLUE); |         System.out.print(ANSI_BLUE); | ||||||
|         for (int i=0; i < byteArray.length; i++) |         for (int i=0; i < byteArray.length; i++) | ||||||
|             System.out.print(String.format("%02d-", i%100)); |             System.out.printf("%02d-", i%100); | ||||||
|         System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); |         System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET); | ||||||
|         for (byte b: byteArray) |         for (byte b: byteArray) | ||||||
|             System.out.print(String.format("%02x ", b)); |             System.out.printf("%02x ", b); | ||||||
|         System.out.println(); |         System.out.println(); | ||||||
|         System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n"); |         System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ import libKonogonka.Tools.NCA.NCASectionTableBlock.NcaFsHeader; | ||||||
| import libKonogonka.Tools.PFS0.IPFS0Provider; | import libKonogonka.Tools.PFS0.IPFS0Provider; | ||||||
| import libKonogonka.Tools.PFS0.PFS0Provider; | import libKonogonka.Tools.PFS0.PFS0Provider; | ||||||
| import libKonogonka.Tools.RomFs.IRomFsProvider; | import libKonogonka.Tools.RomFs.IRomFsProvider; | ||||||
| import libKonogonka.Tools.RomFs.RomFsEncryptedProvider; | import libKonogonka.Tools.RomFs.RomFsProvider; | ||||||
| import libKonogonka.ctraes.AesCtrBufferedInputStream; | import libKonogonka.ctraes.AesCtrBufferedInputStream; | ||||||
| import libKonogonka.ctraes.AesCtrDecryptSimple; | import libKonogonka.ctraes.AesCtrDecryptSimple; | ||||||
| import libKonogonka.exceptions.EmptySectionException; | import libKonogonka.exceptions.EmptySectionException; | ||||||
|  | @ -32,7 +32,6 @@ import org.apache.logging.log4j.Logger; | ||||||
| import java.io.*; | import java.io.*; | ||||||
| import java.nio.file.Files; | import java.nio.file.Files; | ||||||
| import java.nio.file.Paths; | import java.nio.file.Paths; | ||||||
| import java.util.LinkedList; |  | ||||||
| 
 | 
 | ||||||
| public class NCAContent { | public class NCAContent { | ||||||
|     private final static Logger log = LogManager.getLogger(NCAContent.class); |     private final static Logger log = LogManager.getLogger(NCAContent.class); | ||||||
|  | @ -43,7 +42,6 @@ public class NCAContent { | ||||||
|     private final NCAHeaderTableEntry ncaHeaderTableEntry; |     private final NCAHeaderTableEntry ncaHeaderTableEntry; | ||||||
|     private final byte[] decryptedKey; |     private final byte[] decryptedKey; | ||||||
| 
 | 
 | ||||||
|     private LinkedList<byte[]> Pfs0SHA256hashes; |  | ||||||
|     private IPFS0Provider pfs0; |     private IPFS0Provider pfs0; | ||||||
|     private IRomFsProvider romfs; |     private IRomFsProvider romfs; | ||||||
| 
 | 
 | ||||||
|  | @ -59,8 +57,6 @@ public class NCAContent { | ||||||
|         this.ncaFsHeader = ncaFsHeader; |         this.ncaFsHeader = ncaFsHeader; | ||||||
|         this.ncaHeaderTableEntry = ncaHeaderTableEntry; |         this.ncaHeaderTableEntry = ncaHeaderTableEntry; | ||||||
|         this.decryptedKey = decryptedKey; |         this.decryptedKey = decryptedKey; | ||||||
|         System.out.println("NCAContent pfs0offsetPosition: "+ncaOffsetPosition); |  | ||||||
|         Pfs0SHA256hashes = new LinkedList<>(); |  | ||||||
|         // If nothing to do |         // If nothing to do | ||||||
|         if (ncaHeaderTableEntry.getMediaEndOffset() == 0) |         if (ncaHeaderTableEntry.getMediaEndOffset() == 0) | ||||||
|             throw new EmptySectionException("Empty section"); |             throw new EmptySectionException("Empty section"); | ||||||
|  | @ -91,7 +87,6 @@ public class NCAContent { | ||||||
|                 ncaFsHeader.getSuperBlockPFS0(), |                 ncaFsHeader.getSuperBlockPFS0(), | ||||||
|                 ncaHeaderTableEntry.getMediaStartOffset(), |                 ncaHeaderTableEntry.getMediaStartOffset(), | ||||||
|                 ncaHeaderTableEntry.getMediaEndOffset()); |                 ncaHeaderTableEntry.getMediaEndOffset()); | ||||||
|         Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void proceedPFS0Encrypted() throws Exception{ |     private void proceedPFS0Encrypted() throws Exception{ | ||||||
|  | @ -103,16 +98,15 @@ public class NCAContent { | ||||||
|                 decryptor, |                 decryptor, | ||||||
|                 ncaHeaderTableEntry.getMediaStartOffset(), |                 ncaHeaderTableEntry.getMediaStartOffset(), | ||||||
|                 ncaHeaderTableEntry.getMediaEndOffset()); |                 ncaHeaderTableEntry.getMediaEndOffset()); | ||||||
|         Pfs0SHA256hashes = pfs0.getPfs0SHA256hashes(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void proceedRomFs() throws Exception{ |     private void proceedRomFs() throws Exception{ | ||||||
|         switch (ncaFsHeader.getCryptoType()){ |         switch (ncaFsHeader.getCryptoType()){ | ||||||
|             case 0x01: |             case 0x01: | ||||||
|                 proceedRomFsNotEncrypted(); // IF NO ENCRYPTION |                 proceedRomFsNotEncrypted(); | ||||||
|                 break; |                 break; | ||||||
|             case 0x03: |             case 0x03: | ||||||
|                 proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ] |                 proceedRomFsEncrypted(); | ||||||
|                 break; |                 break; | ||||||
|             default: |             default: | ||||||
|                 throw new Exception("Non-supported 'Crypto type'"); |                 throw new Exception("Non-supported 'Crypto type'"); | ||||||
|  | @ -125,7 +119,7 @@ public class NCAContent { | ||||||
|         if (decryptedKey == null) |         if (decryptedKey == null) | ||||||
|             throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); |             throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided."); | ||||||
| 
 | 
 | ||||||
|         this.romfs = new RomFsEncryptedProvider( |         this.romfs = new RomFsProvider( | ||||||
|                 ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(), |                 ncaFsHeader.getSuperBlockIVFC().getLvl6Offset(), | ||||||
|                 file, |                 file, | ||||||
|                 ncaOffsetPosition, |                 ncaOffsetPosition, | ||||||
|  |  | ||||||
|  | @ -29,8 +29,6 @@ import javax.crypto.Cipher; | ||||||
| import javax.crypto.spec.SecretKeySpec; | import javax.crypto.spec.SecretKeySpec; | ||||||
| import java.io.*; | import java.io.*; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
| import java.nio.file.Files; |  | ||||||
| import java.nio.file.Paths; |  | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -33,8 +33,7 @@ import java.nio.file.Paths; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.LinkedList; | import java.util.LinkedList; | ||||||
| 
 | 
 | ||||||
| import static libKonogonka.Converter.getLEint; | import static libKonogonka.Converter.*; | ||||||
| import static libKonogonka.Converter.getLElong; |  | ||||||
| 
 | 
 | ||||||
| public class PFS0Provider implements IPFS0Provider{ | public class PFS0Provider implements IPFS0Provider{ | ||||||
|     private final static Logger log = LogManager.getLogger(PFS0Provider.class); |     private final static Logger log = LogManager.getLogger(PFS0Provider.class); | ||||||
|  | @ -295,11 +294,68 @@ public class PFS0Provider implements IPFS0Provider{ | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     //TODO: REMOVE |     /** | ||||||
|  |      * @deprecated | ||||||
|  |      * */ | ||||||
|     @Override |     @Override | ||||||
|     public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {return null;} |     public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { | ||||||
|  |         for (int i = 0; i < pfs0subFiles.length; i++) { | ||||||
|  |             if (pfs0subFiles[i].getName().equals(subFileName)) | ||||||
|  |                 return getProviderSubFilePipedInpStream(i); | ||||||
|  |         } | ||||||
|  |         throw new Exception("No file with such name exists: "+subFileName); | ||||||
|  |     } | ||||||
|  |     /** | ||||||
|  |      * @deprecated | ||||||
|  |      * */ | ||||||
|     @Override |     @Override | ||||||
|     public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception {return null;} |     public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { | ||||||
|  |         PipedOutputStream streamOut = new PipedOutputStream(); | ||||||
|  |         PipedInputStream streamInp = new PipedInputStream(streamOut); | ||||||
|  | 
 | ||||||
|  |         Thread workerThread = new Thread(() -> { | ||||||
|  |             try { | ||||||
|  |                 PFS0subFile subFile = pfs0subFiles[subFileNumber]; | ||||||
|  | 
 | ||||||
|  |                 if (encrypted) | ||||||
|  |                     createAesCtrEncryptedBufferedInputStream(); | ||||||
|  |                 else | ||||||
|  |                     createBufferedInputStream(); | ||||||
|  | 
 | ||||||
|  |                 long subFileSize = subFile.getSize(); | ||||||
|  | 
 | ||||||
|  |                 long toSkip = subFile.getOffset() + mediaStartOffset * 0x200 + rawBlockDataStart; | ||||||
|  |                 if (toSkip != stream.skip(toSkip)) | ||||||
|  |                     throw new Exception("Unable to skip offset: " + toSkip); | ||||||
|  | 
 | ||||||
|  |                 int blockSize = 0x200; | ||||||
|  |                 if (subFileSize < 0x200) | ||||||
|  |                     blockSize = (int) subFileSize; | ||||||
|  | 
 | ||||||
|  |                 long i = 0; | ||||||
|  |                 byte[] block = new byte[blockSize]; | ||||||
|  | 
 | ||||||
|  |                 int actuallyRead; | ||||||
|  |                 while (true) { | ||||||
|  |                     if ((actuallyRead = stream.read(block)) != blockSize) | ||||||
|  |                         throw new Exception("Read failure. Block Size: " + blockSize + ", actuallyRead: " + actuallyRead); | ||||||
|  |                     streamOut.write(block); | ||||||
|  |                     i += blockSize; | ||||||
|  |                     if ((i + blockSize) > subFileSize) { | ||||||
|  |                         blockSize = (int) (subFileSize - i); | ||||||
|  |                         if (blockSize == 0) | ||||||
|  |                             break; | ||||||
|  |                         block = new byte[blockSize]; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception e){ | ||||||
|  |                 log.error(e); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         workerThread.start(); | ||||||
|  |         return streamInp; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     public LinkedList<byte[]> getPfs0SHA256hashes() { |     public LinkedList<byte[]> getPfs0SHA256hashes() { | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ import org.apache.logging.log4j.Logger; | ||||||
|  * This class stores information contained in Level 6 Header of the RomFS image |  * This class stores information contained in Level 6 Header of the RomFS image | ||||||
|  * ------------------------------------ |  * ------------------------------------ | ||||||
|  * | Header Length (usually 0x50)     | |  * | Header Length (usually 0x50)     | | ||||||
|  * | Directory Hash Table Offset      | Not used by this library | '<< 32' to get real offset: see implementation |  * | Directory Hash Table Offset      | Not used by this library | ||||||
|  * | Directory Hash Table Length      | Not used by this library |  * | Directory Hash Table Length      | Not used by this library | ||||||
|  * | Directory Metadata Table Offset  | |  * | Directory Metadata Table Offset  | | ||||||
|  * | Directory Metadata Table Length  | |  * | Directory Metadata Table Length  | | ||||||
|  | @ -42,7 +42,7 @@ public class Level6Header { | ||||||
|     private final static Logger log = LogManager.getLogger(Level6Header.class); |     private final static Logger log = LogManager.getLogger(Level6Header.class); | ||||||
| 
 | 
 | ||||||
|     private final long headerLength; |     private final long headerLength; | ||||||
|     private long directoryHashTableOffset; |     private final long directoryHashTableOffset; | ||||||
|     private final long directoryHashTableLength; |     private final long directoryHashTableLength; | ||||||
|     private final long directoryMetadataTableOffset; |     private final long directoryMetadataTableOffset; | ||||||
|     private final long directoryMetadataTableLength; |     private final long directoryMetadataTableLength; | ||||||
|  | @ -61,7 +61,7 @@ public class Level6Header { | ||||||
|             throw new Exception("Level 6 Header section is too small"); |             throw new Exception("Level 6 Header section is too small"); | ||||||
|         headerLength = getNext(); |         headerLength = getNext(); | ||||||
|         directoryHashTableOffset = getNext(); |         directoryHashTableOffset = getNext(); | ||||||
|         directoryHashTableOffset <<= 32; |         //directoryHashTableOffset <<= 32; | ||||||
|         directoryHashTableLength = getNext(); |         directoryHashTableLength = getNext(); | ||||||
|         directoryMetadataTableOffset = getNext(); |         directoryMetadataTableOffset = getNext(); | ||||||
|         directoryMetadataTableLength = getNext(); |         directoryMetadataTableLength = getNext(); | ||||||
|  | @ -73,7 +73,7 @@ public class Level6Header { | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     private long getNext(){ |     private long getNext(){ | ||||||
|         final long result = Converter.getLEint(headerBytes, _cursor); |         final long result = Converter.getLElongOfInt(headerBytes, _cursor); | ||||||
|         _cursor += 0x8; |         _cursor += 0x8; | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
							
								
								
									
										160
									
								
								src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/main/java/libKonogonka/Tools/RomFs/RomFsConstruct.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,160 @@ | ||||||
|  | /* | ||||||
|  |     Copyright 2018-2022 Dmitry Isaenko | ||||||
|  |       | ||||||
|  |     This file is part of libKonogonka. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  | package libKonogonka.Tools.RomFs; | ||||||
|  | 
 | ||||||
|  | import libKonogonka.Converter; | ||||||
|  | import libKonogonka.ctraes.AesCtrBufferedInputStream; | ||||||
|  | import libKonogonka.ctraes.AesCtrDecryptSimple; | ||||||
|  | import org.apache.logging.log4j.LogManager; | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  | 
 | ||||||
|  | import java.io.BufferedInputStream; | ||||||
|  | import java.io.File; | ||||||
|  | import java.nio.file.Files; | ||||||
|  | 
 | ||||||
|  | public class RomFsConstruct { | ||||||
|  |     private final static Logger log = LogManager.getLogger(RomFsConstruct.class); | ||||||
|  | 
 | ||||||
|  |     private Level6Header header; | ||||||
|  | 
 | ||||||
|  |     private FileSystemEntry rootEntry; | ||||||
|  |     private final BufferedInputStream stream; | ||||||
|  |     private int headerSize; | ||||||
|  |     private byte[] directoryMetadataTable; | ||||||
|  |     private byte[] fileMetadataTable; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private final File file; | ||||||
|  |     private long offsetPositionInFile; | ||||||
|  |     private final long level6Offset; | ||||||
|  | 
 | ||||||
|  |     RomFsConstruct(File file, | ||||||
|  |                    long level6Offset) throws Exception{ | ||||||
|  |         if (level6Offset < 0) | ||||||
|  |             throw new Exception("Incorrect Level 6 Offset"); | ||||||
|  |         this.file = file; | ||||||
|  |         this.level6Offset = level6Offset; | ||||||
|  |         this.stream = new BufferedInputStream(Files.newInputStream(file.toPath())); | ||||||
|  |         constructEverything(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     RomFsConstruct(File file, | ||||||
|  |                    long ncaOffset, // NCA offset position | ||||||
|  |                    long level6Offset, | ||||||
|  |                    AesCtrDecryptSimple decryptor, | ||||||
|  |                    long mediaStartOffset, | ||||||
|  |                    long mediaEndOffset | ||||||
|  |                             ) throws Exception { | ||||||
|  |         if (level6Offset < 0) | ||||||
|  |             throw new Exception("Incorrect Level 6 Offset"); | ||||||
|  |         this.file = file; | ||||||
|  |         this.level6Offset = level6Offset; | ||||||
|  |         this.offsetPositionInFile = ncaOffset + (mediaStartOffset * 0x200); | ||||||
|  |         // In 512-blocks | ||||||
|  |         // In 512-blocks | ||||||
|  |         this.stream = new AesCtrBufferedInputStream( | ||||||
|  |                 decryptor, | ||||||
|  |                 ncaOffset, | ||||||
|  |                 mediaStartOffset, | ||||||
|  |                 mediaEndOffset, | ||||||
|  |                 Files.newInputStream(file.toPath())); | ||||||
|  |         constructEverything(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void constructEverything() throws Exception{ | ||||||
|  |         goToStartingPosition(); | ||||||
|  | 
 | ||||||
|  |         constructHeader(); | ||||||
|  | 
 | ||||||
|  |         directoryMetadataTableLengthCheck(); | ||||||
|  |         directoryMetadataTableConstruct(); | ||||||
|  | 
 | ||||||
|  |         fileMetadataTableLengthCheck(); | ||||||
|  |         fileMetadataTableConstruct(); | ||||||
|  | 
 | ||||||
|  |         constructRootFilesystemEntry(); | ||||||
|  | 
 | ||||||
|  |         stream.close(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void goToStartingPosition() throws Exception{ | ||||||
|  |         skipBytes(offsetPositionInFile + level6Offset); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void constructHeader() throws Exception{ | ||||||
|  |         byte[] headerSizeBytes = detectHeaderSize(); | ||||||
|  |         byte[] rawDataChunk = new byte[headerSize-0x8]; | ||||||
|  | 
 | ||||||
|  |         if (stream.read(rawDataChunk) != headerSize-0x8) | ||||||
|  |             throw new Exception(String.format("Failed to read header (0x%x)", (headerSize-0x8))); | ||||||
|  |         byte[] lv6headerBytes = new byte[headerSize]; | ||||||
|  |         System.arraycopy(headerSizeBytes, 0, lv6headerBytes, 0, 0x8); | ||||||
|  |         System.arraycopy(rawDataChunk, 0, lv6headerBytes, 0x8, headerSize-0x8); | ||||||
|  |         this.header = new Level6Header(lv6headerBytes); | ||||||
|  |     } | ||||||
|  |     private byte[] detectHeaderSize() throws Exception{ | ||||||
|  |         byte[] lv6HeaderSizeRaw = new byte[0x8]; | ||||||
|  |         if (stream.read(lv6HeaderSizeRaw) != 0x8) | ||||||
|  |             throw new Exception("Failed to read header size"); | ||||||
|  |         headerSize = Converter.getLEint(lv6HeaderSizeRaw, 0); | ||||||
|  |         return lv6HeaderSizeRaw; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void directoryMetadataTableLengthCheck() throws Exception{ | ||||||
|  |         if (header.getDirectoryMetadataTableLength() < 0) | ||||||
|  |             throw new Exception("Not supported: DirectoryMetadataTableLength < 0"); | ||||||
|  |     } | ||||||
|  |     private void directoryMetadataTableConstruct() throws Exception{ | ||||||
|  |         skipBytes(header.getDirectoryMetadataTableOffset() - headerSize); | ||||||
|  | 
 | ||||||
|  |         directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()]; | ||||||
|  |         if (stream.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength()) | ||||||
|  |             throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void fileMetadataTableLengthCheck() throws Exception{ | ||||||
|  |         if (header.getFileMetadataTableLength() < 0) | ||||||
|  |             throw new Exception("Not supported: FileMetadataTableLength < 0"); | ||||||
|  |     } | ||||||
|  |     private void fileMetadataTableConstruct() throws Exception{ | ||||||
|  |         skipBytes(header.getFileMetadataTableOffset() - header.getFileHashTableOffset()); | ||||||
|  | 
 | ||||||
|  |         fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()]; | ||||||
|  |         if (stream.read(fileMetadataTable) != (int) header.getFileMetadataTableLength()) | ||||||
|  |             throw new Exception("Failed to read "+header.getFileMetadataTableLength()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void constructRootFilesystemEntry() throws Exception{ | ||||||
|  |         rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void skipBytes(long size) throws Exception{ | ||||||
|  |         long mustSkip = size; | ||||||
|  |         long skipped = 0; | ||||||
|  |         while (mustSkip > 0){ | ||||||
|  |             skipped += stream.skip(mustSkip); | ||||||
|  |             mustSkip = size - skipped; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Level6Header getHeader() { return header; } | ||||||
|  |     FileSystemEntry getRootEntry(){ return rootEntry; } | ||||||
|  |     byte[] getDirectoryMetadataTable() { return directoryMetadataTable; } | ||||||
|  |     byte[] getFileMetadataTable() { return fileMetadataTable;} | ||||||
|  | } | ||||||
							
								
								
									
										125
									
								
								src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								src/main/java/libKonogonka/Tools/RomFs/RomFsContentRetrieve.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,125 @@ | ||||||
|  | /* | ||||||
|  |     Copyright 2018-2022 Dmitry Isaenko | ||||||
|  |       | ||||||
|  |     This file is part of libKonogonka. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  | package libKonogonka.Tools.RomFs; | ||||||
|  | 
 | ||||||
|  | import libKonogonka.ctraes.AesCtrBufferedInputStream; | ||||||
|  | import libKonogonka.ctraes.AesCtrDecryptSimple; | ||||||
|  | import org.apache.logging.log4j.LogManager; | ||||||
|  | import org.apache.logging.log4j.Logger; | ||||||
|  | 
 | ||||||
|  | import java.io.*; | ||||||
|  | import java.nio.file.Files; | ||||||
|  | 
 | ||||||
|  | public class RomFsContentRetrieve implements Runnable{ | ||||||
|  |     private final static Logger log = LogManager.getLogger(RomFsContentRetrieve.class); | ||||||
|  | 
 | ||||||
|  |     private final PipedOutputStream streamOut; | ||||||
|  |     private final long internalFileSize; | ||||||
|  |     private final long startPosition; | ||||||
|  |     private final BufferedInputStream bis; | ||||||
|  | 
 | ||||||
|  |     RomFsContentRetrieve(File parentFile, | ||||||
|  |                          PipedOutputStream streamOut, | ||||||
|  |                          long internalFileRealPosition, | ||||||
|  |                          long internalFileSize) throws Exception{ | ||||||
|  |         this.streamOut = streamOut; | ||||||
|  |         this.internalFileSize = internalFileSize; | ||||||
|  | 
 | ||||||
|  |         this.startPosition = internalFileRealPosition; | ||||||
|  |         this.bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath())); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     RomFsContentRetrieve(File parentFile, | ||||||
|  |                          PipedOutputStream streamOut, | ||||||
|  |                          AesCtrDecryptSimple decryptor, | ||||||
|  |                          long entryOffset, | ||||||
|  |                          long internalFileSize, | ||||||
|  |                          long headersFileDataOffset,        //level6Header.getFileDataOffset() | ||||||
|  |                          long level6Offset, | ||||||
|  |                          long ncaOffsetPosition, | ||||||
|  |                          long mediaStartOffset, | ||||||
|  |                          long mediaEndOffset | ||||||
|  |                                   ) throws Exception{ | ||||||
|  |         log.fatal("Current implementation works incorrectly"); | ||||||
|  |         this.streamOut = streamOut; | ||||||
|  |         this.internalFileSize = internalFileSize; | ||||||
|  | 
 | ||||||
|  |         this.startPosition = entryOffset + mediaStartOffset*0x200 + headersFileDataOffset + level6Offset; | ||||||
|  | 
 | ||||||
|  |         this.bis = new AesCtrBufferedInputStream( | ||||||
|  |                 decryptor, | ||||||
|  |                 ncaOffsetPosition, | ||||||
|  |                 mediaStartOffset, | ||||||
|  |                 mediaEndOffset, | ||||||
|  |                 Files.newInputStream(parentFile.toPath()) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void run() { | ||||||
|  |         log.trace("Executing thread"); | ||||||
|  |         try { | ||||||
|  |             skipBytesTillBegining(); | ||||||
|  | 
 | ||||||
|  |             int readPiece = 8388608; | ||||||
|  |             long readFrom = 0; | ||||||
|  |             byte[] readBuffer; | ||||||
|  | 
 | ||||||
|  |             while (readFrom < internalFileSize) { | ||||||
|  |                 if (internalFileSize - readFrom < readPiece) | ||||||
|  |                     readPiece = Math.toIntExact(internalFileSize - readFrom); | ||||||
|  |                 readBuffer = new byte[readPiece]; | ||||||
|  |                 if (bis.read(readBuffer) != readPiece) { | ||||||
|  |                     log.error("getContent(): Unable to read requested size from file."); | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 streamOut.write(readBuffer); | ||||||
|  |                 readFrom += readPiece; | ||||||
|  |             } | ||||||
|  |         } catch (Exception exception) { | ||||||
|  |             log.error("RomFsProvider -> getContent(): Unable to provide stream", exception); | ||||||
|  |         } | ||||||
|  |         finally { | ||||||
|  |             closeStreams(); | ||||||
|  |         } | ||||||
|  |         log.trace("Thread died"); | ||||||
|  |     } | ||||||
|  |     private void skipBytesTillBegining() throws Exception{ | ||||||
|  |         long mustSkip = startPosition; | ||||||
|  |         long skipped = 0; | ||||||
|  |         while (mustSkip > 0){ | ||||||
|  |             skipped += bis.skip(mustSkip); | ||||||
|  |             mustSkip = startPosition - skipped; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     private void closeStreams(){ | ||||||
|  |         try { | ||||||
|  |             streamOut.close(); | ||||||
|  |         } | ||||||
|  |         catch (IOException e){ | ||||||
|  |             log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'"); | ||||||
|  |         } | ||||||
|  |         try { | ||||||
|  |             bis.close(); | ||||||
|  |         } | ||||||
|  |         catch (IOException e){ | ||||||
|  |             log.error("RomFsProvider -> getContent(): Unable to close 'StreamOut'"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,121 +0,0 @@ | ||||||
| /* |  | ||||||
|     Copyright 2018-2022 Dmitry Isaenko |  | ||||||
|       |  | ||||||
|     This file is part of libKonogonka. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
| package libKonogonka.Tools.RomFs; |  | ||||||
| 
 |  | ||||||
| import libKonogonka.Converter; |  | ||||||
| 
 |  | ||||||
| import java.io.BufferedInputStream; |  | ||||||
| import java.io.File; |  | ||||||
| import java.nio.file.Files; |  | ||||||
| /** |  | ||||||
|  * Construct header for RomFs and obtain root fileSystemEntry (meta information) |  | ||||||
| * */ |  | ||||||
| class RomFsDecryptedConstruct { |  | ||||||
|     private Level6Header header; |  | ||||||
| 
 |  | ||||||
|     private FileSystemEntry rootEntry; |  | ||||||
|     private final BufferedInputStream fileBufferedInputStream; |  | ||||||
|     private int headerSize; |  | ||||||
|     private byte[] directoryMetadataTable; |  | ||||||
|     private byte[] fileMetadataTable; |  | ||||||
| 
 |  | ||||||
|     RomFsDecryptedConstruct(File decryptedFsImageFile, long level6offset) throws Exception{ |  | ||||||
|         if (level6offset < 0) |  | ||||||
|             throw new Exception("Incorrect Level 6 Offset"); |  | ||||||
| 
 |  | ||||||
|         fileBufferedInputStream = new BufferedInputStream(Files.newInputStream(decryptedFsImageFile.toPath())); |  | ||||||
|         fastForwardBySkippingBytes(level6offset); |  | ||||||
| 
 |  | ||||||
|         detectHeaderSize(); |  | ||||||
|         constructHeader(); |  | ||||||
| 
 |  | ||||||
|         fastForwardBySkippingBytes(header.getDirectoryMetadataTableOffset() - headerSize); |  | ||||||
| 
 |  | ||||||
|         directoryMetadataTableLengthCheck(); |  | ||||||
|         directoryMetadataTableConstruct(); |  | ||||||
| 
 |  | ||||||
|         fastForwardBySkippingBytes(header.getFileMetadataTableOffset() - header.getFileHashTableOffset()); |  | ||||||
| 
 |  | ||||||
|         fileMetadataTableLengthCheck(); |  | ||||||
|         fileMetadataTableConstruct(); |  | ||||||
| 
 |  | ||||||
|         constructRootFilesystemEntry(); |  | ||||||
| 
 |  | ||||||
|         fileBufferedInputStream.close(); |  | ||||||
|     } |  | ||||||
|     private void detectHeaderSize() throws Exception{ |  | ||||||
|         fileBufferedInputStream.mark(0x10); |  | ||||||
|         byte[] lv6HeaderSizeRaw = new byte[0x8]; |  | ||||||
|         if (fileBufferedInputStream.read(lv6HeaderSizeRaw) != 0x8) |  | ||||||
|             throw new Exception("Failed to read header size"); |  | ||||||
|         headerSize = Converter.getLEint(lv6HeaderSizeRaw, 0); |  | ||||||
|         fileBufferedInputStream.reset(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void constructHeader() throws Exception{ |  | ||||||
|         byte[] rawDataChunk = new byte[headerSize]; |  | ||||||
| 
 |  | ||||||
|         if (fileBufferedInputStream.read(rawDataChunk) != headerSize) |  | ||||||
|             throw new Exception(String.format("Failed to read header (0x%x)", headerSize)); |  | ||||||
| 
 |  | ||||||
|         this.header = new Level6Header(rawDataChunk); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void directoryMetadataTableLengthCheck() throws Exception{ |  | ||||||
|         if (header.getDirectoryMetadataTableLength() < 0) |  | ||||||
|             throw new Exception("Not supported operation."); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void directoryMetadataTableConstruct() throws Exception{ |  | ||||||
|         directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()]; |  | ||||||
|         if (fileBufferedInputStream.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength()) |  | ||||||
|             throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void fileMetadataTableLengthCheck() throws Exception{ |  | ||||||
|         if (header.getFileMetadataTableLength() < 0) |  | ||||||
|             throw new Exception("Not supported operation."); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void fileMetadataTableConstruct() throws Exception{ |  | ||||||
|         fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()]; |  | ||||||
| 
 |  | ||||||
|         if (fileBufferedInputStream.read(fileMetadataTable) != (int) header.getFileMetadataTableLength()) |  | ||||||
|             throw new Exception("Failed to read "+header.getFileMetadataTableLength()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void constructRootFilesystemEntry() throws Exception{ |  | ||||||
|         rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); |  | ||||||
|         //rootEntry.printTreeForDebug(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void fastForwardBySkippingBytes(long size) throws Exception{ |  | ||||||
|         long mustSkip = size; |  | ||||||
|         long skipped = 0; |  | ||||||
|         while (mustSkip > 0){ |  | ||||||
|             skipped += fileBufferedInputStream.skip(mustSkip); |  | ||||||
|             mustSkip = size - skipped; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Level6Header getHeader() { return header; } |  | ||||||
|     FileSystemEntry getRootEntry(){ return rootEntry; } |  | ||||||
|     byte[] getDirectoryMetadataTable() { return directoryMetadataTable; } |  | ||||||
|     byte[] getFileMetadataTable() { return fileMetadataTable;} |  | ||||||
| } |  | ||||||
|  | @ -1,93 +0,0 @@ | ||||||
| /* |  | ||||||
|     Copyright 2018-2022 Dmitry Isaenko |  | ||||||
|       |  | ||||||
|     This file is part of libKonogonka. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
| package libKonogonka.Tools.RomFs; |  | ||||||
| 
 |  | ||||||
| import org.apache.logging.log4j.LogManager; |  | ||||||
| import org.apache.logging.log4j.Logger; |  | ||||||
| 
 |  | ||||||
| import java.io.BufferedInputStream; |  | ||||||
| import java.io.File; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.io.PipedOutputStream; |  | ||||||
| import java.nio.file.Files; |  | ||||||
| 
 |  | ||||||
| public class RomFsDecryptedContentRetrieve implements Runnable { |  | ||||||
|     private final static Logger log = LogManager.getLogger(RomFsDecryptedContentRetrieve.class); |  | ||||||
| 
 |  | ||||||
|     private final File parentFile; |  | ||||||
|     private final PipedOutputStream streamOut; |  | ||||||
|     private final long internalFileRealPosition; |  | ||||||
|     private final long internalFileSize; |  | ||||||
| 
 |  | ||||||
|     RomFsDecryptedContentRetrieve(File parentFile, |  | ||||||
|                                   PipedOutputStream streamOut, |  | ||||||
|                                   long internalFileRealPosition, |  | ||||||
|                                   long internalFileSize){ |  | ||||||
|         this.parentFile = parentFile; |  | ||||||
|         this.streamOut = streamOut; |  | ||||||
|         this.internalFileRealPosition = internalFileRealPosition; |  | ||||||
|         this.internalFileSize = internalFileSize; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void run() { |  | ||||||
|         log.trace("Executing thread"); |  | ||||||
|         try (BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(parentFile.toPath()))){ |  | ||||||
|             fastForwardBySkippingBytes(bis, internalFileRealPosition); |  | ||||||
| 
 |  | ||||||
|             int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 |  | ||||||
|             long readFrom = 0; |  | ||||||
|             byte[] readBuffer; |  | ||||||
| 
 |  | ||||||
|             while (readFrom < internalFileSize) { |  | ||||||
|                 if (internalFileSize - readFrom < readPice) |  | ||||||
|                     readPice = Math.toIntExact(internalFileSize - readFrom);    // it's safe, I guarantee |  | ||||||
|                 readBuffer = new byte[readPice]; |  | ||||||
|                 if (bis.read(readBuffer) != readPice) { |  | ||||||
|                     log.error("getContent(): Unable to read requested size from file."); |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 streamOut.write(readBuffer); |  | ||||||
|                 readFrom += readPice; |  | ||||||
|             } |  | ||||||
|         } catch (Exception exception) { |  | ||||||
|             log.error("RomFsDecryptedProvider -> getContent(): Unable to provide stream", exception); |  | ||||||
|         } |  | ||||||
|         finally { |  | ||||||
|             closeStreamOut(); |  | ||||||
|         } |  | ||||||
|         log.trace("Thread died"); |  | ||||||
|     } |  | ||||||
|     private void fastForwardBySkippingBytes(BufferedInputStream bis, long size) throws Exception{ |  | ||||||
|         long mustSkip = size; |  | ||||||
|         long skipped = 0; |  | ||||||
|         while (mustSkip > 0){ |  | ||||||
|             skipped += bis.skip(mustSkip); |  | ||||||
|             mustSkip = size - skipped; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     private void closeStreamOut(){ |  | ||||||
|         try { |  | ||||||
|             streamOut.close(); |  | ||||||
|         } |  | ||||||
|         catch (IOException e){ |  | ||||||
|             log.error("RomFsDecryptedProvider -> getContent(): Unable to close 'StreamOut'"); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,76 +0,0 @@ | ||||||
| /* |  | ||||||
|  * Copyright 2019-2022 Dmitry Isaenko |  | ||||||
|  * |  | ||||||
|  * This file is part of libKonogonka. |  | ||||||
|  * |  | ||||||
|  * libKonogonka 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. |  | ||||||
|  * |  | ||||||
|  * libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
| package libKonogonka.Tools.RomFs; |  | ||||||
| 
 |  | ||||||
| import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; |  | ||||||
| import libKonogonka.Tools.RomFs.view.FileMetaTablePlainView; |  | ||||||
| 
 |  | ||||||
| import java.io.*; |  | ||||||
| 
 |  | ||||||
| public class RomFsDecryptedProvider implements IRomFsProvider{ |  | ||||||
|     private final File file; |  | ||||||
|     private final long level6Offset; |  | ||||||
|     private final Level6Header level6Header; |  | ||||||
|     private final FileSystemEntry rootEntry; |  | ||||||
|     // Used only for debug |  | ||||||
|     private final byte[] directoryMetadataTable; |  | ||||||
|     private final byte[] fileMetadataTable; |  | ||||||
| 
 |  | ||||||
|     public RomFsDecryptedProvider(File decryptedFsImageFile, long level6offset) throws Exception{ |  | ||||||
|         RomFsDecryptedConstruct  construct = new RomFsDecryptedConstruct(decryptedFsImageFile, level6offset); |  | ||||||
|         this.file = decryptedFsImageFile; |  | ||||||
|         this.level6Offset = level6offset; |  | ||||||
|         this.level6Header = construct.getHeader(); |  | ||||||
|         this.rootEntry = construct.getRootEntry(); |  | ||||||
| 
 |  | ||||||
|         this.directoryMetadataTable = construct.getDirectoryMetadataTable(); |  | ||||||
|         this.fileMetadataTable = construct.getFileMetadataTable(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public File getFile() { return file; } |  | ||||||
|     @Override |  | ||||||
|     public long getLevel6Offset() { return level6Offset; } |  | ||||||
|     @Override |  | ||||||
|     public Level6Header getHeader() { return level6Header; } |  | ||||||
|     @Override |  | ||||||
|     public FileSystemEntry getRootEntry() { return rootEntry; } |  | ||||||
|     @Override |  | ||||||
|     public PipedInputStream getContent(FileSystemEntry entry) throws Exception{ |  | ||||||
|         if (entry.isDirectory()) |  | ||||||
|             throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); |  | ||||||
| 
 |  | ||||||
|         PipedOutputStream streamOut = new PipedOutputStream(); |  | ||||||
|         PipedInputStream streamIn = new PipedInputStream(streamOut); |  | ||||||
|         long internalFileRealPosition = level6Offset + level6Header.getFileDataOffset() + entry.getOffset(); |  | ||||||
|         long internalFileSize = entry.getSize(); |  | ||||||
| 
 |  | ||||||
|         Thread contentRetrievingThread = new Thread( |  | ||||||
|                 new RomFsDecryptedContentRetrieve(file, streamOut, internalFileRealPosition, internalFileSize)); |  | ||||||
|         contentRetrievingThread.start(); |  | ||||||
|         return streamIn; |  | ||||||
|     } |  | ||||||
|     @Override |  | ||||||
|     public void printDebug(){ |  | ||||||
|         level6Header.printDebugInfo(); |  | ||||||
|         new DirectoryMetaTablePlainView(level6Header.getDirectoryMetadataTableLength(), directoryMetadataTable); |  | ||||||
|         new FileMetaTablePlainView(level6Header.getFileMetadataTableLength(), fileMetadataTable); |  | ||||||
|         rootEntry.printTreeForDebug(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -1,180 +0,0 @@ | ||||||
| /* |  | ||||||
|     Copyright 2018-2022 Dmitry Isaenko |  | ||||||
|       |  | ||||||
|     This file is part of libKonogonka. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
| package libKonogonka.Tools.RomFs; |  | ||||||
| 
 |  | ||||||
| import libKonogonka.ctraes.AesCtrDecryptSimple; |  | ||||||
| 
 |  | ||||||
| import java.io.File; |  | ||||||
| import java.io.RandomAccessFile; |  | ||||||
| import java.util.Arrays; |  | ||||||
| 
 |  | ||||||
| public class RomFsEncryptedConstruct { |  | ||||||
| 
 |  | ||||||
|     private final long absoluteOffsetPosition; |  | ||||||
|     private final long level6Offset; |  | ||||||
| 
 |  | ||||||
|     private final RandomAccessFile raf; |  | ||||||
|     private final AesCtrDecryptSimple decryptor; |  | ||||||
|     private Level6Header header; |  | ||||||
|     private byte[] directoryMetadataTable; |  | ||||||
|     private byte[] fileMetadataTable; |  | ||||||
|     private FileSystemEntry rootEntry; |  | ||||||
| 
 |  | ||||||
|     RomFsEncryptedConstruct(File encryptedFsImageFile, |  | ||||||
|                             long romFsOffset, |  | ||||||
|                             long level6Offset, |  | ||||||
|                             AesCtrDecryptSimple decryptor, |  | ||||||
|                             long mediaStartOffset |  | ||||||
|                             ) throws Exception { |  | ||||||
| 
 |  | ||||||
|         if (level6Offset < 0) |  | ||||||
|             throw new Exception("Incorrect Level 6 Offset"); |  | ||||||
| 
 |  | ||||||
|         this.raf = new RandomAccessFile(encryptedFsImageFile, "r"); |  | ||||||
|         this.level6Offset = level6Offset; |  | ||||||
|         this.absoluteOffsetPosition = romFsOffset + (mediaStartOffset * 0x200); |  | ||||||
|         this.decryptor = decryptor; |  | ||||||
|         raf.seek(absoluteOffsetPosition + level6Offset); |  | ||||||
| 
 |  | ||||||
|         //Go to Level 6 header position |  | ||||||
|         decryptor.skipNext(level6Offset / 0x200); |  | ||||||
| 
 |  | ||||||
|         constructHeader(); |  | ||||||
| 
 |  | ||||||
|         directoryMetadataTableLengthCheck(); |  | ||||||
|         directoryMetadataTableConstruct(); |  | ||||||
| 
 |  | ||||||
|         fileMetadataTableLengthCheck(); |  | ||||||
|         fileMetadataTableConstruct(); |  | ||||||
| 
 |  | ||||||
|         constructRootFilesystemEntry(); |  | ||||||
| 
 |  | ||||||
|         raf.close(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void constructHeader() throws Exception{ |  | ||||||
|         // Decrypt data |  | ||||||
|         byte[] encryptedBlock = new byte[0x200]; |  | ||||||
|         byte[] decryptedBlock; |  | ||||||
|         if (raf.read(encryptedBlock) == 0x200) |  | ||||||
|             decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|         else |  | ||||||
|             throw new Exception("Failed to read header header (0x200 - block)"); |  | ||||||
|         this.header = new Level6Header(decryptedBlock); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void directoryMetadataTableLengthCheck() throws Exception{ |  | ||||||
|         if (header.getDirectoryMetadataTableLength() < 0) |  | ||||||
|             throw new Exception("Not supported: DirectoryMetadataTableLength < 0"); |  | ||||||
|     } |  | ||||||
|     private void directoryMetadataTableConstruct() throws Exception{ |  | ||||||
|         directoryMetadataTable = readMetaTable(header.getDirectoryMetadataTableOffset(), |  | ||||||
|                 header.getDirectoryMetadataTableLength()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void fileMetadataTableLengthCheck() throws Exception{ |  | ||||||
|         if (header.getFileMetadataTableLength() < 0) |  | ||||||
|             throw new Exception("Not supported: FileMetadataTableLength < 0"); |  | ||||||
|     } |  | ||||||
|     private void fileMetadataTableConstruct() throws Exception{ |  | ||||||
|         fileMetadataTable = readMetaTable(header.getFileMetadataTableOffset(), |  | ||||||
|                 header.getFileMetadataTableLength()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private void constructRootFilesystemEntry() throws Exception{ |  | ||||||
|         rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     private byte[] readMetaTable(long metaOffset, |  | ||||||
|                                  long metaSize) throws Exception{ |  | ||||||
|         byte[] encryptedBlock; |  | ||||||
|         byte[] decryptedBlock; |  | ||||||
|         byte[] metadataTable = new byte[(int) metaSize]; |  | ||||||
|         //0 |  | ||||||
|         decryptor.reset(); |  | ||||||
| 
 |  | ||||||
|         long startBlock = metaOffset / 0x200; |  | ||||||
| 
 |  | ||||||
|         decryptor.skipNext(level6Offset / 0x200 + startBlock); |  | ||||||
| 
 |  | ||||||
|         raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200); |  | ||||||
| 
 |  | ||||||
|         //1 |  | ||||||
|         long ignoreBytes = metaOffset - startBlock * 0x200; |  | ||||||
|         long currentPosition = 0; |  | ||||||
| 
 |  | ||||||
|         if (ignoreBytes > 0) { |  | ||||||
|             encryptedBlock = new byte[0x200]; |  | ||||||
|             if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                 decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                 // If we have extra-small file that is less than a block and even more |  | ||||||
|                 if ((0x200 - ignoreBytes) > metaSize){ |  | ||||||
|                     metadataTable = Arrays.copyOfRange(decryptedBlock, (int)ignoreBytes, 0x200); |  | ||||||
|                     return metadataTable; |  | ||||||
|                 } |  | ||||||
|                 else { |  | ||||||
|                     System.arraycopy(decryptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes); |  | ||||||
|                     currentPosition = 0x200 - ignoreBytes; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             else { |  | ||||||
|                 throw new Exception("Unable to get 512 bytes from 1st bock for Directory/File Metadata Table"); |  | ||||||
|             } |  | ||||||
|             startBlock++; |  | ||||||
|         } |  | ||||||
|         long endBlock = (metaSize + ignoreBytes) / 0x200 + startBlock;  // <- pointing to place where any data related to this media-block ends |  | ||||||
| 
 |  | ||||||
|         //2 |  | ||||||
|         int extraData = (int) ((endBlock - startBlock)*0x200 - (metaSize + ignoreBytes)); |  | ||||||
| 
 |  | ||||||
|         if (extraData < 0) |  | ||||||
|             endBlock--; |  | ||||||
|         //3 |  | ||||||
|         while ( startBlock < endBlock ) { |  | ||||||
|             encryptedBlock = new byte[0x200]; |  | ||||||
|             if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                 decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                 System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, 0x200); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|                 throw new Exception("Unable to get 512 bytes from block for Directory/File Metadata Table"); |  | ||||||
| 
 |  | ||||||
|             startBlock++; |  | ||||||
|             currentPosition += 0x200; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         //4 |  | ||||||
|         if (extraData != 0){                 // In case we didn't get what we want |  | ||||||
|             encryptedBlock = new byte[0x200]; |  | ||||||
|             if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                 decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                 System.arraycopy(decryptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData)); |  | ||||||
|             } |  | ||||||
|             else |  | ||||||
|                 throw new Exception("Unable to get 512 bytes from block for Directory/File Metadata Table"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return metadataTable; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     Level6Header getHeader() { return header; } |  | ||||||
|     FileSystemEntry getRootEntry(){ return rootEntry; } |  | ||||||
|     byte[] getDirectoryMetadataTable() { return directoryMetadataTable; } |  | ||||||
|     byte[] getFileMetadataTable() { return fileMetadataTable;} |  | ||||||
| } |  | ||||||
|  | @ -1,139 +0,0 @@ | ||||||
| /* |  | ||||||
|     Copyright 2018-2022 Dmitry Isaenko |  | ||||||
|       |  | ||||||
|     This file is part of libKonogonka. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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. |  | ||||||
| 
 |  | ||||||
|     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
| package libKonogonka.Tools.RomFs; |  | ||||||
| 
 |  | ||||||
| import libKonogonka.ctraes.AesCtrDecryptSimple; |  | ||||||
| import org.apache.logging.log4j.LogManager; |  | ||||||
| import org.apache.logging.log4j.Logger; |  | ||||||
| 
 |  | ||||||
| import java.io.File; |  | ||||||
| import java.io.PipedOutputStream; |  | ||||||
| import java.io.RandomAccessFile; |  | ||||||
| 
 |  | ||||||
| public class RomFsEncryptedContentRetrieve implements Runnable{ |  | ||||||
|     private final static Logger log = LogManager.getLogger(RomFsEncryptedContentRetrieve.class); |  | ||||||
| 
 |  | ||||||
|     private final File parentFile; |  | ||||||
|     private final PipedOutputStream streamOut; |  | ||||||
|     private final long absoluteOffsetPosition; |  | ||||||
|     private final AesCtrDecryptSimple decryptor; |  | ||||||
|     private final long internalFileOffset; |  | ||||||
|     private final long internalFileSize; |  | ||||||
|     private final long level6Offset; |  | ||||||
|     private final long headersFileDataOffset; |  | ||||||
| 
 |  | ||||||
|     RomFsEncryptedContentRetrieve(File parentFile, |  | ||||||
|                                   PipedOutputStream streamOut, |  | ||||||
|                                   long absoluteOffsetPosition, |  | ||||||
|                                   AesCtrDecryptSimple decryptor, |  | ||||||
|                                   long internalFileOffset, |  | ||||||
|                                   long internalFileSize, |  | ||||||
|                                   long level6Offset, |  | ||||||
|                                   long headersFileDataOffset |  | ||||||
|                                   ){ |  | ||||||
|         log.fatal("Current implementation works incorrectly"); |  | ||||||
|         this.parentFile = parentFile; |  | ||||||
|         this.absoluteOffsetPosition = absoluteOffsetPosition; |  | ||||||
|         this.streamOut = streamOut; |  | ||||||
|         this.decryptor = decryptor; |  | ||||||
|         this.internalFileOffset = internalFileOffset; |  | ||||||
|         this.internalFileSize = internalFileSize; |  | ||||||
|         this.level6Offset = level6Offset; |  | ||||||
|         this.headersFileDataOffset = headersFileDataOffset; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |  | ||||||
|     public void run() { |  | ||||||
|         log.trace("Executing thread"); |  | ||||||
|         try { |  | ||||||
|             byte[] encryptedBlock; |  | ||||||
|             byte[] decryptedBlock; |  | ||||||
| 
 |  | ||||||
|             RandomAccessFile raf = new RandomAccessFile(parentFile, "r"); |  | ||||||
| 
 |  | ||||||
|             //0 |  | ||||||
|             long startBlock = (internalFileOffset + headersFileDataOffset) / 0x200; |  | ||||||
| 
 |  | ||||||
|             decryptor.skipNext(level6Offset / 0x200 + startBlock); |  | ||||||
| 
 |  | ||||||
|             // long absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200); // calculated in constructor |  | ||||||
| 
 |  | ||||||
|             raf.seek(absoluteOffsetPosition + level6Offset + startBlock * 0x200); |  | ||||||
| 
 |  | ||||||
|             //1 |  | ||||||
|             long ignoreBytes = (internalFileOffset + headersFileDataOffset) - startBlock * 0x200; |  | ||||||
| 
 |  | ||||||
|             if (ignoreBytes > 0) { |  | ||||||
|                 encryptedBlock = new byte[0x200]; |  | ||||||
|                 if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                     decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                     // If we have extra-small file that is less than a block and even more |  | ||||||
|                     if ((0x200 - ignoreBytes) > internalFileSize){ |  | ||||||
|                         streamOut.write(decryptedBlock, (int)ignoreBytes, (int) internalFileSize);    // safe cast |  | ||||||
|                         raf.close(); |  | ||||||
|                         streamOut.close(); |  | ||||||
|                         return; |  | ||||||
|                     } |  | ||||||
|                     else { |  | ||||||
|                         streamOut.write(decryptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 else { |  | ||||||
|                     throw new Exception("Unable to get 512 bytes from 1st bock"); |  | ||||||
|                 } |  | ||||||
|                 startBlock++; |  | ||||||
|             } |  | ||||||
|             long endBlock = (internalFileSize + ignoreBytes) / 0x200 + startBlock;  // <- pointing to place where any data related to this media-block ends |  | ||||||
| 
 |  | ||||||
|             //2 |  | ||||||
|             int extraData = (int) ((endBlock - startBlock)*0x200 - (internalFileSize + ignoreBytes)); |  | ||||||
| 
 |  | ||||||
|             if (extraData < 0) |  | ||||||
|                 endBlock--; |  | ||||||
|             //3 |  | ||||||
|             while ( startBlock < endBlock ) { |  | ||||||
|                 encryptedBlock = new byte[0x200]; |  | ||||||
|                 if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                     decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                     streamOut.write(decryptedBlock); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                     throw new Exception("Unable to get 512 bytes from block"); |  | ||||||
| 
 |  | ||||||
|                 startBlock++; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             //4 |  | ||||||
|             if (extraData != 0){                 // In case we didn't get what we want |  | ||||||
|                 encryptedBlock = new byte[0x200]; |  | ||||||
|                 if (raf.read(encryptedBlock) == 0x200) { |  | ||||||
|                     decryptedBlock = decryptor.decryptNext(encryptedBlock); |  | ||||||
|                     streamOut.write(decryptedBlock, 0, Math.abs(extraData)); |  | ||||||
|                 } |  | ||||||
|                 else |  | ||||||
|                     throw new Exception("Unable to get 512 bytes from block"); |  | ||||||
|             } |  | ||||||
|             raf.close(); |  | ||||||
|             streamOut.close(); |  | ||||||
|         } catch (Exception exception) { |  | ||||||
|             log.error("Unable to provide stream", exception); |  | ||||||
|         } |  | ||||||
|         log.trace("Thread died"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -16,7 +16,6 @@ | ||||||
|  * You should have received a copy of the GNU General Public License |  * You should have received a copy of the GNU General Public License | ||||||
|  * along with libKonogonka.  If not, see <https://www.gnu.org/licenses/>. |  * along with libKonogonka.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| 
 |  | ||||||
| package libKonogonka.Tools.RomFs; | package libKonogonka.Tools.RomFs; | ||||||
| 
 | 
 | ||||||
| import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; | import libKonogonka.Tools.RomFs.view.DirectoryMetaTablePlainView; | ||||||
|  | @ -27,36 +26,39 @@ import java.io.File; | ||||||
| import java.io.PipedInputStream; | import java.io.PipedInputStream; | ||||||
| import java.io.PipedOutputStream; | import java.io.PipedOutputStream; | ||||||
| 
 | 
 | ||||||
| public class RomFsEncryptedProvider implements IRomFsProvider{ | public class RomFsProvider implements IRomFsProvider{ | ||||||
|     private final File file; |     private final File file; | ||||||
|     private final long level6Offset; |     private final long level6Offset; | ||||||
|     private final Level6Header level6Header; |     private final Level6Header level6Header; | ||||||
|     private final FileSystemEntry rootEntry; |     private final FileSystemEntry rootEntry; | ||||||
| 
 | 
 | ||||||
|     private final byte[] key;               // Used @ createDecryptor only |     private long ncaOffsetPosition; | ||||||
|     private final byte[] sectionCTR;        // Used @ createDecryptor only |     private byte[] key;               // Used @ createDecryptor only | ||||||
|     private final long mediaStartOffset;    // Used @ createDecryptor only |     private byte[] sectionCTR;        // Used @ createDecryptor only | ||||||
|     private final long absoluteOffsetPosition; |     private long mediaStartOffset;    // Used @ createDecryptor only | ||||||
| 
 |     private long mediaEndOffset; | ||||||
|     //private long mediaEndOffset;    // We know this, but actually never use |  | ||||||
| 
 |  | ||||||
|     // Used only for debug |     // Used only for debug | ||||||
|     private final byte[] directoryMetadataTable; |     private final byte[] directoryMetadataTable; | ||||||
|     private final byte[] fileMetadataTable; |     private final byte[] fileMetadataTable; | ||||||
| 
 | 
 | ||||||
|     public RomFsEncryptedProvider(long level6Offset, |     private final boolean encryptedAesCtr; | ||||||
|                                   File encryptedFsImageFile, | 
 | ||||||
|                                   long romFsOffsetPosition, |     public RomFsProvider(File decryptedFsImageFile, long level6offset) throws Exception{ | ||||||
|                                   byte[] key, |         RomFsConstruct construct = new RomFsConstruct(decryptedFsImageFile, level6offset); | ||||||
|                                   byte[] sectionCTR, |         this.file = decryptedFsImageFile; | ||||||
|                                   long mediaStartOffset |         this.level6Offset = level6offset; | ||||||
|     ) throws Exception{ |         this.level6Header = construct.getHeader(); | ||||||
|         this(level6Offset, encryptedFsImageFile, romFsOffsetPosition, key, sectionCTR, mediaStartOffset, -1); |         this.rootEntry = construct.getRootEntry(); | ||||||
|  | 
 | ||||||
|  |         this.directoryMetadataTable = construct.getDirectoryMetadataTable(); | ||||||
|  |         this.fileMetadataTable = construct.getFileMetadataTable(); | ||||||
|  | 
 | ||||||
|  |         this.encryptedAesCtr = false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public RomFsEncryptedProvider(long level6Offset, |     public RomFsProvider(long level6Offset, | ||||||
|                          File encryptedFsImageFile, |                          File encryptedFsImageFile, | ||||||
|                                   long romFsOffsetPosition, |                          long ncaOffsetPosition, | ||||||
|                          byte[] key, |                          byte[] key, | ||||||
|                          byte[] sectionCTR, |                          byte[] sectionCTR, | ||||||
|                          long mediaStartOffset, |                          long mediaStartOffset, | ||||||
|  | @ -65,24 +67,29 @@ public class RomFsEncryptedProvider implements IRomFsProvider{ | ||||||
|         this.key = key; |         this.key = key; | ||||||
|         this.sectionCTR = sectionCTR; |         this.sectionCTR = sectionCTR; | ||||||
|         this.mediaStartOffset = mediaStartOffset; |         this.mediaStartOffset = mediaStartOffset; | ||||||
|  |         this.mediaEndOffset = mediaEndOffset; | ||||||
|  |         this.ncaOffsetPosition = ncaOffsetPosition; | ||||||
| 
 | 
 | ||||||
|         RomFsEncryptedConstruct construct = new RomFsEncryptedConstruct(encryptedFsImageFile, |         RomFsConstruct construct = new RomFsConstruct(encryptedFsImageFile, | ||||||
|                 romFsOffsetPosition, |                 ncaOffsetPosition, | ||||||
|                 level6Offset, |                 level6Offset, | ||||||
|                 createDecryptor(), |                 createDecryptor(), | ||||||
|                 mediaStartOffset); |                 mediaStartOffset, | ||||||
|  |                 mediaEndOffset); | ||||||
|         this.file = encryptedFsImageFile; |         this.file = encryptedFsImageFile; | ||||||
|         this.level6Offset = level6Offset; |         this.level6Offset = level6Offset; | ||||||
|         this.level6Header = construct.getHeader(); |         this.level6Header = construct.getHeader(); | ||||||
|         this.rootEntry = construct.getRootEntry(); |         this.rootEntry = construct.getRootEntry(); | ||||||
|         this.absoluteOffsetPosition = romFsOffsetPosition + (mediaStartOffset * 0x200); |  | ||||||
| 
 | 
 | ||||||
|         this.directoryMetadataTable = construct.getDirectoryMetadataTable(); |         this.directoryMetadataTable = construct.getDirectoryMetadataTable(); | ||||||
|         this.fileMetadataTable = construct.getFileMetadataTable(); |         this.fileMetadataTable = construct.getFileMetadataTable(); | ||||||
|  | 
 | ||||||
|  |         this.encryptedAesCtr = true; | ||||||
|     } |     } | ||||||
|     private AesCtrDecryptSimple createDecryptor() throws Exception{ |     private AesCtrDecryptSimple createDecryptor() throws Exception{ | ||||||
|         return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); |         return new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public File getFile() { return file; } |     public File getFile() { return file; } | ||||||
|     @Override |     @Override | ||||||
|  | @ -92,7 +99,12 @@ public class RomFsEncryptedProvider implements IRomFsProvider{ | ||||||
|     @Override |     @Override | ||||||
|     public FileSystemEntry getRootEntry() { return rootEntry; } |     public FileSystemEntry getRootEntry() { return rootEntry; } | ||||||
|     @Override |     @Override | ||||||
|     public PipedInputStream getContent(FileSystemEntry entry) throws Exception{ |     public PipedInputStream getContent(FileSystemEntry entry) throws Exception { | ||||||
|  |         if (encryptedAesCtr) | ||||||
|  |             return getContentAesCtrEncrypted(entry); | ||||||
|  |         return getContentNonEncrypted(entry); | ||||||
|  |     } | ||||||
|  |     public PipedInputStream getContentAesCtrEncrypted(FileSystemEntry entry) throws Exception{ | ||||||
|         if (entry.isDirectory()) |         if (entry.isDirectory()) | ||||||
|             throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); |             throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); | ||||||
| 
 | 
 | ||||||
|  | @ -101,19 +113,38 @@ public class RomFsEncryptedProvider implements IRomFsProvider{ | ||||||
|         long internalFileOffset = entry.getOffset(); |         long internalFileOffset = entry.getOffset(); | ||||||
|         long internalFileSize = entry.getSize(); |         long internalFileSize = entry.getSize(); | ||||||
| 
 | 
 | ||||||
|         Thread contentRetrievingThread = new Thread(new RomFsEncryptedContentRetrieve( |         Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve( | ||||||
|                 file, |                 file, | ||||||
|                 streamOut, |                 streamOut, | ||||||
|                 absoluteOffsetPosition, |  | ||||||
|                 createDecryptor(), |                 createDecryptor(), | ||||||
|                 internalFileOffset, |                 internalFileOffset, | ||||||
|                 internalFileSize, |                 internalFileSize, | ||||||
|  |                 level6Header.getFileDataOffset(), | ||||||
|                 level6Offset, |                 level6Offset, | ||||||
|                 level6Header.getFileDataOffset() |                 ncaOffsetPosition, | ||||||
|         )); |                 mediaStartOffset, | ||||||
|  |                 mediaEndOffset)); | ||||||
|         contentRetrievingThread.start(); |         contentRetrievingThread.start(); | ||||||
|         return streamIn; |         return streamIn; | ||||||
|     } |     } | ||||||
|  |     public PipedInputStream getContentNonEncrypted(FileSystemEntry entry) throws Exception{ | ||||||
|  |         if (entry.isDirectory()) | ||||||
|  |             throw new Exception("Request of the binary stream for the folder entry is not supported (and doesn't make sense)."); | ||||||
|  | 
 | ||||||
|  |         PipedOutputStream streamOut = new PipedOutputStream(); | ||||||
|  |         PipedInputStream streamIn = new PipedInputStream(streamOut); | ||||||
|  |         long internalFileRealPosition = level6Offset + level6Header.getFileDataOffset() + entry.getOffset(); | ||||||
|  |         long internalFileSize = entry.getSize(); | ||||||
|  | 
 | ||||||
|  |         Thread contentRetrievingThread = new Thread(new RomFsContentRetrieve( | ||||||
|  |                 file, | ||||||
|  |                 streamOut, | ||||||
|  |                 internalFileRealPosition, | ||||||
|  |                 internalFileSize)); | ||||||
|  |         contentRetrievingThread.start(); | ||||||
|  |         return streamIn; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void printDebug(){ |     public void printDebug(){ | ||||||
|         level6Header.printDebugInfo(); |         level6Header.printDebugInfo(); | ||||||
|  | @ -22,6 +22,7 @@ import libKonogonka.Tools.ISuperProvider; | ||||||
| 
 | 
 | ||||||
| import java.io.*; | import java.io.*; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
|  | import java.nio.file.Files; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| 
 | 
 | ||||||
| import static libKonogonka.Converter.*; | import static libKonogonka.Converter.*; | ||||||
|  | @ -30,59 +31,54 @@ import static libKonogonka.Converter.*; | ||||||
|  * HFS0 |  * HFS0 | ||||||
|  * */ |  * */ | ||||||
| public class HFS0Provider implements ISuperProvider { | public class HFS0Provider implements ISuperProvider { | ||||||
|  |     private final String magic; | ||||||
|  |     private final int filesCount; | ||||||
|  |     private final byte[] padding; | ||||||
|  |     private final int stringTableSize; | ||||||
|  |     private final long rawFileDataStart; | ||||||
| 
 | 
 | ||||||
|     private boolean magicHFS0; |     private final HFS0File[] hfs0Files; | ||||||
|     private int filesCnt; |  | ||||||
|     private boolean paddingHfs0; |  | ||||||
|     private int stringTableSize; |  | ||||||
|     private long rawFileDataStart; |  | ||||||
| 
 | 
 | ||||||
|     private HFS0File[] hfs0Files; |     private final File file; | ||||||
| 
 |  | ||||||
|     private File file; |  | ||||||
| 
 | 
 | ||||||
|     HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{ |     HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{ | ||||||
|         this.file = file;    // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation. |         this.file = file;    // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation. | ||||||
|         byte[] hfs0bytes = new byte[16]; |         byte[] hfs0bytes = new byte[16]; | ||||||
|         try{ | 
 | ||||||
|         raf.seek(hfsOffsetPosition); |         raf.seek(hfsOffsetPosition); | ||||||
|         if (raf.read(hfs0bytes) != 16){ |         if (raf.read(hfs0bytes) != 16){ | ||||||
|             throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset."); |             throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset."); | ||||||
|         } |         } | ||||||
|         } |  | ||||||
|         catch (IOException ioe){ |  | ||||||
|             throw new Exception("Read HFS0 structure failure. Can't read first 16 bytes on requested offset: "+ioe.getMessage()); |  | ||||||
|         } |  | ||||||
|         magicHFS0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 0, 4),new byte[]{0x48, 0x46, 0x53, 0x30}); |  | ||||||
|         filesCnt = getLEint(hfs0bytes, 0x4); |  | ||||||
|         stringTableSize = getLEint(hfs0bytes, 8); |  | ||||||
|         paddingHfs0 = Arrays.equals(Arrays.copyOfRange(hfs0bytes, 12, 16),new byte[4]); |  | ||||||
| 
 | 
 | ||||||
|         hfs0Files = new HFS0File[filesCnt]; |         this.magic = new String(hfs0bytes, 0x0, 0x4, StandardCharsets.US_ASCII); | ||||||
|  |         this.filesCount = getLEint(hfs0bytes, 0x4); | ||||||
|  |         this.stringTableSize = getLEint(hfs0bytes, 8); | ||||||
|  |         this.padding = Arrays.copyOfRange(hfs0bytes, 12, 16); | ||||||
| 
 | 
 | ||||||
|         // TODO: IF NOT EMPTY TABLE: |         hfs0Files = new HFS0File[filesCount]; | ||||||
| 
 | 
 | ||||||
|         long[] offsetHfs0files = new long[filesCnt]; |         // TODO: IF NOT EMPTY TABLE: add validation | ||||||
|         long[] sizeHfs0files = new long[filesCnt]; | 
 | ||||||
|         int[] hashedRegionSizeHfs0Files = new int[filesCnt]; |         long[] offsetSubFile = new long[filesCount]; | ||||||
|         boolean[] paddingHfs0Files = new boolean[filesCnt]; |         long[] sizeSubFile = new long[filesCount]; | ||||||
|         byte[][] SHA256HashHfs0Files = new byte[filesCnt][]; |         int[] hashedRegionSubFile = new int[filesCount]; | ||||||
|         int[] strTableOffsets = new int[filesCnt]; |         boolean[] paddingSubFile = new boolean[filesCount]; | ||||||
|  |         byte[][] SHA256HashSubFile = new byte[filesCount][]; | ||||||
|  |         int[] stringTableOffsetSubFile = new int[filesCount]; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             // Populate meta information regarding each file inside (?) HFS0 |             // Populate meta information regarding each file inside (?) HFS0 | ||||||
|             byte[] metaInfoBytes = new byte[64]; |             byte[] metaInfoBytes = new byte[64]; | ||||||
|             for (int i=0; i < filesCnt; i++){ |             for (int i = 0; i < filesCount; i++){ | ||||||
|                 if (raf.read(metaInfoBytes) != 64) { |                 if (raf.read(metaInfoBytes) != 64) | ||||||
|                     throw new Exception("Read HFS0 File Entry Table failure for file # "+i); |                     throw new Exception("Read HFS0 File Entry Table failure for file # "+i); | ||||||
|                 } |                 offsetSubFile[i] = getLElong(metaInfoBytes, 0); | ||||||
|                 offsetHfs0files[i] = getLElong(metaInfoBytes, 0); |                 sizeSubFile[i] = getLElong(metaInfoBytes, 8); | ||||||
|                 sizeHfs0files[i] = getLElong(metaInfoBytes, 8); |                 hashedRegionSubFile[i] = getLEint(metaInfoBytes, 20); | ||||||
|                 hashedRegionSizeHfs0Files[i] = getLEint(metaInfoBytes, 20); |                 paddingSubFile[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]); | ||||||
|                 paddingHfs0Files[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]); |                 SHA256HashSubFile[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64); | ||||||
|                 SHA256HashHfs0Files[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64); |  | ||||||
| 
 | 
 | ||||||
|                 strTableOffsets[i] = getLEint(metaInfoBytes, 16); |                 stringTableOffsetSubFile[i] = getLEint(metaInfoBytes, 16); | ||||||
|             } |             } | ||||||
|             // Define location of actual data for this HFS0 |             // Define location of actual data for this HFS0 | ||||||
|             rawFileDataStart = raf.getFilePointer()+stringTableSize; |             rawFileDataStart = raf.getFilePointer()+stringTableSize; | ||||||
|  | @ -92,24 +88,24 @@ public class HFS0Provider implements ISuperProvider { | ||||||
|             if (raf.read(stringTbl) != stringTableSize){ |             if (raf.read(stringTbl) != stringTableSize){ | ||||||
|                 throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")"); |                 throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")"); | ||||||
|             } |             } | ||||||
|             String[] namesHfs0files = new String[filesCnt]; |             String[] namesSubFile = new String[filesCount]; | ||||||
|             // Parse string table |             // Parse string table | ||||||
|             for (int i=0; i < filesCnt; i++){ |             for (int i = 0; i < filesCount; i++){ | ||||||
|                 int j = 0; |                 int j = 0; | ||||||
|                 while (stringTbl[strTableOffsets[i]+j] != (byte)0x00) |                 while (stringTbl[stringTableOffsetSubFile[i]+j] != (byte)0x00) | ||||||
|                     j++; |                     j++; | ||||||
|                 namesHfs0files[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8); |                 namesSubFile[i] = new String(stringTbl, stringTableOffsetSubFile[i], j, StandardCharsets.UTF_8); | ||||||
|             } |             } | ||||||
|             //---------------------------------------------------------------------------------------------------------- |             //---------------------------------------------------------------------------------------------------------- | ||||||
|             // Set files |             // Set files | ||||||
|             for (int i=0; i < filesCnt; i++){ |             for (int i = 0; i < filesCount; i++){ | ||||||
|                 hfs0Files[i] = new HFS0File( |                 hfs0Files[i] = new HFS0File( | ||||||
|                         namesHfs0files[i], |                         namesSubFile[i], | ||||||
|                         offsetHfs0files[i], |                         offsetSubFile[i], | ||||||
|                         sizeHfs0files[i], |                         sizeSubFile[i], | ||||||
|                         hashedRegionSizeHfs0Files[i], |                         hashedRegionSubFile[i], | ||||||
|                         paddingHfs0Files[i], |                         paddingSubFile[i], | ||||||
|                         SHA256HashHfs0Files[i] |                         SHA256HashSubFile[i] | ||||||
|                 ); |                 ); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -118,15 +114,18 @@ public class HFS0Provider implements ISuperProvider { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean isMagicHFS0() { return magicHFS0; } |     public String getMagic() { return magic; } | ||||||
|     public int getFilesCnt() { return filesCnt; } |     public int getFilesCount() { return filesCount; } | ||||||
|     public boolean isPaddingHfs0() { return paddingHfs0; } |     public byte[] getPadding() { return padding; } | ||||||
|     public int getStringTableSize() { return stringTableSize; } |     public int getStringTableSize() { return stringTableSize; } | ||||||
|     @Override |     @Override | ||||||
|     public long getRawFileDataStart() { return rawFileDataStart; } |     public long getRawFileDataStart() { return rawFileDataStart; } | ||||||
|     public HFS0File[] getHfs0Files() { return hfs0Files; } |     public HFS0File[] getHfs0Files() { return hfs0Files; } | ||||||
|     @Override |     @Override | ||||||
|     public File getFile(){ return file; } |     public File getFile(){ return file; } | ||||||
|  |     /** | ||||||
|  |      * @deprecated | ||||||
|  |      * */ | ||||||
|     @Override |     @Override | ||||||
|     public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ |     public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ | ||||||
|         PipedOutputStream streamOut = new PipedOutputStream(); |         PipedOutputStream streamOut = new PipedOutputStream(); | ||||||
|  | @ -140,13 +139,11 @@ public class HFS0Provider implements ISuperProvider { | ||||||
|             System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread"); |             System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread"); | ||||||
|             try{ |             try{ | ||||||
|                 long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset(); |                 long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset(); | ||||||
|                 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); |                 BufferedInputStream bis = new BufferedInputStream(Files.newInputStream(file.toPath())); | ||||||
|                 if (bis.skip(subFileRealPosition) != subFileRealPosition) { |                 if (bis.skip(subFileRealPosition) != subFileRealPosition) | ||||||
|                     System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset"); |                     throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset"); | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576 |                 int readPice = 0x800000; // 8mb NOTE: consider switching to 1mb 1048576 | ||||||
| 
 | 
 | ||||||
|                 long readFrom = 0; |                 long readFrom = 0; | ||||||
|                 long realFileSize = hfs0Files[subFileNumber].getSize(); |                 long realFileSize = hfs0Files[subFileNumber].getSize(); | ||||||
|  | @ -157,17 +154,16 @@ public class HFS0Provider implements ISuperProvider { | ||||||
|                     if (realFileSize - readFrom < readPice) |                     if (realFileSize - readFrom < readPice) | ||||||
|                         readPice = Math.toIntExact(realFileSize - readFrom);    // it's safe, I guarantee |                         readPice = Math.toIntExact(realFileSize - readFrom);    // it's safe, I guarantee | ||||||
|                     readBuf = new byte[readPice]; |                     readBuf = new byte[readPice]; | ||||||
|                     if (bis.read(readBuf) != readPice) { |                     if (bis.read(readBuf) != readPice) | ||||||
|                         System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file."); |                         throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file."); | ||||||
|                         return; | 
 | ||||||
|                     } |  | ||||||
|                     streamOut.write(readBuf, 0, readPice); |                     streamOut.write(readBuf, 0, readPice); | ||||||
|                     readFrom += readPice; |                     readFrom += readPice; | ||||||
|                 } |                 } | ||||||
|                 bis.close(); |                 bis.close(); | ||||||
|                 streamOut.close(); |                 streamOut.close(); | ||||||
|             } |             } | ||||||
|             catch (IOException ioe){ |             catch (Exception ioe){ | ||||||
|                 System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream"); |                 System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream"); | ||||||
|                 ioe.printStackTrace(); |                 ioe.printStackTrace(); | ||||||
|             } |             } | ||||||
|  | @ -176,20 +172,17 @@ public class HFS0Provider implements ISuperProvider { | ||||||
|         workerThread.start(); |         workerThread.start(); | ||||||
|         return streamIn; |         return streamIn; | ||||||
|     } |     } | ||||||
| 
 |     //TODO | ||||||
|     @Override |     @Override | ||||||
|     public boolean exportContent(String saveToLocation, String subFileName) throws Exception { |     public boolean exportContent(String saveToLocation, String subFileName) throws Exception { | ||||||
|         return false; |         throw new Exception("Not implemented yet"); | ||||||
|     } |     } | ||||||
| 
 |     //TODO | ||||||
|     @Override |     @Override | ||||||
|     public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception { |     public boolean exportContent(String saveToLocation, int subFileNumber) throws Exception { | ||||||
|         return false; |         throw new Exception("Not implemented yet"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Sugar |  | ||||||
|      * */ |  | ||||||
|     @Override |     @Override | ||||||
|     public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { |     public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception { | ||||||
|         for (int i = 0; i < hfs0Files.length; i++){ |         for (int i = 0; i < hfs0Files.length; i++){ | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ public class XCIProvider{ | ||||||
|             xciGamecardCert = new XCIGamecardCert(gamecardCertBytes); |             xciGamecardCert = new XCIGamecardCert(gamecardCertBytes); | ||||||
| 
 | 
 | ||||||
|             hfs0ProviderMain = new HFS0Provider(0xf000, raf, file); |             hfs0ProviderMain = new HFS0Provider(0xf000, raf, file); | ||||||
|             if (hfs0ProviderMain.getFilesCnt() < 3){ |             if (hfs0ProviderMain.getFilesCount() < 3){ | ||||||
|                 raf.close(); |                 raf.close(); | ||||||
|                 throw new Exception("XCI Can't read Gamecard certificate bytes."); |                 throw new Exception("XCI Can't read Gamecard certificate bytes."); | ||||||
|             } |             } | ||||||
|  | @ -106,7 +106,7 @@ public class XCIProvider{ | ||||||
|             throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n  "+ioe.getMessage()); |             throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n  "+ioe.getMessage()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|     /* Getters */ |     /* API */ | ||||||
|     public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; } |     public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; } | ||||||
|     public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; } |     public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; } | ||||||
|     public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; } |     public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; } | ||||||
|  |  | ||||||
|  | @ -172,23 +172,14 @@ public class AesCtrBufferedInputStream extends BufferedInputStream { | ||||||
| 
 | 
 | ||||||
|                 long leftovers = realCountOfBytesToSkip % 0x200;           // most likely will be 0;  TODO: a lot of tests |                 long leftovers = realCountOfBytesToSkip % 0x200;           // most likely will be 0;  TODO: a lot of tests | ||||||
|                 long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers; |                 long bytesToSkipTillRequiredBlock = realCountOfBytesToSkip - leftovers; | ||||||
|                 long skipped = super.skip(bytesToSkipTillRequiredBlock); |                 skipLoop(bytesToSkipTillRequiredBlock); | ||||||
|                 if (bytesToSkipTillRequiredBlock != skipped) |  | ||||||
|                     throw new IOException("Can't skip bytes. To skip: " + |  | ||||||
|                             bytesToSkipTillRequiredBlock + |  | ||||||
|                             ".\nActually skipped: " + skipped + |  | ||||||
|                             ".\nLeftovers inside encrypted section: " + leftovers); |  | ||||||
|                 fillDecryptedCache(); |                 fillDecryptedCache(); | ||||||
|                 pseudoPos += n; |                 pseudoPos += n; | ||||||
|                 pointerInsideDecryptedSection = (int) leftovers; |                 pointerInsideDecryptedSection = (int) leftovers; | ||||||
|                 return n; |                 return n; | ||||||
|             } |             } | ||||||
|             log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); |             log.trace("4. Pointer Inside + End Position Outside Encrypted Section ("+pseudoPos+"-"+(pseudoPos+n)+")"); | ||||||
|             long skipped = super.skip(realCountOfBytesToSkip); |             skipLoop(realCountOfBytesToSkip); | ||||||
|             if (realCountOfBytesToSkip != skipped) |  | ||||||
|                 throw new IOException("Can't skip bytes. To skip: " + |  | ||||||
|                         realCountOfBytesToSkip + |  | ||||||
|                         ".\nActually skipped: " + skipped); |  | ||||||
|             pseudoPos += n; |             pseudoPos += n; | ||||||
|             pointerInsideDecryptedSection = 0; |             pointerInsideDecryptedSection = 0; | ||||||
|             return n; |             return n; | ||||||
|  | @ -217,12 +208,20 @@ public class AesCtrBufferedInputStream extends BufferedInputStream { | ||||||
|             return n; |             return n; | ||||||
|         } |         } | ||||||
|         log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")"); |         log.trace("6. Not encrypted ("+pseudoPos+"-"+(pseudoPos+n)+")"); | ||||||
|         long skipped = super.skip(n); |         skipLoop(n); | ||||||
|         pseudoPos += n; |         pseudoPos += n; | ||||||
|         pointerInsideDecryptedSection = 0; |         pointerInsideDecryptedSection = 0; | ||||||
|         return skipped; |         return n; | ||||||
|  |     } | ||||||
|  |     private void skipLoop(long size) throws IOException{ | ||||||
|  |         long mustSkip = size; | ||||||
|  |         long skipped = 0; | ||||||
|  |         while (mustSkip > 0){ | ||||||
|  |             skipped += super.skip(mustSkip); | ||||||
|  |             mustSkip = size - skipped; | ||||||
|  |             log.trace("Skip loop: skipped: "+skipped+"\tmustSkip "+mustSkip); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public synchronized int read() throws IOException { |     public synchronized int read() throws IOException { | ||||||
|         byte[] b = new byte[1]; |         byte[] b = new byte[1]; | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ package libKonogonka.RomFsDecrypted; | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.nio.file.Path; | import java.nio.file.Path; | ||||||
| 
 | 
 | ||||||
| import libKonogonka.Tools.RomFs.RomFsDecryptedProvider; | import libKonogonka.Tools.RomFs.RomFsProvider; | ||||||
| import org.junit.jupiter.api.Disabled; | import org.junit.jupiter.api.Disabled; | ||||||
| import org.junit.jupiter.api.DisplayName; | import org.junit.jupiter.api.DisplayName; | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
|  | @ -36,7 +36,7 @@ public class RomFsDecryptedTest { | ||||||
|     private static final String decryptedFileAbsolutePath = "./FilesForTests/NCAContent_0 [lv6 147456].bin"; |     private static final String decryptedFileAbsolutePath = "./FilesForTests/NCAContent_0 [lv6 147456].bin"; | ||||||
|     private File decryptedFile; |     private File decryptedFile; | ||||||
|     long lv6offset; |     long lv6offset; | ||||||
|     RomFsDecryptedProvider provider; |     RomFsProvider provider; | ||||||
| 
 | 
 | ||||||
|     @Disabled |     @Disabled | ||||||
|     @DisplayName("RomFsDecryptedProvider: tests") |     @DisplayName("RomFsDecryptedProvider: tests") | ||||||
|  | @ -55,7 +55,7 @@ public class RomFsDecryptedTest { | ||||||
|         lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", "")); |         lv6offset = Long.parseLong(decryptedFile.getName().replaceAll("(^.*lv6\\s)|(]\\.bin)", "")); | ||||||
|     } |     } | ||||||
|     void makeProvider() throws Exception{ |     void makeProvider() throws Exception{ | ||||||
|         provider = new RomFsDecryptedProvider(decryptedFile, lv6offset); |         provider = new RomFsProvider(decryptedFile, lv6offset); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  |  | ||||||
|  | @ -117,7 +117,7 @@ public class RomFsEncryptedTest { | ||||||
| 
 | 
 | ||||||
|         exportFolderContent(entry, "/tmp/brandnew"); |         exportFolderContent(entry, "/tmp/brandnew"); | ||||||
|         //---------------------------------------------------------------------- |         //---------------------------------------------------------------------- | ||||||
|         exportFolderContentLegacy(entry, "/tmp/legacy"); |         // exportFolderContentLegacy(entry, "/tmp/legacy"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{ |     private void exportFolderContent(FileSystemEntry entry, String saveToLocation) throws Exception{ | ||||||
|  | @ -195,7 +195,7 @@ public class RomFsEncryptedTest { | ||||||
|     private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception { |     private void exportSingleFileLegacy(FileSystemEntry entry, String saveToLocation) throws Exception { | ||||||
|         File contentFile = new File(saveToLocation + entry.getName()); |         File contentFile = new File(saveToLocation + entry.getName()); | ||||||
| 
 | 
 | ||||||
|         BufferedOutputStream extractedFileBOS = new BufferedOutputStream(new FileOutputStream(contentFile)); |         BufferedOutputStream extractedFileBOS = new BufferedOutputStream(Files.newOutputStream(contentFile.toPath())); | ||||||
|         PipedInputStream pis = ncaProvider.getNCAContentProvider(1).getRomfs().getContent(entry); |         PipedInputStream pis = ncaProvider.getNCAContentProvider(1).getRomfs().getContent(entry); | ||||||
| 
 | 
 | ||||||
|         byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 |         byte[] readBuf = new byte[0x200]; // 8mb NOTE: consider switching to 1mb 1048576 | ||||||
|  |  | ||||||
							
								
								
									
										66
									
								
								src/test/java/libKonogonka/RomFsDecrypted/XciTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/test/java/libKonogonka/RomFsDecrypted/XciTest.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | /* | ||||||
|  |     Copyright 2018-2022 Dmitry Isaenko | ||||||
|  |       | ||||||
|  |     This file is part of libKonogonka. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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. | ||||||
|  | 
 | ||||||
|  |     libKonogonka 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 libKonogonka.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  */ | ||||||
|  | package libKonogonka.RomFsDecrypted; | ||||||
|  | 
 | ||||||
|  | import libKonogonka.Tools.XCI.XCIProvider; | ||||||
|  | import org.junit.jupiter.api.Disabled; | ||||||
|  | import org.junit.jupiter.api.DisplayName; | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  | 
 | ||||||
|  | import java.io.BufferedReader; | ||||||
|  | import java.io.File; | ||||||
|  | import java.io.FileReader; | ||||||
|  | 
 | ||||||
|  | // log.fatal("Configuration File Defined To Be :: "+System.getProperty("log4j.configurationFile")); | ||||||
|  | 
 | ||||||
|  | public class XciTest { | ||||||
|  |     private static final String xci_header_keyFileLocation = "./FilesForTests/xci_header_key.txt"; | ||||||
|  |     private static final String decryptedFileAbsolutePath = "./FilesForTests/sample.xci"; | ||||||
|  |     private File xciFile; | ||||||
|  |     XCIProvider provider; | ||||||
|  |     String xci_header_key; | ||||||
|  | 
 | ||||||
|  |     @Disabled | ||||||
|  |     @DisplayName("RomFsDecryptedProvider: tests") | ||||||
|  |     @Test | ||||||
|  |     void romFsValidation() throws Exception{ | ||||||
|  |         makeFile(); | ||||||
|  |         getXciHeaderKey(); | ||||||
|  |         makeProvider(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void getXciHeaderKey() throws Exception{ | ||||||
|  |         BufferedReader br = new BufferedReader(new FileReader(xci_header_keyFileLocation)); | ||||||
|  |         xci_header_key = br.readLine(); | ||||||
|  |         br.close(); | ||||||
|  | 
 | ||||||
|  |         if (xci_header_key == null) | ||||||
|  |             throw new Exception("Unable to retrieve xci_header_key"); | ||||||
|  | 
 | ||||||
|  |         xci_header_key = xci_header_key.trim(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void makeFile(){ | ||||||
|  |         xciFile = new File(decryptedFileAbsolutePath); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     void makeProvider() throws Exception{ | ||||||
|  |         provider = new XCIProvider(xciFile, xci_header_key); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in a new issue
	
	 Dmitry Isaenko
						Dmitry Isaenko