Remove all back end things to separate library. Update copyrights.
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
6623a553d0
commit
27b7ba5e7f
87 changed files with 138 additions and 5307 deletions
21
README.md
21
README.md
|
@ -8,6 +8,8 @@ Nightly builds could be found somewhere on [redrise.ru](https://redrise.ru)
|
|||
|
||||
Deep WIP multi-tool to work with NS-specific files / filesystem images.
|
||||
|
||||
Front end to libKonogonka
|
||||
|
||||
### License
|
||||
|
||||
[GNU General Public License v3+](https://github.com/developersu/konogonka/blob/master/LICENSE)
|
||||
|
@ -19,15 +21,9 @@ Deep WIP multi-tool to work with NS-specific files / filesystem images.
|
|||
<img src="screenshots/7.png" alt="drawing" width="250"/>
|
||||
|
||||
### Used libraries & resources
|
||||
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
|
||||
* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes.
|
||||
* [OpenJFX](https://wiki.openjdk.java.net/display/OpenJFX/Main)
|
||||
* Few icons taken from: [materialdesignicons.com](http://materialdesignicons.com/)
|
||||
|
||||
#### Thanks
|
||||
* Switch brew wiki
|
||||
* Original ScriesM software
|
||||
* roothorick, [shchmue](https://github.com/shchmue/), He, other Team AtlasNX discord members for their advices, notes and examples!
|
||||
* See libKonogonka project for details
|
||||
|
||||
### System requirements
|
||||
|
||||
|
@ -42,9 +38,14 @@ JRE/JDK 8u60 or higher.
|
|||
|
||||
1. Install JDK
|
||||
2. Install Maven
|
||||
3. $ git clone https://github.com/developersu/konogonka.git
|
||||
4. $ mvn -B -DskipTests clean package
|
||||
5. $ java -jar target/konogonka-0.x.x-jar-with-dependencies.jar
|
||||
3. Install libKonogonka to local repository:
|
||||
4. $ git clone https://git.redrise.ru/desu/libKonogonka
|
||||
5. $ mvn -B -DskipTests clean package
|
||||
6. $ mvn install:install-file -Dfile=./target/libKonogonka-*-jar-with-dependencies.jar -DgroupId=ru.redrise -DartifactId=libKonogonka -Dversion=`grep -m 1 '<version>' pom.xml| sed -e 's/\s*.\/\?version>//g'
|
||||
` -Dpackaging=jar -DgeneratePom=true;
|
||||
7. $ git clone https://github.com/developersu/konogonka.git
|
||||
8. $ mvn -B -DskipTests clean package
|
||||
9. $ java -jar target/konogonka-0.x.x-jar-with-dependencies.jar
|
||||
|
||||
### Checklist
|
||||
|
||||
|
|
28
pom.xml
28
pom.xml
|
@ -8,7 +8,7 @@
|
|||
<name>konogonka</name>
|
||||
|
||||
<artifactId>konogonka</artifactId>
|
||||
<version>0.0.3-SNAPSHOT</version>
|
||||
<version>0.0.4-SNAPSHOT</version>
|
||||
|
||||
<url>https://github.com/developersu/${project.name}}/</url>
|
||||
<description>
|
||||
|
@ -137,19 +137,14 @@
|
|||
<classifier>mac</classifier>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- For AES XTS using bouncycastle -->
|
||||
<!-- Local entry of the libKonogonka: replace once deployed to maven central -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.54</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.jcip</groupId>
|
||||
<artifactId>jcip-annotations</artifactId>
|
||||
<version>1.0</version>
|
||||
<groupId>ru.redrise</groupId>
|
||||
<artifactId>libKonogonka</artifactId>
|
||||
<version>0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Consider removing
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
|
@ -166,6 +161,17 @@
|
|||
</dependencies>
|
||||
<build>
|
||||
<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources-filtered</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -26,7 +26,7 @@ import javafx.stage.Stage;
|
|||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Controllers.XML.XMLController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
|||
package konogonka.Controllers;
|
||||
|
||||
import javafx.fxml.Initializable;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -36,7 +36,7 @@ import konogonka.Controllers.XCI.XCIController;
|
|||
import konogonka.Controllers.XML.XMLController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Settings.SettingsWindow;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -24,9 +24,9 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.TextField;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
import konogonka.Tools.NCA.NCAProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.NCA.NCAContent;
|
||||
import libKonogonka.Tools.NCA.NCAProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -34,7 +34,7 @@ import java.net.URL;
|
|||
import java.util.HashMap;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NCAController implements ITabController {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -28,9 +28,9 @@ import javafx.scene.layout.VBox;
|
|||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.NSP.NSPController;
|
||||
import konogonka.Controllers.RFS.RomFsController;
|
||||
import konogonka.LoperConverter;
|
||||
import libKonogonka.LoperConverter;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
import libKonogonka.Tools.NCA.NCAContent;
|
||||
import konogonka.Workers.DumbNCA3ContentExtractor;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -22,9 +22,9 @@ import javafx.fxml.FXML;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.TitledPane;
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NCASectionHeaderBlockController {
|
||||
@FXML
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -20,9 +20,9 @@ package konogonka.Controllers.NCA;
|
|||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import konogonka.Tools.NCA.NCAHeaderTableEntry;
|
||||
import libKonogonka.Tools.NCA.NCAHeaderTableEntry;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NCATableController {
|
||||
@FXML
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
|
|||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import konogonka.Tools.NPDM.ACID.FSAccessControlProvider;
|
||||
import libKonogonka.Tools.NPDM.ACID.FSAccessControlProvider;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static konogonka.LoperConverter.longToOctString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.longToOctString;
|
||||
|
||||
public class FSAccessControlController implements Initializable {
|
||||
@FXML
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
|
|||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import konogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
|
||||
import libKonogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static konogonka.LoperConverter.longToOctString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.longToOctString;
|
||||
|
||||
public class FSAccessHeaderController implements Initializable {
|
||||
@FXML
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -24,7 +24,7 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.Separator;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import konogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
import libKonogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -23,17 +23,17 @@ import javafx.fxml.FXML;
|
|||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||
import konogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||
import konogonka.Tools.NPDM.NPDMProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||
import libKonogonka.Tools.NPDM.NPDMProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NPDMController implements ITabController {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -26,9 +26,9 @@ import konogonka.AppPreferences;
|
|||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
import konogonka.Workers.Extractor;
|
||||
|
||||
|
@ -37,7 +37,7 @@ import java.net.URL;
|
|||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class NSPController implements ITabController {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -34,8 +34,8 @@ import javafx.scene.input.KeyEvent;
|
|||
import javafx.scene.input.MouseButton;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -34,7 +34,7 @@ import javafx.scene.input.KeyCode;
|
|||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
|||
package konogonka.Controllers.RFS;
|
||||
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||
|
||||
public class RFSModelEntry implements IRowModel {
|
||||
private FileSystemEntry fileSystemEntry;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -25,11 +25,11 @@ import javafx.scene.layout.Region;
|
|||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||
import konogonka.Tools.RomFs.Level6Header;
|
||||
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||
import libKonogonka.Tools.RomFs.Level6Header;
|
||||
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
import konogonka.Workers.DumbRomFsExtractor;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -25,15 +25,15 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.TextField;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.TIK.TIKProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.TIK.TIKProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class TIKController implements ITabController {
|
||||
@FXML
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -26,8 +26,8 @@ import javafx.scene.control.TitledPane;
|
|||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.XCI.HFS0Provider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.XCI.HFS0Provider;
|
||||
import konogonka.Workers.Extractor;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -38,8 +38,8 @@ import javafx.scene.input.MouseEvent;
|
|||
import javafx.util.Callback;
|
||||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.XCI.HFS0Provider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.XCI.HFS0Provider;
|
||||
|
||||
|
||||
import java.net.URL;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -24,15 +24,15 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.TextField;
|
||||
import konogonka.AppPreferences;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.XCI.XCIProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.XCI.XCIProvider;
|
||||
import konogonka.Workers.Analyzer;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||
|
||||
public class XCIController implements ITabController {
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -22,7 +22,7 @@ import javafx.fxml.FXML;
|
|||
import javafx.scene.control.TextArea;
|
||||
import konogonka.Controllers.ITabController;
|
||||
import konogonka.MediatorControl;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public class LoperConverter {
|
||||
public static int getLEint(byte[] bytes, int fromOffset){
|
||||
return ByteBuffer.wrap(bytes, fromOffset, 0x4).order(ByteOrder.LITTLE_ENDIAN).getInt();
|
||||
}
|
||||
|
||||
public static long getLElong(byte[] bytes, int fromOffset){
|
||||
return ByteBuffer.wrap(bytes, fromOffset, 0x8).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
}
|
||||
/**
|
||||
* Convert int to long. Workaround to store unsigned int
|
||||
* @param bytes original array
|
||||
* @param fromOffset start position of the 4-bytes value
|
||||
* */
|
||||
public static long getLElongOfInt(byte[] bytes, int fromOffset){
|
||||
final byte[] holder = new byte[8];
|
||||
System.arraycopy(bytes, fromOffset, holder, 0, 4);
|
||||
return ByteBuffer.wrap(holder).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
}
|
||||
|
||||
public static String byteArrToHexString(byte[] bArr){
|
||||
if (bArr == null)
|
||||
return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b: bArr)
|
||||
sb.append(String.format("%02x", b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String longToOctString(long value){
|
||||
return String.format("%64s", Long.toBinaryString( value )).replace(' ', '0');
|
||||
}
|
||||
|
||||
public static byte[] flip(byte[] bytes){
|
||||
int size = bytes.length;
|
||||
byte[] ret = new byte[size];
|
||||
for (int i = 0; i < size; i++){
|
||||
ret[size-i-1] = bytes[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -27,10 +27,10 @@ import javafx.stage.Stage;
|
|||
import konogonka.Controllers.MainController;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class MainFx extends Application {
|
||||
public static final String appVersion = "v0.0.3";
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception{
|
||||
|
@ -40,17 +40,20 @@ public class MainFx extends Application {
|
|||
Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); // NOTE: user locale based on ISO3 Language codes
|
||||
ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale);
|
||||
|
||||
ResourceBundle rbFiltered = ResourceBundle.getBundle("app", userLocale);
|
||||
|
||||
|
||||
loader.setResources(rb);
|
||||
|
||||
Parent root = loader.load();
|
||||
|
||||
primaryStage.getIcons().addAll(
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")),
|
||||
new Image(getClass().getResourceAsStream("/res/app_icon128x128.png"))
|
||||
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon32x32.png"))),
|
||||
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon48x48.png"))),
|
||||
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon64x64.png"))),
|
||||
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon128x128.png")))
|
||||
);
|
||||
primaryStage.setTitle("konogonka "+appVersion);
|
||||
primaryStage.setTitle("konogonka "+rbFiltered.getString("_version"));
|
||||
|
||||
Scene mainScene = new Scene(root, 1200, 800);
|
||||
mainScene.getStylesheets().add("/res/app_light.css");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Debug tool like hexdump <3
|
||||
*/
|
||||
public class RainbowDump {
|
||||
private static final String ANSI_RESET = "\u001B[0m";
|
||||
private static final String ANSI_BLACK = "\u001B[30m";
|
||||
private static final String ANSI_RED = "\u001B[31m";
|
||||
private static final String ANSI_GREEN = "\u001B[32m";
|
||||
private static final String ANSI_YELLOW = "\u001B[33m";
|
||||
private static final String ANSI_BLUE = "\u001B[34m";
|
||||
private static final String ANSI_PURPLE = "\u001B[35m";
|
||||
private static final String ANSI_CYAN = "\u001B[36m";
|
||||
private static final String ANSI_WHITE = "\u001B[37m";
|
||||
|
||||
|
||||
public static void hexDumpUTF8(byte[] byteArray){
|
||||
if (byteArray == null || byteArray.length == 0)
|
||||
return;
|
||||
System.out.print(ANSI_BLUE);
|
||||
for (int i=0; i < byteArray.length; i++)
|
||||
System.out.print(String.format("%02d-", i%100));
|
||||
System.out.println(">"+ANSI_RED+byteArray.length+ANSI_RESET);
|
||||
for (byte b: byteArray)
|
||||
System.out.print(String.format("%02x ", b));
|
||||
System.out.println();
|
||||
System.out.print(new String(byteArray, StandardCharsets.UTF_8)+"\n");
|
||||
}
|
||||
|
||||
public static void octDumpInt(int value){
|
||||
System.out.println(String.format("%32s", Integer.toBinaryString( value )).replace(' ', '0')+" | "+value);
|
||||
}
|
||||
|
||||
public static void octDumpLong(long value){
|
||||
System.out.println(String.format("%64s", Long.toBinaryString( value )).replace(' ', '0')+" | "+value);
|
||||
}
|
||||
|
||||
public static String formatDecHexString(long value){
|
||||
return String.format("%-20d 0x%x", value, value);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
/**
|
||||
* Any class of this type must be able to accept data from stream (and file as any other).
|
||||
* */
|
||||
|
||||
public abstract class ASuperInFileProvider {
|
||||
protected byte[] readFromStream(PipedInputStream pis, int size) throws IOException {
|
||||
byte[] buffer = new byte[size];
|
||||
int startingPos = 0;
|
||||
int readCnt;
|
||||
while (size > 0){
|
||||
readCnt = pis.read(buffer, startingPos, size);
|
||||
if (readCnt == -1)
|
||||
return null;
|
||||
startingPos += readCnt;
|
||||
size -= readCnt;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
/**
|
||||
* Any class of this type must provide streams
|
||||
* */
|
||||
public interface ISuperProvider {
|
||||
PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception;
|
||||
PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception;
|
||||
|
||||
File getFile();
|
||||
long getRawFileDataStart();
|
||||
}
|
|
@ -1,443 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
||||
import konogonka.Tools.PFS0.PFS0EncryptedProvider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||
import konogonka.Tools.RomFs.RomFsEncryptedProvider;
|
||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||
import konogonka.exceptions.EmptySectionException;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.LinkedList;
|
||||
/**
|
||||
* THIS CLASS BECOMES MORE UGLY AFTER EACH ITERATION OF REFACTORING.
|
||||
* TODO: MAKE SOME DECOMPOSITION
|
||||
* */
|
||||
public class NCAContent {
|
||||
private File file;
|
||||
private long offsetPosition;
|
||||
private NCASectionBlock ncaSectionBlock;
|
||||
private NCAHeaderTableEntry ncaHeaderTableEntry;
|
||||
private byte[] decryptedKey;
|
||||
|
||||
private LinkedList<byte[]> Pfs0SHA256hashes;
|
||||
private IPFS0Provider pfs0;
|
||||
private IRomFsProvider romfs;
|
||||
|
||||
// TODO: if decryptedKey is empty, throw exception ??
|
||||
public NCAContent(File file,
|
||||
long offsetPosition,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
NCAHeaderTableEntry ncaHeaderTableEntry,
|
||||
byte[] decryptedKey) throws Exception
|
||||
{
|
||||
this.file = file;
|
||||
this.offsetPosition = offsetPosition;
|
||||
this.ncaSectionBlock = ncaSectionBlock;
|
||||
this.ncaHeaderTableEntry = ncaHeaderTableEntry;
|
||||
this.decryptedKey = decryptedKey;
|
||||
|
||||
Pfs0SHA256hashes = new LinkedList<>();
|
||||
// If nothing to do
|
||||
if (ncaHeaderTableEntry.getMediaEndOffset() == 0)
|
||||
throw new EmptySectionException("Empty section");
|
||||
// If it's PFS0Provider
|
||||
if (ncaSectionBlock.getSuperBlockPFS0() != null)
|
||||
this.proceedPFS0();
|
||||
else if (ncaSectionBlock.getSuperBlockIVFC() != null)
|
||||
this.proceedRomFs();
|
||||
else
|
||||
throw new Exception("NCAContent(): Not supported. PFS0 or RomFS supported only.");
|
||||
}
|
||||
|
||||
private void proceedPFS0() throws Exception {
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedPFS0NotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedPFS0Encrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedPFS0(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedPFS0NotEncrypted() throws Exception{
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long thisMediaLocation = offsetPosition + (ncaHeaderTableEntry.getMediaStartOffset() * 0x200);
|
||||
long hashTableLocation = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset();
|
||||
long pfs0Location = thisMediaLocation + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset();
|
||||
|
||||
raf.seek(hashTableLocation);
|
||||
|
||||
byte[] rawData;
|
||||
long sha256recordsNumber = ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20;
|
||||
// Collect hashes
|
||||
for (int i = 0; i < sha256recordsNumber; i++){
|
||||
rawData = new byte[0x20]; // 32 bytes - size of SHA256 hash
|
||||
if (raf.read(rawData) != -1)
|
||||
Pfs0SHA256hashes.add(rawData);
|
||||
else {
|
||||
raf.close();
|
||||
return; // TODO: fix
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
// Get pfs0
|
||||
pfs0 = new PFS0Provider(file, pfs0Location);
|
||||
}
|
||||
private void proceedPFS0Encrypted() throws Exception{
|
||||
new CryptoSection03Pfs0(file,
|
||||
offsetPosition,
|
||||
decryptedKey,
|
||||
ncaSectionBlock,
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
private void proceedRomFs() throws Exception{
|
||||
switch (ncaSectionBlock.getCryptoType()){
|
||||
case 0x01:
|
||||
proceedRomFsNotEncrypted(); // IF NO ENCRYPTION
|
||||
break;
|
||||
case 0x03:
|
||||
proceedRomFsEncrypted(); // If encrypted regular [ 0x03 ]
|
||||
break;
|
||||
default:
|
||||
throw new Exception("NCAContent() -> proceedRomFs(): Non-supported 'Crypto type'");
|
||||
}
|
||||
}
|
||||
private void proceedRomFsNotEncrypted(){
|
||||
// TODO: Clarify, implement if needed
|
||||
System.out.println("proceedRomFs() -> proceedRomFsNotEncrypted() is not implemented :(");
|
||||
}
|
||||
private void proceedRomFsEncrypted() throws Exception{
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
this.romfs = new RomFsEncryptedProvider(
|
||||
offsetPosition,
|
||||
ncaSectionBlock.getSuperBlockIVFC().getLvl6Offset(),
|
||||
file,
|
||||
decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
ncaHeaderTableEntry.getMediaStartOffset(),
|
||||
ncaHeaderTableEntry.getMediaEndOffset());
|
||||
}
|
||||
|
||||
public LinkedList<byte[]> getPfs0SHA256hashes() { return Pfs0SHA256hashes; }
|
||||
public IPFS0Provider getPfs0() { return pfs0; }
|
||||
public IRomFsProvider getRomfs() { return romfs; }
|
||||
|
||||
private class CryptoSection03Pfs0 {
|
||||
|
||||
CryptoSection03Pfs0(File file,
|
||||
long offsetPosition,
|
||||
byte[] decryptedKey,
|
||||
NCASectionBlock ncaSectionBlock,
|
||||
long mediaStartBlocksOffset,
|
||||
long mediaEndBlocksOffset) throws Exception
|
||||
{
|
||||
/*//--------------------------------------------------------------------------------------------------
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("SHA256 hash tbl size: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableSize());
|
||||
System.out.println("SHA256 hash tbl offs: " + ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset());
|
||||
System.out.println("PFS0 Offs: " + ncaSectionBlock.getSuperBlockPFS0().getPfs0offset());
|
||||
System.out.println("SHA256 records: " + (ncaSectionBlock.getSuperBlockPFS0().getHashTableSize() / 0x20));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//--------------------------------------------------------------------------------------------------*/
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("CryptoSection03: unable to proceed. No decrypted key provided.");
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey, ncaSectionBlock.getSectionCTR(), mediaStartBlocksOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
// Prepare thread to parse encrypted data
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
PipedInputStream streamInp = new PipedInputStream(streamOut);
|
||||
|
||||
Thread pThread = new Thread(new ParseThread(
|
||||
streamInp,
|
||||
ncaSectionBlock.getSuperBlockPFS0().getPfs0offset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableOffset(),
|
||||
ncaSectionBlock.getSuperBlockPFS0().getHashTableSize(),
|
||||
offsetPosition,
|
||||
file,
|
||||
decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
mediaStartBlocksOffset,
|
||||
mediaEndBlocksOffset
|
||||
));
|
||||
pThread.start();
|
||||
// Decrypt data
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
try {
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
catch (IOException e){
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pThread.join();
|
||||
streamOut.close();
|
||||
raf.close();
|
||||
}
|
||||
/*
|
||||
* Since we representing decrypted data as stream (it's easier to look on it this way),
|
||||
* this thread will be parsing it.
|
||||
* */
|
||||
private class ParseThread implements Runnable{
|
||||
|
||||
PipedInputStream pipedInputStream;
|
||||
|
||||
long hashTableOffset;
|
||||
long hashTableSize;
|
||||
long hashTableRecordsCount;
|
||||
long pfs0offset;
|
||||
|
||||
private long MetaOffsetPositionInFile;
|
||||
private File MetaFileWithEncPFS0;
|
||||
private byte[] MetaKey;
|
||||
private byte[] MetaSectionCTR;
|
||||
private long MetaMediaStartOffset;
|
||||
private long MetaMediaEndOffset;
|
||||
|
||||
|
||||
ParseThread(PipedInputStream pipedInputStream,
|
||||
long pfs0offset,
|
||||
long hashTableOffset,
|
||||
long hashTableSize,
|
||||
|
||||
long MetaOffsetPositionInFile,
|
||||
File MetaFileWithEncPFS0,
|
||||
byte[] MetaKey,
|
||||
byte[] MetaSectionCTR,
|
||||
long MetaMediaStartOffset,
|
||||
long MetaMediaEndOffset
|
||||
){
|
||||
this.pipedInputStream = pipedInputStream;
|
||||
this.hashTableOffset = hashTableOffset;
|
||||
this.hashTableSize = hashTableSize;
|
||||
this.hashTableRecordsCount = hashTableSize / 0x20;
|
||||
this.pfs0offset = pfs0offset;
|
||||
|
||||
this.MetaOffsetPositionInFile = MetaOffsetPositionInFile;
|
||||
this.MetaFileWithEncPFS0 = MetaFileWithEncPFS0;
|
||||
this.MetaKey = MetaKey;
|
||||
this.MetaSectionCTR = MetaSectionCTR;
|
||||
this.MetaMediaStartOffset = MetaMediaStartOffset;
|
||||
this.MetaMediaEndOffset = MetaMediaEndOffset;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
long counter = 0; // How many bytes already read
|
||||
|
||||
try{
|
||||
if (hashTableOffset > 0){
|
||||
if (hashTableOffset != pipedInputStream.skip(hashTableOffset))
|
||||
return; // TODO: fix?
|
||||
counter = hashTableOffset;
|
||||
}
|
||||
// Loop for collecting all recrods from sha256 hash table
|
||||
while ((counter - hashTableOffset) < hashTableSize){
|
||||
int hashCounter = 0;
|
||||
byte[] sectionHash = new byte[0x20];
|
||||
// Loop for collecting bytes for every SINGLE records, where record size == 0x20
|
||||
while (hashCounter < 0x20){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1)
|
||||
break;
|
||||
sectionHash[hashCounter] = (byte)currentByte;
|
||||
hashCounter++;
|
||||
counter++;
|
||||
}
|
||||
// Write after collecting
|
||||
Pfs0SHA256hashes.add(sectionHash); // From the NCAContentProvider obviously
|
||||
}
|
||||
// Skip padding and go to PFS0 location
|
||||
if (counter < pfs0offset){
|
||||
long toSkip = pfs0offset-counter;
|
||||
if (toSkip != pipedInputStream.skip(toSkip))
|
||||
return; // TODO: fix?
|
||||
counter += toSkip;
|
||||
}
|
||||
//---------------------------------------------------------
|
||||
pfs0 = new PFS0EncryptedProvider(pipedInputStream,
|
||||
counter,
|
||||
MetaOffsetPositionInFile,
|
||||
MetaFileWithEncPFS0,
|
||||
MetaKey,
|
||||
MetaSectionCTR,
|
||||
MetaMediaStartOffset,
|
||||
MetaMediaEndOffset);
|
||||
pipedInputStream.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("'ParseThread' thread exception");
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
System.out.println("Thread dies");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export NCA content AS IS.
|
||||
* Not so good for PFS0 since there are SHAs list that discourages but good for 'romfs' and things like that
|
||||
* */
|
||||
public PipedInputStream getRawDataContentPipedInpStream() throws Exception {
|
||||
long mediaStartBlocksOffset = ncaHeaderTableEntry.getMediaStartOffset();
|
||||
long mediaEndBlocksOffset = ncaHeaderTableEntry.getMediaEndOffset();
|
||||
long mediaBlocksSize = mediaEndBlocksOffset - mediaStartBlocksOffset;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
///--------------------------------------------------------------------------------------------------
|
||||
System.out.println("NCAContent() -> exportEncryptedSectionType03() Debug information");
|
||||
System.out.println("Media start location: " + mediaStartBlocksOffset);
|
||||
System.out.println("Media end location: " + mediaEndBlocksOffset);
|
||||
System.out.println("Media size : " + (mediaEndBlocksOffset-mediaStartBlocksOffset));
|
||||
System.out.println("Media act. location: " + (offsetPosition + (mediaStartBlocksOffset * 0x200)));
|
||||
System.out.println("KEY: " + LoperConverter.byteArrToHexString(decryptedKey));
|
||||
System.out.println("CTR: " + LoperConverter.byteArrToHexString(ncaSectionBlock.getSectionCTR()));
|
||||
System.out.println();
|
||||
//---------------------------------------------------------------------------------------------------/
|
||||
|
||||
if (ncaSectionBlock.getCryptoType() == 0x01){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Zero encryption section type 01): Thread started");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
byte[] rawDataBlock;
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
rawDataBlock = new byte[0x200];
|
||||
if (raf.read(rawDataBlock) != -1)
|
||||
streamOut.write(rawDataBlock);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else if (ncaSectionBlock.getCryptoType() == 0x03){
|
||||
System.out.println("NCAContent -> getRawDataContentPipedInpStream (Encrypted Section Type 03): Thread started");
|
||||
|
||||
if (decryptedKey == null)
|
||||
throw new Exception("NCAContent -> exportRawData(): unable to proceed. No decrypted key provided.");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
try {
|
||||
//RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = offsetPosition + (mediaStartBlocksOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(decryptedKey,
|
||||
ncaSectionBlock.getSectionCTR(),
|
||||
mediaStartBlocksOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
// Decrypt data
|
||||
for (int i = 0; i < mediaBlocksSize; i++){
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) != -1){
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("NCAContent -> exportRawData(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
raf.close();
|
||||
}catch (Exception ignored) {}
|
||||
try {
|
||||
streamOut.close();
|
||||
}catch (Exception ignored) {}
|
||||
}
|
||||
System.out.println("NCAContent -> exportRawData(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
public long getRawDataContentSize(){
|
||||
return (ncaHeaderTableEntry.getMediaEndOffset() - ncaHeaderTableEntry.getMediaStartOffset()) * 0x200;
|
||||
}
|
||||
public String getFileName(){
|
||||
return file.getName();
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class NCAHeaderTableEntry {
|
||||
|
||||
private long mediaStartOffset;
|
||||
private long mediaEndOffset;
|
||||
private byte[] unknwn1;
|
||||
private byte[] unknwn2;
|
||||
|
||||
public NCAHeaderTableEntry(byte[] table) throws Exception{
|
||||
if (table.length < 0x10)
|
||||
throw new Exception("Section Table size is too small.");
|
||||
|
||||
this.mediaStartOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x0, 0x4));
|
||||
this.mediaEndOffset = convertUnsignedIntBytesToLong(Arrays.copyOfRange(table, 0x4, 0x8));
|
||||
this.unknwn1 = Arrays.copyOfRange(table, 0x8, 0xC);
|
||||
this.unknwn2 = Arrays.copyOfRange(table, 0xC, 0x10);
|
||||
}
|
||||
|
||||
private long convertUnsignedIntBytesToLong(byte[] intBytes){
|
||||
if (intBytes.length == 4)
|
||||
return ByteBuffer.wrap(Arrays.copyOf(intBytes, 8)).order(ByteOrder.LITTLE_ENDIAN).getLong();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
public long getMediaStartOffset() { return mediaStartOffset; }
|
||||
public long getMediaEndOffset() { return mediaEndOffset; }
|
||||
public byte[] getUnknwn1() { return unknwn1; }
|
||||
public byte[] getUnknwn2() { return unknwn2; }
|
||||
}
|
|
@ -1,384 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA;
|
||||
|
||||
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
|
||||
import konogonka.exceptions.EmptySectionException;
|
||||
import konogonka.xtsaes.XTSAESCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
import static konogonka.LoperConverter.byteArrToHexString;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
|
||||
// TODO: check file size
|
||||
public class NCAProvider {
|
||||
private File file; // File that contains NCA
|
||||
private long offset; // Offset where NCA actually located
|
||||
private HashMap<String, String> keys; // hashmap with keys using _0x naming (where x number 0-N)
|
||||
// Header
|
||||
private byte[] rsa2048one;
|
||||
private byte[] rsa2048two;
|
||||
private String magicnum;
|
||||
private byte systemOrGcIndicator;
|
||||
private byte contentType;
|
||||
private byte cryptoType1; // keyblob index. Considering as number within application/ocean/system
|
||||
private byte keyIndex; // application/ocean/system (kaek index?)
|
||||
private long ncaSize; // Size of this NCA (bytes)
|
||||
private byte[] titleId;
|
||||
private byte[] contentIndx;
|
||||
private byte[] sdkVersion; // version ver_revision.ver_micro.vev_minor.ver_major
|
||||
private byte cryptoType2; // keyblob index. Considering as number within application/ocean/system | AKA KeyGeneration
|
||||
private byte Header1SignatureKeyGeneration;
|
||||
private byte[] keyGenerationReserved;
|
||||
private byte[] rightsId;
|
||||
|
||||
private byte cryptoTypeReal;
|
||||
|
||||
private byte[] sha256hash0;
|
||||
private byte[] sha256hash1;
|
||||
private byte[] sha256hash2;
|
||||
private byte[] sha256hash3;
|
||||
|
||||
private byte[] encryptedKey0;
|
||||
private byte[] encryptedKey1;
|
||||
private byte[] encryptedKey2;
|
||||
private byte[] encryptedKey3;
|
||||
|
||||
private byte[] decryptedKey0;
|
||||
private byte[] decryptedKey1;
|
||||
private byte[] decryptedKey2;
|
||||
private byte[] decryptedKey3;
|
||||
|
||||
private NCAHeaderTableEntry tableEntry0;
|
||||
private NCAHeaderTableEntry tableEntry1;
|
||||
private NCAHeaderTableEntry tableEntry2;
|
||||
private NCAHeaderTableEntry tableEntry3;
|
||||
|
||||
private NCASectionBlock sectionBlock0;
|
||||
private NCASectionBlock sectionBlock1;
|
||||
private NCASectionBlock sectionBlock2;
|
||||
private NCASectionBlock sectionBlock3;
|
||||
|
||||
private NCAContent ncaContent0;
|
||||
private NCAContent ncaContent1;
|
||||
private NCAContent ncaContent2;
|
||||
private NCAContent ncaContent3;
|
||||
|
||||
public NCAProvider(File file, HashMap<String, String> keys) throws Exception{
|
||||
this(file, keys, 0);
|
||||
}
|
||||
|
||||
public NCAProvider (File file, HashMap<String, String> keys, long offsetPosition) throws Exception{
|
||||
this.keys = keys;
|
||||
String header_key = keys.get("header_key");
|
||||
if (header_key == null )
|
||||
throw new Exception("header_key is not found within key set provided.");
|
||||
if (header_key.length() != 64)
|
||||
throw new Exception("header_key is too small or too big. Must be 64 symbols.");
|
||||
|
||||
this.file = file;
|
||||
this.offset = offsetPosition;
|
||||
|
||||
KeyParameter key1 = new KeyParameter(
|
||||
hexStrToByteArray(header_key.substring(0, 32))
|
||||
);
|
||||
KeyParameter key2 = new KeyParameter(
|
||||
hexStrToByteArray(header_key.substring(32, 64))
|
||||
);
|
||||
|
||||
XTSAESCipher xtsaesCipher = new XTSAESCipher(false);
|
||||
xtsaesCipher.init(false, key1, key2);
|
||||
//-------------------------------------------------------------------------------------------------------------------------
|
||||
byte[] decryptedHeader = new byte[0xC00];
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
byte[] encryptedSequence = new byte[0x200];
|
||||
byte[] decryptedSequence;
|
||||
|
||||
raf.seek(offsetPosition);
|
||||
|
||||
for (int i = 0; i < 6; i++){
|
||||
if (raf.read(encryptedSequence) != 0x200)
|
||||
throw new Exception("Read error "+i);
|
||||
decryptedSequence = new byte[0x200];
|
||||
xtsaesCipher.processDataUnit(encryptedSequence, 0, 0x200, decryptedSequence, 0, i);
|
||||
System.arraycopy(decryptedSequence, 0, decryptedHeader, i * 0x200, 0x200);
|
||||
}
|
||||
|
||||
getHeader(decryptedHeader);
|
||||
|
||||
raf.close();
|
||||
|
||||
getNCAContent();
|
||||
/*
|
||||
//---------------------------------------------------------------------
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/decrypted.nca"));
|
||||
int i = 0;
|
||||
byte[] block = new byte[0x200];
|
||||
while (fis.read(block) != -1){
|
||||
decryptedSequence = new byte[0x200];
|
||||
xtsaesCipher.processDataUnit(block, 0, 0x200, decryptedSequence, 0, i++);
|
||||
bos.write(decryptedSequence);
|
||||
}
|
||||
bos.close();
|
||||
//---------------------------------------------------------------------*/
|
||||
}
|
||||
|
||||
private byte[] hexStrToByteArray(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
private void getHeader(byte[] decryptedData) throws Exception{
|
||||
rsa2048one = Arrays.copyOfRange(decryptedData, 0, 0x100);
|
||||
rsa2048two = Arrays.copyOfRange(decryptedData, 0x100, 0x200);
|
||||
magicnum = new String(decryptedData, 0x200, 0x4, StandardCharsets.US_ASCII);
|
||||
systemOrGcIndicator = decryptedData[0x204];
|
||||
contentType = decryptedData[0x205];
|
||||
cryptoType1 = decryptedData[0x206];
|
||||
keyIndex = decryptedData[0x207];
|
||||
ncaSize = getLElong(decryptedData, 0x208);
|
||||
titleId = Arrays.copyOfRange(decryptedData, 0x210, 0x218);
|
||||
contentIndx = Arrays.copyOfRange(decryptedData, 0x218, 0x21C);
|
||||
sdkVersion = Arrays.copyOfRange(decryptedData, 0x21c, 0x220);
|
||||
cryptoType2 = decryptedData[0x220];
|
||||
Header1SignatureKeyGeneration = decryptedData[0x221];
|
||||
keyGenerationReserved = Arrays.copyOfRange(decryptedData, 0x222, 0x230);
|
||||
rightsId = Arrays.copyOfRange(decryptedData, 0x230, 0x240);
|
||||
byte[] tableBytes = Arrays.copyOfRange(decryptedData, 0x240, 0x280);
|
||||
byte[] sha256tableBytes = Arrays.copyOfRange(decryptedData, 0x280, 0x300);
|
||||
sha256hash0 = Arrays.copyOfRange(sha256tableBytes, 0, 0x20);
|
||||
sha256hash1 = Arrays.copyOfRange(sha256tableBytes, 0x20, 0x40);
|
||||
sha256hash2 = Arrays.copyOfRange(sha256tableBytes, 0x40, 0x60);
|
||||
sha256hash3 = Arrays.copyOfRange(sha256tableBytes, 0x60, 0x80);
|
||||
byte [] encryptedKeysArea = Arrays.copyOfRange(decryptedData, 0x300, 0x340);
|
||||
|
||||
encryptedKey0 = Arrays.copyOfRange(encryptedKeysArea, 0, 0x10);
|
||||
encryptedKey1 = Arrays.copyOfRange(encryptedKeysArea, 0x10, 0x20);
|
||||
encryptedKey2 = Arrays.copyOfRange(encryptedKeysArea, 0x20, 0x30);
|
||||
encryptedKey3 = Arrays.copyOfRange(encryptedKeysArea, 0x30, 0x40);
|
||||
|
||||
// Calculate real Crypto Type
|
||||
if (cryptoType1 < cryptoType2)
|
||||
cryptoTypeReal = cryptoType2;
|
||||
else
|
||||
cryptoTypeReal = cryptoType1;
|
||||
|
||||
if (cryptoTypeReal > 0) // TODO: CLARIFY WHY THE FUCK IS IT FAIR????
|
||||
cryptoTypeReal -= 1;
|
||||
|
||||
//todo: if nca3 proceed
|
||||
// Decrypt keys if encrypted
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
String keyAreaKey;
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_application_%02x", cryptoTypeReal));
|
||||
break;
|
||||
case 1:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_ocean_%02x", cryptoTypeReal));
|
||||
break;
|
||||
case 2:
|
||||
keyAreaKey = keys.get(String.format("key_area_key_system_%02x", cryptoTypeReal));
|
||||
break;
|
||||
default:
|
||||
keyAreaKey = null;
|
||||
}
|
||||
|
||||
if (keyAreaKey != null){
|
||||
SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(keyAreaKey), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
decryptedKey0 = cipher.doFinal(encryptedKey0);
|
||||
decryptedKey1 = cipher.doFinal(encryptedKey1);
|
||||
decryptedKey2 = cipher.doFinal(encryptedKey2);
|
||||
decryptedKey3 = cipher.doFinal(encryptedKey3);
|
||||
}
|
||||
else
|
||||
keyAreaKeyNotSupportedOrFound();
|
||||
}
|
||||
|
||||
tableEntry0 = new NCAHeaderTableEntry(tableBytes);
|
||||
tableEntry1 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x10, 0x20));
|
||||
tableEntry2 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x20, 0x30));
|
||||
tableEntry3 = new NCAHeaderTableEntry(Arrays.copyOfRange(tableBytes, 0x30, 0x40));
|
||||
|
||||
sectionBlock0 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x400, 0x600));
|
||||
sectionBlock1 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x600, 0x800));
|
||||
sectionBlock2 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0x800, 0xa00));
|
||||
sectionBlock3 = new NCASectionBlock(Arrays.copyOfRange(decryptedData, 0xa00, 0xc00));
|
||||
}
|
||||
|
||||
private void keyAreaKeyNotSupportedOrFound() throws Exception{
|
||||
StringBuilder exceptionStringBuilder = new StringBuilder("key_area_key_");
|
||||
switch (keyIndex){
|
||||
case 0:
|
||||
exceptionStringBuilder.append("application_");
|
||||
break;
|
||||
case 1:
|
||||
exceptionStringBuilder.append("ocean_");
|
||||
break;
|
||||
case 2:
|
||||
exceptionStringBuilder.append("system_");
|
||||
break;
|
||||
default:
|
||||
exceptionStringBuilder.append(keyIndex);
|
||||
exceptionStringBuilder.append("[UNKNOWN]_");
|
||||
}
|
||||
exceptionStringBuilder.append(String.format("%02x", cryptoTypeReal));
|
||||
exceptionStringBuilder.append(" requested. Not supported or not found.");
|
||||
throw new Exception(exceptionStringBuilder.toString());
|
||||
}
|
||||
|
||||
private void getNCAContent(){
|
||||
byte[] key;
|
||||
|
||||
// If empty Rights ID
|
||||
if (Arrays.equals(rightsId, new byte[0x10])) {
|
||||
key = decryptedKey2; // TODO: Just remember this dumb hack
|
||||
}
|
||||
else {
|
||||
try {
|
||||
byte[] rightsIDkey = hexStrToByteArray(keys.get(byteArrToHexString(rightsId))); // throws NullPointerException
|
||||
|
||||
SecretKeySpec skSpec = new SecretKeySpec(
|
||||
hexStrToByteArray(keys.get(String.format("titlekek_%02x", cryptoTypeReal))
|
||||
), "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec);
|
||||
key = cipher.doFinal(rightsIDkey);
|
||||
}
|
||||
catch (Exception e){
|
||||
e.printStackTrace();
|
||||
System.out.println("No title.keys loaded?");
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.ncaContent0 = new NCAContent(file, offset, sectionBlock0, tableEntry0, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent0 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent1 = new NCAContent(file, offset, sectionBlock1, tableEntry1, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent1 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent2 = new NCAContent(file, offset, sectionBlock2, tableEntry2, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent2 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
try{
|
||||
this.ncaContent3 = new NCAContent(file, offset, sectionBlock3, tableEntry3, key);
|
||||
}
|
||||
catch (EmptySectionException ignored){}
|
||||
catch (Exception e){
|
||||
this.ncaContent3 = null;
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getRsa2048one() { return rsa2048one; }
|
||||
public byte[] getRsa2048two() { return rsa2048two; }
|
||||
public String getMagicnum() { return magicnum; }
|
||||
public byte getSystemOrGcIndicator() { return systemOrGcIndicator; }
|
||||
public byte getContentType() { return contentType; }
|
||||
public byte getCryptoType1() { return cryptoType1; }
|
||||
public byte getKeyIndex() { return keyIndex; }
|
||||
public long getNcaSize() { return ncaSize; }
|
||||
public byte[] getTitleId() { return titleId; }
|
||||
public byte[] getContentIndx() { return contentIndx; }
|
||||
public byte[] getSdkVersion() { return sdkVersion; }
|
||||
public byte getCryptoType2() { return cryptoType2; }
|
||||
public byte getHeader1SignatureKeyGeneration() { return Header1SignatureKeyGeneration; }
|
||||
public byte[] getKeyGenerationReserved() { return keyGenerationReserved; }
|
||||
public byte[] getRightsId() { return rightsId; }
|
||||
|
||||
public byte[] getSha256hash0() { return sha256hash0; }
|
||||
public byte[] getSha256hash1() { return sha256hash1; }
|
||||
public byte[] getSha256hash2() { return sha256hash2; }
|
||||
public byte[] getSha256hash3() { return sha256hash3; }
|
||||
|
||||
public byte[] getEncryptedKey0() { return encryptedKey0; }
|
||||
public byte[] getEncryptedKey1() { return encryptedKey1; }
|
||||
public byte[] getEncryptedKey2() { return encryptedKey2; }
|
||||
public byte[] getEncryptedKey3() { return encryptedKey3; }
|
||||
|
||||
public byte[] getDecryptedKey0() { return decryptedKey0; }
|
||||
public byte[] getDecryptedKey1() { return decryptedKey1; }
|
||||
public byte[] getDecryptedKey2() { return decryptedKey2; }
|
||||
public byte[] getDecryptedKey3() { return decryptedKey3; }
|
||||
|
||||
public NCAHeaderTableEntry getTableEntry0() { return tableEntry0; }
|
||||
public NCAHeaderTableEntry getTableEntry1() { return tableEntry1; }
|
||||
public NCAHeaderTableEntry getTableEntry2() { return tableEntry2; }
|
||||
public NCAHeaderTableEntry getTableEntry3() { return tableEntry3; }
|
||||
|
||||
public NCASectionBlock getSectionBlock0() { return sectionBlock0; }
|
||||
public NCASectionBlock getSectionBlock1() { return sectionBlock1; }
|
||||
public NCASectionBlock getSectionBlock2() { return sectionBlock2; }
|
||||
public NCASectionBlock getSectionBlock3() { return sectionBlock3; }
|
||||
|
||||
public boolean isKeyAvailable(){ // TODO: USE
|
||||
if (Arrays.equals(rightsId, new byte[0x10]))
|
||||
return true;
|
||||
else
|
||||
return keys.containsKey(byteArrToHexString(rightsId));
|
||||
}
|
||||
/**
|
||||
* Get content for the selected section
|
||||
* @param sectionNumber should be 0-3
|
||||
* */
|
||||
public NCAContent getNCAContentProvider(int sectionNumber){
|
||||
switch (sectionNumber) {
|
||||
case 0:
|
||||
return ncaContent0;
|
||||
case 1:
|
||||
return ncaContent1;
|
||||
case 2:
|
||||
return ncaContent2;
|
||||
case 3:
|
||||
return ncaContent3;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
|
||||
public class NCASectionBlock {
|
||||
private byte[] version;
|
||||
private byte fsType;
|
||||
private byte hashType;
|
||||
private byte cryptoType;
|
||||
private byte[] padding;
|
||||
private SuperBlockIVFC superBlockIVFC;
|
||||
private SuperBlockPFS0 superBlockPFS0;
|
||||
private byte[] BKTRfullHeader;
|
||||
// BKTR extended
|
||||
private long BKTRoffsetSection1;
|
||||
private long BKTRsizeSection1;
|
||||
private String BKTRmagicSection1;
|
||||
private int BKTRu32Section1;
|
||||
private int BKTRs32Section1;
|
||||
private byte[] BKTRunknownSection1;
|
||||
|
||||
private long BKTRoffsetSection2;
|
||||
private long BKTRsizeSection2;
|
||||
private String BKTRmagicSection2;
|
||||
private int BKTRu32Section2;
|
||||
private int BKTRs32Section2;
|
||||
private byte[] BKTRunknownSection2;
|
||||
|
||||
private byte[] sectionCTR;
|
||||
private byte[] unknownEndPadding;
|
||||
|
||||
public NCASectionBlock(byte[] tableBlockBytes) throws Exception{
|
||||
if (tableBlockBytes.length != 0x200)
|
||||
throw new Exception("Table Block Section size is incorrect.");
|
||||
version = Arrays.copyOfRange(tableBlockBytes, 0, 0x2);
|
||||
fsType = tableBlockBytes[0x2];
|
||||
hashType = tableBlockBytes[0x3];
|
||||
cryptoType = tableBlockBytes[0x4];
|
||||
padding = Arrays.copyOfRange(tableBlockBytes, 0x5, 0x8);
|
||||
byte[] superBlockBytes = Arrays.copyOfRange(tableBlockBytes, 0x8, 0xf8);
|
||||
|
||||
if ((fsType == 0) && (hashType == 0x3))
|
||||
superBlockIVFC = new SuperBlockIVFC(superBlockBytes);
|
||||
else if ((fsType == 0x1) && (hashType == 0x2))
|
||||
superBlockPFS0 = new SuperBlockPFS0(superBlockBytes);
|
||||
|
||||
BKTRfullHeader = Arrays.copyOfRange(tableBlockBytes, 0x100, 0x140);
|
||||
|
||||
BKTRoffsetSection1 = getLElong(BKTRfullHeader, 0);
|
||||
BKTRsizeSection1 = getLElong(BKTRfullHeader, 0x8);
|
||||
BKTRmagicSection1 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x10, 0x14), StandardCharsets.US_ASCII);
|
||||
BKTRu32Section1 = getLEint(BKTRfullHeader, 0x14);
|
||||
BKTRs32Section1 = getLEint(BKTRfullHeader, 0x18);
|
||||
BKTRunknownSection1 = Arrays.copyOfRange(tableBlockBytes, 0x1c, 0x20);
|
||||
|
||||
BKTRoffsetSection2 = getLElong(BKTRfullHeader, 0x20);
|
||||
BKTRsizeSection2 = getLElong(BKTRfullHeader, 0x28);
|
||||
BKTRmagicSection2 = new String(Arrays.copyOfRange(BKTRfullHeader, 0x30, 0x34), StandardCharsets.US_ASCII);
|
||||
BKTRu32Section2 = getLEint(BKTRfullHeader, 0x34);
|
||||
BKTRs32Section2 = getLEint(BKTRfullHeader, 0x38);
|
||||
BKTRunknownSection2 = Arrays.copyOfRange(BKTRfullHeader, 0x3c, 0x40);
|
||||
|
||||
sectionCTR = Arrays.copyOfRange(tableBlockBytes, 0x140, 0x148);
|
||||
unknownEndPadding = Arrays.copyOfRange(tableBlockBytes, 0x148, 0x200);
|
||||
}
|
||||
|
||||
public byte[] getVersion() { return version; }
|
||||
public byte getFsType() { return fsType; }
|
||||
public byte getHashType() { return hashType; }
|
||||
public byte getCryptoType() { return cryptoType; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public SuperBlockIVFC getSuperBlockIVFC() { return superBlockIVFC; }
|
||||
public SuperBlockPFS0 getSuperBlockPFS0() { return superBlockPFS0; }
|
||||
public byte[] getBKTRfullHeader() { return BKTRfullHeader; }
|
||||
|
||||
public long getBKTRoffsetSection1() { return BKTRoffsetSection1; }
|
||||
public long getBKTRsizeSection1() { return BKTRsizeSection1; }
|
||||
public String getBKTRmagicSection1() { return BKTRmagicSection1; }
|
||||
public int getBKTRu32Section1() { return BKTRu32Section1; }
|
||||
public int getBKTRs32Section1() { return BKTRs32Section1; }
|
||||
public byte[] getBKTRunknownSection1() { return BKTRunknownSection1; }
|
||||
public long getBKTRoffsetSection2() { return BKTRoffsetSection2; }
|
||||
public long getBKTRsizeSection2() { return BKTRsizeSection2; }
|
||||
public String getBKTRmagicSection2() { return BKTRmagicSection2; }
|
||||
public int getBKTRu32Section2() { return BKTRu32Section2; }
|
||||
public int getBKTRs32Section2() { return BKTRs32Section2; }
|
||||
public byte[] getBKTRunknownSection2() { return BKTRunknownSection2; }
|
||||
public byte[] getSectionCTR() { return sectionCTR; }
|
||||
public byte[] getUnknownEndPadding() { return unknownEndPadding; }
|
||||
}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
|
||||
public class SuperBlockIVFC {
|
||||
private String magic;
|
||||
private int magicNumber;
|
||||
private int masterHashSize;
|
||||
private int totalNumberOfLevels;
|
||||
private long lvl1Offset;
|
||||
private long lvl1Size;
|
||||
private int lvl1SBlockSize;
|
||||
private byte[] reserved1;
|
||||
|
||||
private long lvl2Offset;
|
||||
private long lvl2Size;
|
||||
private int lvl2SBlockSize;
|
||||
private byte[] reserved2;
|
||||
|
||||
private long lvl3Offset;
|
||||
private long lvl3Size;
|
||||
private int lvl3SBlockSize;
|
||||
private byte[] reserved3;
|
||||
|
||||
private long lvl4Offset;
|
||||
private long lvl4Size;
|
||||
private int lvl4SBlockSize;
|
||||
private byte[] reserved4;
|
||||
|
||||
private long lvl5Offset;
|
||||
private long lvl5Size;
|
||||
private int lvl5SBlockSize;
|
||||
private byte[] reserved5;
|
||||
|
||||
private long lvl6Offset;
|
||||
private long lvl6Size;
|
||||
private int lvl6SBlockSize;
|
||||
private byte[] reserved6;
|
||||
|
||||
private byte[] unknown;
|
||||
private byte[] hash;
|
||||
|
||||
SuperBlockIVFC(byte[] sbBytes){
|
||||
this.magic = new String(Arrays.copyOfRange(sbBytes, 0, 4), StandardCharsets.US_ASCII);
|
||||
this.magicNumber = getLEint(sbBytes, 0x4);
|
||||
this.masterHashSize = getLEint(sbBytes, 0x8);
|
||||
this.totalNumberOfLevels = getLEint(sbBytes, 0xc);
|
||||
|
||||
this.lvl1Offset = getLElong(sbBytes, 0x10);
|
||||
this.lvl1Size = getLElong(sbBytes, 0x18);
|
||||
this.lvl1SBlockSize = getLEint(sbBytes, 0x20);
|
||||
this.reserved1 = Arrays.copyOfRange(sbBytes, 0x24, 0x28);
|
||||
|
||||
this.lvl2Offset = getLElong(sbBytes, 0x28);
|
||||
this.lvl2Size = getLElong(sbBytes, 0x30);
|
||||
this.lvl2SBlockSize = getLEint(sbBytes, 0x38);
|
||||
this.reserved2 = Arrays.copyOfRange(sbBytes, 0x3c, 0x40);
|
||||
|
||||
this.lvl3Offset = getLElong(sbBytes, 0x40);
|
||||
this.lvl3Size = getLElong(sbBytes, 0x48);
|
||||
this.lvl3SBlockSize = getLEint(sbBytes, 0x50);
|
||||
this.reserved3 = Arrays.copyOfRange(sbBytes, 0x54, 0x58);
|
||||
|
||||
this.lvl4Offset = getLElong(sbBytes, 0x58);
|
||||
this.lvl4Size = getLElong(sbBytes, 0x60);
|
||||
this.lvl4SBlockSize = getLEint(sbBytes, 0x68);
|
||||
this.reserved4 = Arrays.copyOfRange(sbBytes, 0x6c, 0x70);
|
||||
|
||||
this.lvl5Offset = getLElong(sbBytes, 0x70);
|
||||
this.lvl5Size = getLElong(sbBytes, 0x78);
|
||||
this.lvl5SBlockSize = getLEint(sbBytes, 0x80);
|
||||
this.reserved5 = Arrays.copyOfRange(sbBytes, 0x84, 0x88);
|
||||
|
||||
this.lvl6Offset = getLElong(sbBytes, 0x88);
|
||||
this.lvl6Size = getLElong(sbBytes, 0x90);
|
||||
this.lvl6SBlockSize = getLEint(sbBytes, 0x98);
|
||||
this.reserved6 = Arrays.copyOfRange(sbBytes, 0x9c, 0xa0);
|
||||
|
||||
this.unknown = Arrays.copyOfRange(sbBytes, 0xa0, 0xc0);
|
||||
this.hash = Arrays.copyOfRange(sbBytes, 0xc0, 0xe0);
|
||||
/*
|
||||
System.out.println(magic);
|
||||
System.out.println(magicNumber);
|
||||
System.out.println(masterHashSize);
|
||||
System.out.println(totalNumberOfLevels);
|
||||
System.out.println(lvl1Offset);
|
||||
System.out.println(lvl1Size);
|
||||
System.out.println(lvl1SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved1);
|
||||
|
||||
System.out.println(lvl2Offset);
|
||||
System.out.println(lvl2Size);
|
||||
System.out.println(lvl2SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved2);
|
||||
|
||||
System.out.println(lvl3Offset);
|
||||
System.out.println(lvl3Size);
|
||||
System.out.println(lvl3SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved3);
|
||||
|
||||
System.out.println(lvl4Offset);
|
||||
System.out.println(lvl4Size);
|
||||
System.out.println(lvl4SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved4);
|
||||
|
||||
System.out.println(lvl5Offset);
|
||||
System.out.println(lvl5Size);
|
||||
System.out.println(lvl5SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved5);
|
||||
|
||||
System.out.println(lvl6Offset);
|
||||
System.out.println(lvl6Size);
|
||||
System.out.println(lvl6SBlockSize);
|
||||
RainbowHexDump.hexDumpUTF8(reserved6);
|
||||
|
||||
RainbowHexDump.hexDumpUTF8(unknown);
|
||||
RainbowHexDump.hexDumpUTF8(hash);
|
||||
// */
|
||||
}
|
||||
|
||||
public String getMagic() { return magic; }
|
||||
public int getMagicNumber() { return magicNumber; }
|
||||
public int getMasterHashSize() { return masterHashSize; }
|
||||
public int getTotalNumberOfLevels() { return totalNumberOfLevels; }
|
||||
public long getLvl1Offset() { return lvl1Offset; }
|
||||
public long getLvl1Size() { return lvl1Size; }
|
||||
public int getLvl1SBlockSize() { return lvl1SBlockSize; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public long getLvl2Offset() { return lvl2Offset; }
|
||||
public long getLvl2Size() { return lvl2Size; }
|
||||
public int getLvl2SBlockSize() { return lvl2SBlockSize; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
public long getLvl3Offset() { return lvl3Offset; }
|
||||
public long getLvl3Size() { return lvl3Size; }
|
||||
public int getLvl3SBlockSize() { return lvl3SBlockSize; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
public long getLvl4Offset() { return lvl4Offset; }
|
||||
public long getLvl4Size() { return lvl4Size; }
|
||||
public int getLvl4SBlockSize() { return lvl4SBlockSize; }
|
||||
public byte[] getReserved4() { return reserved4; }
|
||||
public long getLvl5Offset() { return lvl5Offset; }
|
||||
public long getLvl5Size() { return lvl5Size; }
|
||||
public int getLvl5SBlockSize() { return lvl5SBlockSize; }
|
||||
public byte[] getReserved5() { return reserved5; }
|
||||
public long getLvl6Offset() { return lvl6Offset; }
|
||||
public long getLvl6Size() { return lvl6Size; }
|
||||
public int getLvl6SBlockSize() { return lvl6SBlockSize; }
|
||||
public byte[] getReserved6() { return reserved6; }
|
||||
public byte[] getUnknown() { return unknown; }
|
||||
public byte[] getHash() { return hash; }
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NCA.NCASectionTableBlock;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
|
||||
public class SuperBlockPFS0 {
|
||||
private byte[] SHA256hash;
|
||||
private int blockSize;
|
||||
private int unknownNumberTwo;
|
||||
private long hashTableOffset;
|
||||
private long hashTableSize;
|
||||
private long pfs0offset;
|
||||
private long pfs0size;
|
||||
private byte[] zeroes;
|
||||
|
||||
SuperBlockPFS0(byte[] sbBytes){
|
||||
SHA256hash = Arrays.copyOfRange(sbBytes, 0, 0x20);
|
||||
blockSize = getLEint(sbBytes, 0x20);
|
||||
unknownNumberTwo = getLEint(sbBytes, 0x24);
|
||||
hashTableOffset = getLElong(sbBytes, 0x28);
|
||||
hashTableSize = getLElong(sbBytes, 0x30);
|
||||
pfs0offset = getLElong(sbBytes, 0x38);
|
||||
pfs0size = getLElong(sbBytes, 0x40);
|
||||
zeroes = Arrays.copyOfRange(sbBytes, 0x48, 0xf8);
|
||||
}
|
||||
|
||||
public byte[] getSHA256hash() { return SHA256hash; }
|
||||
public int getBlockSize() { return blockSize; }
|
||||
public int getUnknownNumberTwo() { return unknownNumberTwo; }
|
||||
public long getHashTableOffset() { return hashTableOffset; }
|
||||
public long getHashTableSize() { return hashTableSize; }
|
||||
public long getPfs0offset() { return pfs0offset; }
|
||||
public long getPfs0size() { return pfs0size; }
|
||||
public byte[] getZeroes() { return zeroes; }
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM.ACI0;
|
||||
|
||||
import konogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
import konogonka.Tools.NPDM.ServiceAccessControlProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
|
||||
public class ACI0Provider {
|
||||
private String magicNum;
|
||||
private byte[] reserved1;
|
||||
private byte[] titleID;
|
||||
private byte[] reserved2;
|
||||
private int fsAccessHeaderOffset;
|
||||
private int fsAccessHeaderSize;
|
||||
private int serviceAccessControlOffset;
|
||||
private int serviceAccessControlSize;
|
||||
private int kernelAccessControlOffset;
|
||||
private int kernelAccessControlSize;
|
||||
private byte[] reserved3;
|
||||
|
||||
private FSAccessHeaderProvider fsAccessHeaderProvider;
|
||||
private ServiceAccessControlProvider serviceAccessControlProvider;
|
||||
private KernelAccessControlProvider kernelAccessControlProvider;
|
||||
|
||||
public ACI0Provider(byte[] aci0bytes) throws Exception {
|
||||
if (aci0bytes.length < 0x40)
|
||||
throw new Exception("ACI0 size is too short");
|
||||
magicNum = new String(aci0bytes, 0, 0x4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(aci0bytes, 0x4, 0x10);
|
||||
titleID = Arrays.copyOfRange(aci0bytes, 0x10, 0x18);
|
||||
reserved2 = Arrays.copyOfRange(aci0bytes, 0x18, 0x20);
|
||||
fsAccessHeaderOffset = getLEint(aci0bytes, 0x20);
|
||||
fsAccessHeaderSize = getLEint(aci0bytes, 0x24);
|
||||
serviceAccessControlOffset = getLEint(aci0bytes, 0x28);
|
||||
serviceAccessControlSize = getLEint(aci0bytes, 0x2C);
|
||||
kernelAccessControlOffset = getLEint(aci0bytes, 0x30);
|
||||
kernelAccessControlSize = getLEint(aci0bytes, 0x34);
|
||||
reserved3 = Arrays.copyOfRange(aci0bytes, 0x38, 0x40);
|
||||
|
||||
fsAccessHeaderProvider = new FSAccessHeaderProvider(Arrays.copyOfRange(aci0bytes, fsAccessHeaderOffset, fsAccessHeaderOffset+fsAccessHeaderSize));
|
||||
serviceAccessControlProvider = new ServiceAccessControlProvider(Arrays.copyOfRange(aci0bytes, serviceAccessControlOffset, serviceAccessControlOffset+serviceAccessControlSize));
|
||||
kernelAccessControlProvider = new KernelAccessControlProvider(Arrays.copyOfRange(aci0bytes, kernelAccessControlOffset, kernelAccessControlOffset+kernelAccessControlSize));
|
||||
}
|
||||
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte[] getTitleID() { return titleID; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
public int getFsAccessHeaderOffset() { return fsAccessHeaderOffset; }
|
||||
public int getFsAccessHeaderSize() { return fsAccessHeaderSize; }
|
||||
public int getServiceAccessControlOffset() { return serviceAccessControlOffset; }
|
||||
public int getServiceAccessControlSize() { return serviceAccessControlSize; }
|
||||
public int getKernelAccessControlOffset() { return kernelAccessControlOffset; }
|
||||
public int getKernelAccessControlSize() { return kernelAccessControlSize; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
|
||||
public FSAccessHeaderProvider getFsAccessHeaderProvider() { return fsAccessHeaderProvider; }
|
||||
public ServiceAccessControlProvider getServiceAccessControlProvider() { return serviceAccessControlProvider; }
|
||||
public KernelAccessControlProvider getKernelAccessControlProvider() { return kernelAccessControlProvider; }
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM.ACI0;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* For ACI0 Provider
|
||||
* */
|
||||
public class FSAccessHeaderProvider {
|
||||
|
||||
private byte version;
|
||||
private byte[] padding;
|
||||
private long permissionsBitmask;
|
||||
private int dataSize;
|
||||
private int contentOwnIdSectionSize;
|
||||
private int dataNownerSizes;
|
||||
private int saveDataOwnSectionSize;
|
||||
private byte[] unknownData;
|
||||
|
||||
public FSAccessHeaderProvider(byte[] bytes) {
|
||||
version = bytes[0];
|
||||
padding = Arrays.copyOfRange(bytes, 1, 0x4);
|
||||
permissionsBitmask = LoperConverter.getLElong(bytes, 0x4);
|
||||
dataSize = LoperConverter.getLEint(bytes, 0xC);
|
||||
contentOwnIdSectionSize = LoperConverter.getLEint(bytes, 0x10);
|
||||
dataNownerSizes = LoperConverter.getLEint(bytes, 0x14);
|
||||
saveDataOwnSectionSize = LoperConverter.getLEint(bytes, 0x18);
|
||||
unknownData = Arrays.copyOfRange(bytes, 0x1C, bytes.length);
|
||||
}
|
||||
|
||||
public byte getVersion() { return version; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public long getPermissionsBitmask() { return permissionsBitmask; }
|
||||
public int getDataSize() { return dataSize; }
|
||||
public int getContentOwnIdSectionSize() { return contentOwnIdSectionSize; }
|
||||
public int getDataNownerSizes() { return dataNownerSizes; }
|
||||
public int getSaveDataOwnSectionSize() { return saveDataOwnSectionSize; }
|
||||
public byte[] getUnknownData() { return unknownData; }
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM.ACID;
|
||||
|
||||
import konogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||
import konogonka.Tools.NPDM.ServiceAccessControlProvider;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
|
||||
public class ACIDProvider {
|
||||
|
||||
private byte[] rsa2048signature;
|
||||
private byte[] rsa2048publicKey;
|
||||
private String magicNum;
|
||||
private int dataSize;
|
||||
private byte[] reserved1;
|
||||
private byte flag1;
|
||||
private byte flag2;
|
||||
private byte flag3;
|
||||
private byte flag4;
|
||||
private long titleRangeMin;
|
||||
private long titleRangeMax;
|
||||
private int fsAccessControlOffset;
|
||||
private int fsAccessControlSize;
|
||||
private int serviceAccessControlOffset;
|
||||
private int serviceAccessControlSize;
|
||||
private int kernelAccessControlOffset;
|
||||
private int kernelAccessControlSize;
|
||||
private byte[] reserved2;
|
||||
|
||||
private FSAccessControlProvider fsAccessControlProvider;
|
||||
private ServiceAccessControlProvider serviceAccessControlProvider;
|
||||
private KernelAccessControlProvider kernelAccessControlProvider;
|
||||
|
||||
public ACIDProvider(byte[] acidBytes) throws Exception{
|
||||
if (acidBytes.length < 0x240)
|
||||
throw new Exception("ACIDProvider -> ACI0 size is too short");
|
||||
rsa2048signature = Arrays.copyOfRange(acidBytes, 0, 0x100);
|
||||
rsa2048publicKey = Arrays.copyOfRange(acidBytes, 0x100, 0x200);
|
||||
magicNum = new String(acidBytes, 0x200, 0x4, StandardCharsets.UTF_8);
|
||||
dataSize = getLEint(acidBytes, 0x204);
|
||||
reserved1 = Arrays.copyOfRange(acidBytes, 0x208, 0x20C);
|
||||
flag1 = acidBytes[0x20C];
|
||||
flag2 = acidBytes[0x20D];
|
||||
flag3 = acidBytes[0x20E];
|
||||
flag4 = acidBytes[0x20F];
|
||||
titleRangeMin = getLElong(acidBytes, 0x210);
|
||||
titleRangeMax = getLElong(acidBytes, 0x218);
|
||||
fsAccessControlOffset = getLEint(acidBytes, 0x220);
|
||||
fsAccessControlSize = getLEint(acidBytes, 0x224);
|
||||
serviceAccessControlOffset = getLEint(acidBytes, 0x228);
|
||||
serviceAccessControlSize = getLEint(acidBytes, 0x22C);
|
||||
kernelAccessControlOffset = getLEint(acidBytes, 0x230);
|
||||
kernelAccessControlSize = getLEint(acidBytes, 0x234);
|
||||
reserved2 = Arrays.copyOfRange(acidBytes, 0x238, 0x240);
|
||||
if (fsAccessControlOffset > serviceAccessControlOffset || serviceAccessControlOffset > kernelAccessControlOffset )
|
||||
throw new Exception("ACIDProvider -> blocks inside the ACID are not sorted in ascending order. Only ascending order supported.");
|
||||
fsAccessControlProvider = new FSAccessControlProvider(Arrays.copyOfRange(acidBytes, fsAccessControlOffset, fsAccessControlOffset+fsAccessControlSize));
|
||||
serviceAccessControlProvider = new ServiceAccessControlProvider(Arrays.copyOfRange(acidBytes, serviceAccessControlOffset, serviceAccessControlOffset+serviceAccessControlSize));
|
||||
kernelAccessControlProvider = new KernelAccessControlProvider(Arrays.copyOfRange(acidBytes, kernelAccessControlOffset, kernelAccessControlOffset+kernelAccessControlSize));
|
||||
}
|
||||
|
||||
public byte[] getRsa2048signature() { return rsa2048signature; }
|
||||
public byte[] getRsa2048publicKey() { return rsa2048publicKey; }
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public int getDataSize() { return dataSize; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte getFlag1() { return flag1; }
|
||||
public byte getFlag2() { return flag2; }
|
||||
public byte getFlag3() { return flag3; }
|
||||
public byte getFlag4() { return flag4; }
|
||||
public long getTitleRangeMin() { return titleRangeMin; }
|
||||
public long getTitleRangeMax() { return titleRangeMax; }
|
||||
public int getFsAccessControlOffset() { return fsAccessControlOffset; }
|
||||
public int getFsAccessControlSize() { return fsAccessControlSize; }
|
||||
public int getServiceAccessControlOffset() { return serviceAccessControlOffset; }
|
||||
public int getServiceAccessControlSize() { return serviceAccessControlSize; }
|
||||
public int getKernelAccessControlOffset() { return kernelAccessControlOffset; }
|
||||
public int getKernelAccessControlSize() { return kernelAccessControlSize; }
|
||||
public byte[] getReserved2() { return reserved2; }
|
||||
|
||||
public FSAccessControlProvider getFsAccessControlProvider() { return fsAccessControlProvider; }
|
||||
public ServiceAccessControlProvider getServiceAccessControlProvider() { return serviceAccessControlProvider; }
|
||||
public KernelAccessControlProvider getKernelAccessControlProvider() { return kernelAccessControlProvider; }
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM.ACID;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* For ACID Provider
|
||||
* */
|
||||
public class FSAccessControlProvider {
|
||||
|
||||
private byte version;
|
||||
private byte[] padding;
|
||||
private long permissionsBitmask;
|
||||
private byte[] reserved;
|
||||
|
||||
public FSAccessControlProvider(byte[] bytes) {
|
||||
version = bytes[0];
|
||||
padding = Arrays.copyOfRange(bytes, 1, 0x4);
|
||||
permissionsBitmask = LoperConverter.getLElong(bytes, 0x4);
|
||||
reserved = Arrays.copyOfRange(bytes, 0xC, 0x2C);
|
||||
}
|
||||
|
||||
public byte getVersion() { return version; }
|
||||
public byte[] getPadding() { return padding; }
|
||||
public long getPermissionsBitmask() { return permissionsBitmask; }
|
||||
public byte[] getReserved() { return reserved; }
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/*
|
||||
NOTE: This implementation is extremely bad for using application as library. Use raw for own purposes.
|
||||
|
||||
NOTE:
|
||||
KAC is set of 4-byes blocks
|
||||
Consider them as uInt32 (Read as Little endian)
|
||||
Look on the tail of each block (low bits). If tail is equals to mask like 0111111 then such block is related to one of the possible sections (KernelFlags etc.)
|
||||
If it's related to the one of the blocks, then we could pick useful data from this block.
|
||||
Example:
|
||||
36 BYES on this section, then 9 blocks with len = 4-bytes each available
|
||||
1 00-01-02-03
|
||||
2 04-05-06-07
|
||||
3 08-09-10-11
|
||||
4 12-13-14-15
|
||||
5 16-17-18-19
|
||||
6 20-21-22-23
|
||||
7 24-25-26-27
|
||||
8 28-29-30-31
|
||||
9 32-33-34-35
|
||||
|
||||
Possible patterns are:
|
||||
Where '+' is useful data; '0' and '1' in low bytes are pattern.
|
||||
Octal | Decimal
|
||||
++++++++++++++++++++++++++++0111 | 7 <- KernelFlags
|
||||
+++++++++++++++++++++++++++01111 | 15 <- SyscallMask
|
||||
+++++++++++++++++++++++++0111111 | 63 <- MapIoOrNormalRange
|
||||
++++++++++++++++++++++++01111111 | 127 <- MapNormalPage (RW)
|
||||
++++++++++++++++++++011111111111 | 2+47 <- InterruptPair
|
||||
++++++++++++++++++01111111111111 | 8191 <- ApplicationType
|
||||
+++++++++++++++++011111111111111 | 16383 <- KernelReleaseVersion
|
||||
++++++++++++++++0111111111111111 | 32767 <- HandleTableSize
|
||||
+++++++++++++++01111111111111111 | 65535 <- DebugFlags
|
||||
Other masks could be implemented by N in future (?).
|
||||
|
||||
Calculation example:
|
||||
Dec 1 = 00000000000000000000000000000001
|
||||
00100000000000000000000000000111 & 1 = 1
|
||||
00010000000000000000000000000011 & 1 = 1
|
||||
00001000000000000000000000000001 & 1 = 1
|
||||
00000100000000000000000000000000 & 1 = 0
|
||||
|
||||
TIP: Generate
|
||||
int j = 0xFFFFFFFF;
|
||||
for (byte i = 0; i < 16; i++){
|
||||
j = (j << 1);
|
||||
RainbowHexDump.octDumpInt(~j);
|
||||
}
|
||||
*/
|
||||
|
||||
public class KernelAccessControlProvider {
|
||||
|
||||
private static final int KERNELFLAGS = 3,
|
||||
SYSCALLMASK = 4,
|
||||
MAPIOORNORMALRANGE = 6,
|
||||
MAPNORMALPAGE_RW = 7,
|
||||
INTERRUPTPAIR = 11,
|
||||
APPLICATIONTYPE = 13,
|
||||
KERNELRELEASEVERSION = 14,
|
||||
HANDLETABLESIZE = 15,
|
||||
DEBUGFLAGS = 16;
|
||||
|
||||
// RAW data
|
||||
private LinkedList<Integer> rawData;
|
||||
// Kernel flags
|
||||
private boolean kernelFlagsAvailable;
|
||||
private int kernelFlagCpuIdHi,
|
||||
kernelFlagCpuIdLo,
|
||||
kernelFlagThreadPrioHi,
|
||||
kernelFlagThreadPrioLo;
|
||||
// Syscall Masks as index | mask - order AS IS. [0] = bit5; [1] = bit6
|
||||
private LinkedHashMap<Byte, byte[]> syscallMasks; // Index, Mask
|
||||
// MapIoOrNormalRange
|
||||
private LinkedHashMap<byte[], Boolean> mapIoOrNormalRange; // alt page+num, RO flag
|
||||
// MapNormalPage (RW)
|
||||
private byte[] mapNormalPage; // TODO: clarify is possible to have multiple
|
||||
// InterruptPair
|
||||
private LinkedHashMap<Integer, byte[][]> interruptPairs; // Number; irq0, irq2
|
||||
// Application type
|
||||
private int applicationType;
|
||||
// KernelReleaseVersion
|
||||
private boolean isKernelRelVersionAvailable;
|
||||
private int kernelRelVersionMajor,
|
||||
kernelRelVersionMinor;
|
||||
// Handle Table Size
|
||||
private int handleTableSize;
|
||||
// Debug flags
|
||||
private boolean debugFlagsAvailable,
|
||||
canBeDebugged,
|
||||
canDebugOthers;
|
||||
|
||||
public KernelAccessControlProvider(byte[] bytes) throws Exception{
|
||||
if (bytes.length < 4)
|
||||
throw new Exception("ACID-> KernelAccessControlProvider: too small size of the Kernel Access Control");
|
||||
|
||||
rawData = new LinkedList<Integer>();
|
||||
|
||||
interruptPairs = new LinkedHashMap<>();
|
||||
syscallMasks = new LinkedHashMap<Byte, byte[]>();
|
||||
mapIoOrNormalRange = new LinkedHashMap<byte[], Boolean>();
|
||||
|
||||
int position = 0;
|
||||
// Collect all blocks
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
int block = LoperConverter.getLEint(bytes, position);
|
||||
position += 4;
|
||||
|
||||
rawData.add(block);
|
||||
|
||||
//RainbowHexDump.octDumpInt(block);
|
||||
|
||||
int type = getMinBitCnt(block);
|
||||
|
||||
switch (type){
|
||||
case KERNELFLAGS:
|
||||
kernelFlagsAvailable = true;
|
||||
kernelFlagCpuIdHi = block >> 24;
|
||||
kernelFlagCpuIdLo = block >> 16 & 0b11111111;
|
||||
kernelFlagThreadPrioHi = block >> 10 & 0b111111;
|
||||
kernelFlagThreadPrioLo = block >> 4 & 0b111111;
|
||||
//System.out.println("KERNELFLAGS "+kernelFlagCpuIdHi+" "+kernelFlagCpuIdLo+" "+kernelFlagThreadPrioHi+" "+kernelFlagThreadPrioLo);
|
||||
break;
|
||||
case SYSCALLMASK:
|
||||
byte maskTableIndex = (byte) (block >> 29 & 0b111); // declared as byte; max value could be 7; min - 0;
|
||||
byte[] mask = new byte[24]; // Consider as bit.
|
||||
//System.out.println("SYSCALLMASK ind: "+maskTableIndex);
|
||||
|
||||
for (int k = 28; k >= 5; k--) {
|
||||
mask[k-5] = (byte) (block >> k & 1); // Only 1 or 0 possible
|
||||
//System.out.print(mask[k-5]);
|
||||
}
|
||||
//System.out.println();
|
||||
syscallMasks.put(maskTableIndex, mask);
|
||||
break;
|
||||
case MAPIOORNORMALRANGE:
|
||||
byte[] altStPgNPgNum = new byte[24];
|
||||
//System.out.println("MAPIOORNORMALRANGE Flag: "+((block >> 31 & 1) != 0));
|
||||
|
||||
for (int k = 30; k >= 7; k--){
|
||||
altStPgNPgNum[k-7] = (byte) (block >> k & 1); // Only 1 or 0 possible
|
||||
//System.out.print(altStPgNPgNum[k-7]);
|
||||
}
|
||||
mapIoOrNormalRange.put(altStPgNPgNum, (block >> 31 & 1) != 0);
|
||||
//System.out.println();
|
||||
break;
|
||||
case MAPNORMALPAGE_RW:
|
||||
//System.out.println("MAPNORMALPAGE_RW\t");
|
||||
mapNormalPage = new byte[24];
|
||||
for (int k = 31; k >= 8; k--){
|
||||
mapNormalPage[k-8] = (byte) (block >> k & 1);
|
||||
//System.out.print(mapNormalPage[k-8]);
|
||||
}
|
||||
//System.out.println();
|
||||
break;
|
||||
case INTERRUPTPAIR:
|
||||
//System.out.println("INTERRUPTPAIR");
|
||||
//RainbowHexDump.octDumpInt(block);
|
||||
byte[][] pair = new byte[2][];
|
||||
byte[] irq0 = new byte[10];
|
||||
byte[] irq1 = new byte[10];
|
||||
|
||||
for (int k = 21; k >= 12; k--)
|
||||
irq0[k-12] = (byte) (block >> k & 1);
|
||||
for (int k = 31; k >= 22; k--)
|
||||
irq1[k-22] = (byte) (block >> k & 1);
|
||||
pair[0] = irq0;
|
||||
pair[1] = irq1;
|
||||
interruptPairs.put(interruptPairs.size(), pair);
|
||||
break;
|
||||
case APPLICATIONTYPE:
|
||||
applicationType = block >> 14 & 0b111;
|
||||
//System.out.println("APPLICATIONTYPE "+applicationType);
|
||||
break;
|
||||
case KERNELRELEASEVERSION:
|
||||
//System.out.println("KERNELRELEASEVERSION\t"+(block >> 19 & 0b111111111111)+"."+(block >> 15 & 0b1111)+".X");
|
||||
isKernelRelVersionAvailable = true;
|
||||
kernelRelVersionMajor = (block >> 19 & 0b111111111111);
|
||||
kernelRelVersionMinor = (block >> 15 & 0b1111);
|
||||
break;
|
||||
case HANDLETABLESIZE:
|
||||
handleTableSize = block >> 16 & 0b1111111111;
|
||||
//System.out.println("HANDLETABLESIZE "+handleTableSize);
|
||||
break;
|
||||
case DEBUGFLAGS:
|
||||
debugFlagsAvailable = true;
|
||||
canBeDebugged = (block >> 17 & 1) != 0;
|
||||
canDebugOthers = (block >> 18 & 1) != 0;
|
||||
//System.out.println("DEBUGFLAGS "+canBeDebugged+" "+canDebugOthers);
|
||||
break;
|
||||
default:
|
||||
System.out.println("UNKNOWN\t\t"+block+" "+type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getMinBitCnt(int value){
|
||||
int minBitCnt = 0;
|
||||
while ((value & 1) != 0){
|
||||
value >>= 1;
|
||||
minBitCnt++;
|
||||
}
|
||||
return minBitCnt;
|
||||
}
|
||||
public LinkedList<Integer> getRawData() { return rawData; }
|
||||
public boolean isKernelFlagsAvailable() { return kernelFlagsAvailable; }
|
||||
public int getKernelFlagCpuIdHi() { return kernelFlagCpuIdHi; }
|
||||
public int getKernelFlagCpuIdLo() { return kernelFlagCpuIdLo; }
|
||||
public int getKernelFlagThreadPrioHi() { return kernelFlagThreadPrioHi; }
|
||||
public int getKernelFlagThreadPrioLo() { return kernelFlagThreadPrioLo; }
|
||||
public LinkedHashMap<byte[], Boolean> getMapIoOrNormalRange() { return mapIoOrNormalRange; }
|
||||
public byte[] getMapNormalPage() { return mapNormalPage; }
|
||||
public LinkedHashMap<Integer, byte[][]> getInterruptPairs() { return interruptPairs; }
|
||||
public int getApplicationType() { return applicationType; }
|
||||
public boolean isKernelRelVersionAvailable() { return isKernelRelVersionAvailable; }
|
||||
public int getKernelRelVersionMajor() { return kernelRelVersionMajor; }
|
||||
public int getKernelRelVersionMinor() { return kernelRelVersionMinor;}
|
||||
public int getHandleTableSize() { return handleTableSize; }
|
||||
public boolean isDebugFlagsAvailable() { return debugFlagsAvailable; }
|
||||
public boolean isCanBeDebugged() { return canBeDebugged; }
|
||||
public boolean isCanDebugOthers() { return canDebugOthers; }
|
||||
public LinkedHashMap<Byte, byte[]> getSyscallMasks() { return syscallMasks; }
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM;
|
||||
|
||||
import konogonka.Tools.ASuperInFileProvider;
|
||||
import konogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||
import konogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
|
||||
public class NPDMProvider extends ASuperInFileProvider {
|
||||
|
||||
private String magicNum;
|
||||
private byte[] reserved1;
|
||||
private byte MMUFlags;
|
||||
private byte reserved2;
|
||||
private byte mainThreadPrio;
|
||||
private byte mainThreadCoreNum;
|
||||
private byte[] reserved3;
|
||||
private int personalMmHeapSize; // safe-to-store
|
||||
private int version; // safe?
|
||||
private long mainThreadStackSize; // TODO: check if safe
|
||||
private String titleName;
|
||||
private byte[] productCode;
|
||||
private byte[] reserved4;
|
||||
private int aci0offset; // originally 4-bytes (u-int)
|
||||
private int aci0size; // originally 4-bytes (u-int)
|
||||
private int acidOffset; // originally 4-bytes (u-int)
|
||||
private int acidSize; // originally 4-bytes (u-int)
|
||||
|
||||
private ACI0Provider aci0;
|
||||
private ACIDProvider acid;
|
||||
|
||||
public NPDMProvider(PipedInputStream pis) throws Exception{
|
||||
byte[] mainBuf = new byte[0x80];
|
||||
if(pis.read(mainBuf) != 0x80)
|
||||
throw new Exception("NPDMProvider: Failed to read 'META'");
|
||||
aci0offset = getLEint(mainBuf, 0x70);
|
||||
aci0size = getLEint(mainBuf, 0x74);
|
||||
acidOffset = getLEint(mainBuf, 0x78);
|
||||
acidSize = getLEint(mainBuf, 0x7C);
|
||||
byte[] aci0Buf;
|
||||
byte[] acidBuf;
|
||||
if (aci0offset < acidOffset){
|
||||
if (pis.skip(aci0offset - 0x80) != (aci0offset - 0x80))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
|
||||
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
if (pis.skip(acidOffset - aci0offset - aci0size) != (acidOffset - aci0offset - aci0size))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
|
||||
if ((acidBuf = readFromStream(pis, acidSize)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
}
|
||||
else {
|
||||
if (pis.skip(acidOffset - 0x80) != (acidOffset - 0x80))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACID'");
|
||||
if ((acidBuf = readFromStream(pis, acidSize)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
if (pis.skip(aci0offset - acidOffset - acidSize) != (aci0offset - acidOffset - acidSize))
|
||||
throw new Exception("NPDMProvider: Failed to skip bytes till 'ACI0'");
|
||||
if ((aci0Buf = readFromStream(pis, aci0size)) == null)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
}
|
||||
magicNum = new String(mainBuf, 0, 4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(mainBuf, 0x4, 0xC);
|
||||
MMUFlags = mainBuf[0xC];
|
||||
reserved2 = mainBuf[0xD];
|
||||
mainThreadPrio = mainBuf[0xE];
|
||||
mainThreadCoreNum = mainBuf[0xF];
|
||||
reserved3 = Arrays.copyOfRange(mainBuf, 0x10, 0x14);
|
||||
personalMmHeapSize = getLEint(mainBuf, 0x14);
|
||||
version = getLEint(mainBuf, 0x18);
|
||||
mainThreadStackSize = getLElongOfInt(mainBuf, 0x1C);
|
||||
titleName = new String(mainBuf, 0x20, 0x10, StandardCharsets.UTF_8);
|
||||
productCode = Arrays.copyOfRange(mainBuf, 0x30, 0x40);
|
||||
reserved4 = Arrays.copyOfRange(mainBuf, 0x40, 0x70);
|
||||
|
||||
aci0 = new ACI0Provider(aci0Buf);
|
||||
acid = new ACIDProvider(acidBuf);
|
||||
}
|
||||
|
||||
public NPDMProvider(File file) throws Exception { this(file, 0); }
|
||||
|
||||
public NPDMProvider(File file, long offset) throws Exception {
|
||||
if (file.length() - offset < 0x80) // Header's size
|
||||
throw new Exception("NPDMProvider: File is too small.");
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
raf.seek(offset);
|
||||
// Get META
|
||||
byte[] metaBuf = new byte[0x80];
|
||||
if (raf.read(metaBuf) != 0x80)
|
||||
throw new Exception("NPDMProvider: Failed to read 'META'");
|
||||
magicNum = new String(metaBuf, 0, 4, StandardCharsets.UTF_8);
|
||||
reserved1 = Arrays.copyOfRange(metaBuf, 0x4, 0xC);
|
||||
MMUFlags = metaBuf[0xC];
|
||||
reserved2 = metaBuf[0xD];
|
||||
mainThreadPrio = metaBuf[0xE];
|
||||
mainThreadCoreNum = metaBuf[0xF];
|
||||
reserved3 = Arrays.copyOfRange(metaBuf, 0x10, 0x14);
|
||||
personalMmHeapSize = getLEint(metaBuf, 0x14);
|
||||
version = getLEint(metaBuf, 0x18);
|
||||
mainThreadStackSize = getLElongOfInt(metaBuf, 0x1C);
|
||||
titleName = new String(metaBuf, 0x20, 0x10, StandardCharsets.UTF_8);
|
||||
productCode = Arrays.copyOfRange(metaBuf, 0x30, 0x40);
|
||||
reserved4 = Arrays.copyOfRange(metaBuf, 0x40, 0x70);
|
||||
aci0offset = getLEint(metaBuf, 0x70);
|
||||
aci0size = getLEint(metaBuf, 0x74);
|
||||
acidOffset = getLEint(metaBuf, 0x78);
|
||||
acidSize = getLEint(metaBuf, 0x7C);
|
||||
// Get ACI0
|
||||
raf.seek(aci0offset);
|
||||
metaBuf = new byte[aci0size]; // TODO: NOTE: we read all size but it's memory consuming
|
||||
if (raf.read(metaBuf) != aci0size)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACI0'");
|
||||
aci0 = new ACI0Provider(metaBuf);
|
||||
// Get ACID
|
||||
raf.seek(acidOffset);
|
||||
metaBuf = new byte[acidSize]; // TODO: NOTE: we read all size but it's memory consuming
|
||||
if (raf.read(metaBuf) != acidSize)
|
||||
throw new Exception("NPDMProvider: Failed to read 'ACID'");
|
||||
acid = new ACIDProvider(metaBuf);
|
||||
raf.close();
|
||||
}
|
||||
|
||||
public String getMagicNum() { return magicNum; }
|
||||
public byte[] getReserved1() { return reserved1; }
|
||||
public byte getMMUFlags() { return MMUFlags; }
|
||||
public byte getReserved2() { return reserved2; }
|
||||
public byte getMainThreadPrio() { return mainThreadPrio; }
|
||||
public byte getMainThreadCoreNum() { return mainThreadCoreNum; }
|
||||
public byte[] getReserved3() { return reserved3; }
|
||||
public int getPersonalMmHeapSize() { return personalMmHeapSize; }
|
||||
public int getVersion() { return version; }
|
||||
public long getMainThreadStackSize() { return mainThreadStackSize; }
|
||||
public String getTitleName() { return titleName; }
|
||||
public byte[] getProductCode() { return productCode; }
|
||||
public byte[] getReserved4() { return reserved4; }
|
||||
public int getAci0offset() { return aci0offset; }
|
||||
public int getAci0size() { return aci0size; }
|
||||
public int getAcidOffset() { return acidOffset; }
|
||||
public int getAcidSize() { return acidSize; }
|
||||
|
||||
public ACI0Provider getAci0() { return aci0; }
|
||||
public ACIDProvider getAcid() { return acid; }
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.NPDM;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
public class ServiceAccessControlProvider {
|
||||
|
||||
private LinkedHashMap<String, Byte> collection;
|
||||
|
||||
public ServiceAccessControlProvider(byte[] bytes){
|
||||
collection = new LinkedHashMap<>();
|
||||
byte key;
|
||||
String value;
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i < bytes.length){
|
||||
key = bytes[i];
|
||||
value = new String(bytes, i+1, getSize(key), StandardCharsets.UTF_8);
|
||||
collection.put(value, key);
|
||||
i += getSize(key)+1;
|
||||
}
|
||||
}
|
||||
|
||||
private int getSize(byte control) {
|
||||
return ((byte) 0x7 & control) + (byte) 0x01;
|
||||
}
|
||||
|
||||
public LinkedHashMap<String, Byte> getCollection() { return collection; }
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.PFS0;
|
||||
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
|
||||
public interface IPFS0Provider extends ISuperProvider {
|
||||
boolean isEncrypted();
|
||||
String getMagic();
|
||||
int getFilesCount();
|
||||
int getStringTableSize();
|
||||
byte[] getPadding();
|
||||
|
||||
PFS0subFile[] getPfs0subFiles();
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.PFS0;
|
||||
|
||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
|
||||
public class PFS0EncryptedProvider implements IPFS0Provider{
|
||||
private long rawFileDataStart; // Always -1 @ PFS0EncryptedProvider
|
||||
|
||||
private String magic;
|
||||
private int filesCount;
|
||||
private int stringTableSize;
|
||||
private byte[] padding;
|
||||
private PFS0subFile[] pfs0subFiles;
|
||||
|
||||
//---------------------------------------
|
||||
|
||||
private long rawBlockDataStart;
|
||||
|
||||
private long offsetPositionInFile;
|
||||
private File file;
|
||||
private byte[] key;
|
||||
private byte[] sectionCTR;
|
||||
private long mediaStartOffset; // In 512-blocks
|
||||
private long mediaEndOffset; // In 512-blocks
|
||||
|
||||
public PFS0EncryptedProvider(PipedInputStream pipedInputStream,
|
||||
long pfs0offsetPosition,
|
||||
long offsetPositionInFile,
|
||||
File fileWithEncPFS0,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
) throws Exception{
|
||||
// Populate 'meta' data that is needed for getProviderSubFilePipedInpStream()
|
||||
this.offsetPositionInFile = offsetPositionInFile;
|
||||
this.file = fileWithEncPFS0;
|
||||
this.key = key;
|
||||
this.sectionCTR = sectionCTR;
|
||||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
// pfs0offsetPosition is a position relative to Media block. Lets add pfs0 'header's' bytes count and get raw data start position in media block
|
||||
rawFileDataStart = -1; // Set -1 for PFS0EncryptedProvider
|
||||
// Detect raw data start position using next var
|
||||
rawBlockDataStart = pfs0offsetPosition;
|
||||
|
||||
byte[] fileStartingBytes = new byte[0x10];
|
||||
// Read PFS0Provider, files count, header, padding (4 zero bytes)
|
||||
|
||||
for (int i = 0; i < 0x10; i++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read starting 0x10 bytes");
|
||||
}
|
||||
fileStartingBytes[i] = (byte)currentByte;
|
||||
}
|
||||
// Update position
|
||||
rawBlockDataStart += 0x10;
|
||||
// Check PFS0Provider
|
||||
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||
if (! magic.equals("PFS0")){
|
||||
throw new Exception("PFS0EncryptedProvider: Bad magic");
|
||||
}
|
||||
// Get files count
|
||||
filesCount = getLEint(fileStartingBytes, 0x4);
|
||||
if (filesCount <= 0 ) {
|
||||
throw new Exception("PFS0EncryptedProvider: Files count is too small");
|
||||
}
|
||||
// Get string table
|
||||
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
||||
if (stringTableSize <= 0 ){
|
||||
throw new Exception("PFS0EncryptedProvider: String table is too small");
|
||||
}
|
||||
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
pfs0subFiles = new PFS0subFile[filesCount];
|
||||
|
||||
long[] offsetsSubFiles = new long[filesCount];
|
||||
long[] sizesSubFiles = new long[filesCount];
|
||||
int[] strTableOffsets = new int[filesCount];
|
||||
byte[][] zeroBytes = new byte[filesCount][];
|
||||
|
||||
byte[] fileEntryTable = new byte[0x18];
|
||||
for (int i=0; i < filesCount; i++){
|
||||
for (int j = 0; j < 0x18; j++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read File Entry Table #"+i);
|
||||
}
|
||||
fileEntryTable[j] = (byte)currentByte;
|
||||
}
|
||||
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
||||
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
||||
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
||||
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
||||
// Update position
|
||||
rawBlockDataStart += 0x18;
|
||||
}
|
||||
//**********************************************************************************************************
|
||||
// In here pointer in front of String table
|
||||
String[] subFileNames = new String[filesCount];
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
|
||||
for (int i = 0; i < stringTableSize; i++){
|
||||
int currentByte = pipedInputStream.read();
|
||||
if (currentByte == -1) {
|
||||
throw new Exception("PFS0EncryptedProvider: Reading stream suddenly ended while trying to read string table");
|
||||
}
|
||||
stringTbl[i] = (byte)currentByte;
|
||||
}
|
||||
// Update position
|
||||
rawBlockDataStart += stringTableSize;
|
||||
|
||||
for (int i=0; i < filesCount; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
pfs0subFiles[i] = new PFS0subFile(
|
||||
subFileNames[i],
|
||||
offsetsSubFiles[i],
|
||||
sizesSubFiles[i],
|
||||
zeroBytes[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() { return true; }
|
||||
@Override
|
||||
public String getMagic() { return magic; }
|
||||
@Override
|
||||
public int getFilesCount() { return filesCount; }
|
||||
@Override
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public byte[] getPadding() { return padding; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception { // TODO: rewrite
|
||||
if (subFileNumber >= pfs0subFiles.length)
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
|
||||
Thread workerThread;
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
// Let's store what we're about to skip
|
||||
long skipInitL = offsetPositionInFile + (mediaStartOffset * 0x200); // NOTE: NEVER cast to int.
|
||||
// Check if skip was successful
|
||||
if (bis.skip(skipInitL) != skipInitL) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipInitL);
|
||||
return;
|
||||
}
|
||||
|
||||
AesCtrDecryptSimple aesCtrDecryptSimple = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
//----------------------------- Pre-set: skip non-necessary data --------------------------------
|
||||
|
||||
long startBlock = (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) / 0x200; // <- pointing to place where actual data starts
|
||||
int skipBytes;
|
||||
|
||||
if (startBlock > 0) {
|
||||
aesCtrDecryptSimple.skipNext(startBlock);
|
||||
skipBytes = (int)(startBlock * 0x200);
|
||||
if (bis.skip(skipBytes) != skipBytes) {
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Failed to skip range "+skipBytes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------- Step 1: get starting bytes from the end of the junk block --------------------------------
|
||||
|
||||
// Since our data could be located in position with some offset from the decrypted block, let's skip bytes left. Considering the case when data is not aligned to block
|
||||
skipBytes = (int) ( (rawBlockDataStart + pfs0subFiles[subFileNumber].getOffset()) - startBlock * 0x200); // <- How much bytes shall we skip to reach requested data start of sub-file
|
||||
|
||||
if (skipBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - skipBytes) > pfs0subFiles[subFileNumber].getSize()){
|
||||
streamOut.write(dectyptedBlock, skipBytes, (int) pfs0subFiles[subFileNumber].getSize()); // safe cast
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
return;
|
||||
}
|
||||
else
|
||||
streamOut.write(dectyptedBlock, skipBytes, 0x200 - skipBytes);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from 1st bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
long endBlock = pfs0subFiles[subFileNumber].getSize() / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//----------------------------- Step 2: Detect if we have junk data on the end of the final block --------------------------------
|
||||
int extraData = (int)(rawBlockDataStart+pfs0subFiles[subFileNumber].getOffset()+pfs0subFiles[subFileNumber].getSize() - (endBlock*0x200)); // safe cast
|
||||
if (extraData < 0){
|
||||
endBlock--;
|
||||
}
|
||||
//----------------------------- Step 3: Read main part of data --------------------------------
|
||||
// Here we're reading main amount of bytes. We can read only less bytes.
|
||||
while ( startBlock < endBlock) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
//dectyptedBlock = aesCtr.decrypt(encryptedBlock);
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
// Writing decrypted data to pipe
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
//----------------------------- Step 4: Read what's left --------------------------------
|
||||
// Now we have to find out if data overlaps to one more extra block
|
||||
if (extraData > 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, extraData);
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (extraData < 0){ // In case we can get more than we need
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (bis.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = aesCtrDecryptSimple.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, 0x200 + extraData); // WTF ??? THIS LOOKS INCORRECT
|
||||
}
|
||||
else {
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): Unable to get 512 bytes from last bock");
|
||||
return;
|
||||
}
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (Exception e){
|
||||
System.out.println("PFS0EncryptedProvider -> getProviderSubFilePipedInpStream(): "+e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0EncryptedProvider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
|
||||
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception{
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.PFS0;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
|
||||
public class PFS0Provider implements IPFS0Provider{
|
||||
private long rawFileDataStart; // Where data starts, excluding header, string table etc.
|
||||
|
||||
private String magic;
|
||||
private int filesCount;
|
||||
private int stringTableSize;
|
||||
private byte[] padding;
|
||||
private PFS0subFile[] pfs0subFiles;
|
||||
|
||||
private File file;
|
||||
|
||||
public PFS0Provider(File fileWithPfs0) throws Exception{ this(fileWithPfs0, 0); }
|
||||
|
||||
public PFS0Provider(File fileWithPfs0, long pfs0offsetPosition) throws Exception{
|
||||
file = fileWithPfs0;
|
||||
RandomAccessFile raf = new RandomAccessFile(fileWithPfs0, "r"); // TODO: replace to bufferedInputStream
|
||||
|
||||
raf.seek(pfs0offsetPosition);
|
||||
byte[] fileStartingBytes = new byte[0x10];
|
||||
// Read PFS0Provider, files count, header, padding (4 zero bytes)
|
||||
if (raf.read(fileStartingBytes) != 0x10){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Unable to read starting bytes");
|
||||
}
|
||||
// Check PFS0Provider
|
||||
magic = new String(fileStartingBytes, 0x0, 0x4, StandardCharsets.US_ASCII);
|
||||
if (! magic.equals("PFS0")){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Bad magic");
|
||||
}
|
||||
// Get files count
|
||||
filesCount = getLEint(fileStartingBytes, 0x4);
|
||||
if (filesCount <= 0 ) {
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: Files count is too small");
|
||||
}
|
||||
// Get string table
|
||||
stringTableSize = getLEint(fileStartingBytes, 0x8);
|
||||
if (stringTableSize <= 0 ){
|
||||
raf.close();
|
||||
throw new Exception("PFS0Provider: String table is too small");
|
||||
}
|
||||
padding = Arrays.copyOfRange(fileStartingBytes, 0xc, 0x10);
|
||||
//---------------------------------------------------------------------------------------------------------
|
||||
pfs0subFiles = new PFS0subFile[filesCount];
|
||||
|
||||
long[] offsetsSubFiles = new long[filesCount];
|
||||
long[] sizesSubFiles = new long[filesCount];
|
||||
int[] strTableOffsets = new int[filesCount];
|
||||
byte[][] zeroBytes = new byte[filesCount][];
|
||||
|
||||
byte[] fileEntryTable = new byte[0x18];
|
||||
for (int i=0; i<filesCount; i++){
|
||||
if (raf.read(fileEntryTable) != 0x18)
|
||||
throw new Exception("PFS0Provider: String table is too small");
|
||||
offsetsSubFiles[i] = getLElong(fileEntryTable, 0);
|
||||
sizesSubFiles[i] = getLElong(fileEntryTable, 0x8);
|
||||
strTableOffsets[i] = getLEint(fileEntryTable, 0x10);
|
||||
zeroBytes[i] = Arrays.copyOfRange(fileEntryTable, 0x14, 0x18);
|
||||
}
|
||||
//**********************************************************************************************************
|
||||
// In here pointer in front of String table
|
||||
String[] subFileNames = new String[filesCount];
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
if (raf.read(stringTbl) != stringTableSize){
|
||||
throw new Exception("Read PFS0Provider String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||
}
|
||||
|
||||
for (int i=0; i < filesCount; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
subFileNames[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
for (int i = 0; i < filesCount; i++){
|
||||
pfs0subFiles[i] = new PFS0subFile(
|
||||
subFileNames[i],
|
||||
offsetsSubFiles[i],
|
||||
sizesSubFiles[i],
|
||||
zeroBytes[i]
|
||||
);
|
||||
}
|
||||
rawFileDataStart = raf.getFilePointer();
|
||||
raf.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncrypted() { return false; }
|
||||
@Override
|
||||
public String getMagic() { return magic; }
|
||||
@Override
|
||||
public int getFilesCount() { return filesCount; }
|
||||
@Override
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public byte[] getPadding() { return padding; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
@Override
|
||||
public PFS0subFile[] getPfs0subFiles() { return pfs0subFiles; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{ // TODO: Throw exceptions?
|
||||
if (subFileNumber >= pfs0subFiles.length) {
|
||||
throw new Exception("PFS0Provider -> getPfs0subFilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = rawFileDataStart + pfs0subFiles[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = pfs0subFiles[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (IOException ioe) {
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("PFS0Provider -> getPfs0subFilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Some sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < pfs0subFiles.length; i++){
|
||||
if (pfs0subFiles[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.PFS0;
|
||||
|
||||
public class PFS0subFile {
|
||||
private String name;
|
||||
private long offset; // REAL in file (including offset in NCA/NSP file)
|
||||
private long size;
|
||||
private byte[] zeroes;
|
||||
|
||||
public PFS0subFile(String name, long offset, long size, byte[] zeroesInTable){
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
this.zeroes = zeroesInTable;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public long getOffset() { return offset; }
|
||||
public long getSize() { return size; }
|
||||
public byte[] getZeroes() { return zeroes; }
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static konogonka.RainbowDump.formatDecHexString;
|
||||
|
||||
public class FileMeta4Debug {
|
||||
|
||||
List<FileMeta> allFiles;
|
||||
|
||||
FileMeta4Debug(long fileMetadataTableLength, byte[] fileMetadataTable) {
|
||||
allFiles = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (i < fileMetadataTableLength) {
|
||||
FileMeta fileMeta = new FileMeta();
|
||||
fileMeta.containingDirectoryOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.nextSiblingFileOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileDataOffset = LoperConverter.getLElong(fileMetadataTable, i);
|
||||
i += 8;
|
||||
fileMeta.fileDataLength = LoperConverter.getLElong(fileMetadataTable, i);
|
||||
i += 8;
|
||||
fileMeta.nextFileOffset = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileNameLength = LoperConverter.getLEint(fileMetadataTable, i);
|
||||
i += 4;
|
||||
fileMeta.fileName = new String(Arrays.copyOfRange(fileMetadataTable, i, i + fileMeta.fileNameLength), StandardCharsets.UTF_8);
|
||||
;
|
||||
i += getRealNameSize(fileMeta.fileNameLength);
|
||||
|
||||
allFiles.add(fileMeta);
|
||||
}
|
||||
|
||||
for (FileMeta fileMeta : allFiles){
|
||||
System.out.println(
|
||||
"-------------------------FILE--------------------------------\n" +
|
||||
"Offset of Containing Directory " + formatDecHexString(fileMeta.containingDirectoryOffset) + "\n" +
|
||||
"Offset of next Sibling File " + formatDecHexString(fileMeta.nextSiblingFileOffset) + "\n" +
|
||||
"Offset of File's Data " + formatDecHexString(fileMeta.fileDataOffset) + "\n" +
|
||||
"Length of File's Data " + formatDecHexString(fileMeta.fileDataLength) + "\n" +
|
||||
"Offset of next File in the same Hash Table bucket " + formatDecHexString(fileMeta.nextFileOffset) + "\n" +
|
||||
"Name Length " + formatDecHexString(fileMeta.fileNameLength) + "\n" +
|
||||
"Name Length (rounded up to multiple of 4) " + fileMeta.fileName + "\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
|
||||
private static class FileMeta{
|
||||
int containingDirectoryOffset;
|
||||
int nextSiblingFileOffset;
|
||||
long fileDataOffset;
|
||||
long fileDataLength;
|
||||
int nextFileOffset;
|
||||
int fileNameLength;
|
||||
String fileName;
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class FileSystemEntry {
|
||||
private boolean directoryFlag;
|
||||
private String name;
|
||||
private List<FileSystemEntry> content;
|
||||
|
||||
private static byte[] dirsMetadataTable;
|
||||
private static byte[] filesMetadataTable;
|
||||
|
||||
private long fileOffset;
|
||||
private long fileSize;
|
||||
|
||||
public FileSystemEntry(byte[] dirsMetadataTable, byte[] filesMetadataTable) throws Exception{
|
||||
FileSystemEntry.dirsMetadataTable = dirsMetadataTable;
|
||||
FileSystemEntry.filesMetadataTable = filesMetadataTable;
|
||||
this.content = new ArrayList<>();
|
||||
this.directoryFlag = true;
|
||||
DirectoryMetaData rootDirectoryMetaData = new DirectoryMetaData();
|
||||
if (rootDirectoryMetaData.dirName.isEmpty())
|
||||
this.name = "ROOT";
|
||||
else
|
||||
this.name = rootDirectoryMetaData.dirName;
|
||||
if (rootDirectoryMetaData.parentDirectoryOffset != 0)
|
||||
throw new Exception("Offset of Parent Directory is incorrect. Expected 0 for root, received value is "+ rootDirectoryMetaData.parentDirectoryOffset);
|
||||
if (rootDirectoryMetaData.nextSiblingDirectoryOffset != -1)
|
||||
throw new Exception("Offset of next Sibling Directory is incorrect. Expected -1 for root, received value is "+ rootDirectoryMetaData.nextSiblingDirectoryOffset);
|
||||
if (rootDirectoryMetaData.firstSubdirectoryOffset != -1)
|
||||
content.add(getDirectory(rootDirectoryMetaData.firstSubdirectoryOffset));
|
||||
if (rootDirectoryMetaData.firstFileOffset != -1)
|
||||
content.add(getFile(this, rootDirectoryMetaData.firstFileOffset));
|
||||
content.sort(Comparator.comparingLong(FileSystemEntry::getFileOffset));
|
||||
}
|
||||
|
||||
private FileSystemEntry(){
|
||||
this.content = new ArrayList<>();
|
||||
}
|
||||
|
||||
private FileSystemEntry getDirectory(int childDirMetaPosition){
|
||||
FileSystemEntry fileSystemEntry = new FileSystemEntry();
|
||||
fileSystemEntry.directoryFlag = true;
|
||||
|
||||
DirectoryMetaData directoryMetaData = new DirectoryMetaData(childDirMetaPosition);
|
||||
fileSystemEntry.name = directoryMetaData.dirName;
|
||||
|
||||
if (directoryMetaData.nextSiblingDirectoryOffset != -1)
|
||||
this.content.add(getDirectory(directoryMetaData.nextSiblingDirectoryOffset));
|
||||
|
||||
if (directoryMetaData.firstSubdirectoryOffset != -1)
|
||||
fileSystemEntry.content.add(getDirectory(directoryMetaData.firstSubdirectoryOffset));
|
||||
|
||||
if (directoryMetaData.firstFileOffset != -1)
|
||||
fileSystemEntry.content.add(getFile(fileSystemEntry, directoryMetaData.firstFileOffset));
|
||||
|
||||
fileSystemEntry.content.sort(Comparator.comparingLong(FileSystemEntry::getFileOffset));
|
||||
|
||||
return fileSystemEntry;
|
||||
}
|
||||
|
||||
private FileSystemEntry getFile(FileSystemEntry directoryContainer, int childFileMetaPosition){
|
||||
FileSystemEntry fileSystemEntry = new FileSystemEntry();
|
||||
fileSystemEntry.directoryFlag = false;
|
||||
|
||||
FileMetaData fileMetaData = new FileMetaData(childFileMetaPosition);
|
||||
fileSystemEntry.name = fileMetaData.fileName;
|
||||
fileSystemEntry.fileOffset = fileMetaData.fileDataRealOffset;
|
||||
fileSystemEntry.fileSize = fileMetaData.fileDataRealLength;
|
||||
if (fileMetaData.nextSiblingFileOffset != -1)
|
||||
directoryContainer.content.add( getFile(directoryContainer, fileMetaData.nextSiblingFileOffset) );
|
||||
|
||||
return fileSystemEntry;
|
||||
}
|
||||
|
||||
public boolean isDirectory() { return directoryFlag; }
|
||||
public boolean isFile() { return ! directoryFlag; }
|
||||
public long getFileOffset() { return fileOffset; }
|
||||
public long getFileSize() { return fileSize; }
|
||||
public List<FileSystemEntry> getContent() { return content; }
|
||||
public String getName(){ return name; }
|
||||
|
||||
|
||||
private static class DirectoryMetaData {
|
||||
private int parentDirectoryOffset;
|
||||
private int nextSiblingDirectoryOffset;
|
||||
private int firstSubdirectoryOffset;
|
||||
private int firstFileOffset;
|
||||
|
||||
private String dirName;
|
||||
|
||||
private DirectoryMetaData(){
|
||||
this(0);
|
||||
}
|
||||
private DirectoryMetaData(int childDirMetaPosition){
|
||||
int i = childDirMetaPosition;
|
||||
parentDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
nextSiblingDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
firstSubdirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
firstFileOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
// int nextHashTableBucketDirectoryOffset = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
int dirNameLength = LoperConverter.getLEint(dirsMetadataTable, i);
|
||||
i += 4;
|
||||
dirName = new String(Arrays.copyOfRange(dirsMetadataTable, i, i + dirNameLength), StandardCharsets.UTF_8);
|
||||
//i += getRealNameSize(dirNameLength);
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
}
|
||||
private static class FileMetaData {
|
||||
|
||||
private int nextSiblingFileOffset;
|
||||
private long fileDataRealOffset;
|
||||
private long fileDataRealLength;
|
||||
|
||||
private String fileName;
|
||||
|
||||
private FileMetaData(){
|
||||
this(0);
|
||||
}
|
||||
|
||||
private FileMetaData(int childFileMetaPosition){
|
||||
int i = childFileMetaPosition;
|
||||
// int containingDirectoryOffset = LoperConverter.getLEint(filesMetadataTable, i); // never used
|
||||
i += 4;
|
||||
nextSiblingFileOffset = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
fileDataRealOffset = LoperConverter.getLElong(filesMetadataTable, i);
|
||||
i += 8;
|
||||
fileDataRealLength = LoperConverter.getLElong(filesMetadataTable, i);
|
||||
i += 8;
|
||||
//int nextHashTableBucketFileOffset = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
int fileNameLength = LoperConverter.getLEint(filesMetadataTable, i);
|
||||
i += 4;
|
||||
fileName = new String(Arrays.copyOfRange(filesMetadataTable, i, i + fileNameLength), StandardCharsets.UTF_8);;
|
||||
//i += getRealNameSize(fileNameLength);
|
||||
}
|
||||
}
|
||||
|
||||
public void printTreeForDebug(){
|
||||
System.out.println("/");
|
||||
for (FileSystemEntry entry: content)
|
||||
printEntry(2, entry);
|
||||
}
|
||||
private void printEntry(int cnt, FileSystemEntry entry) {
|
||||
for (int i = 0; i < cnt; i++)
|
||||
System.out.print(" ");
|
||||
|
||||
if (entry.isDirectory()){
|
||||
System.out.println("|-" + entry.getName());
|
||||
for (FileSystemEntry e : entry.content)
|
||||
printEntry(cnt+2, e);
|
||||
}
|
||||
else
|
||||
System.out.println("|-" + entry.getName() + String.format(" 0x%-10x 0x%-10x", entry.fileOffset, entry.fileSize));
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static konogonka.RainbowDump.formatDecHexString;
|
||||
|
||||
public class FolderMeta4Debug {
|
||||
|
||||
List<FolderMeta> allFolders;
|
||||
|
||||
FolderMeta4Debug(long directoryMetadataTableLength, byte[] directoryMetadataTable){
|
||||
allFolders = new ArrayList<>();
|
||||
int i = 0;
|
||||
while (i < directoryMetadataTableLength){
|
||||
FolderMeta folderMeta = new FolderMeta();
|
||||
folderMeta.parentDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.nextSiblingDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.firstSubdirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.firstFileOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.nextDirectoryOffset = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.dirNameLength = LoperConverter.getLEint(directoryMetadataTable, i);
|
||||
i += 4;
|
||||
folderMeta.dirName = new String(Arrays.copyOfRange(directoryMetadataTable, i, i + folderMeta.dirNameLength), StandardCharsets.UTF_8);
|
||||
i += getRealNameSize(folderMeta.dirNameLength);
|
||||
|
||||
System.out.println(
|
||||
"---------------------------DIRECTORY------------------------\n" +
|
||||
"Offset of Parent Directory (self if Root) " + formatDecHexString(folderMeta.parentDirectoryOffset ) +"\n" +
|
||||
"Offset of next Sibling Directory " + formatDecHexString(folderMeta.nextSiblingDirectoryOffset) +"\n" +
|
||||
"Offset of first Child Directory (Subdirectory) " + formatDecHexString(folderMeta.firstSubdirectoryOffset ) +"\n" +
|
||||
"Offset of first File (in File Metadata Table) " + formatDecHexString(folderMeta.firstFileOffset ) +"\n" +
|
||||
"Offset of next Directory in the same Hash Table bucket " + formatDecHexString(folderMeta.nextDirectoryOffset ) +"\n" +
|
||||
"Name Length " + formatDecHexString(folderMeta.dirNameLength ) +"\n" +
|
||||
"Name Length (rounded up to multiple of 4) " + folderMeta.dirName + "\n"
|
||||
);
|
||||
|
||||
allFolders.add(folderMeta);
|
||||
}
|
||||
}
|
||||
|
||||
private int getRealNameSize(int value){
|
||||
if (value % 4 == 0)
|
||||
return value;
|
||||
return value + 4 - value % 4;
|
||||
}
|
||||
|
||||
private static class FolderMeta {
|
||||
int parentDirectoryOffset;
|
||||
int nextSiblingDirectoryOffset;
|
||||
int firstSubdirectoryOffset;
|
||||
int firstFileOffset;
|
||||
int nextDirectoryOffset;
|
||||
int dirNameLength;
|
||||
String dirName;
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PipedInputStream;
|
||||
|
||||
public interface IRomFsProvider {
|
||||
long getLevel6Offset();
|
||||
Level6Header getHeader();
|
||||
FileSystemEntry getRootEntry();
|
||||
PipedInputStream getContent(FileSystemEntry entry) throws Exception;
|
||||
File getFile();
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
import konogonka.RainbowDump;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Level6Header {
|
||||
private long headerLength;
|
||||
private long directoryHashTableOffset;
|
||||
private long directoryHashTableLength;
|
||||
private long directoryMetadataTableOffset;
|
||||
private long directoryMetadataTableLength;
|
||||
private long fileHashTableOffset;
|
||||
private long fileHashTableLength;
|
||||
private long fileMetadataTableOffset;
|
||||
private long fileMetadataTableLength;
|
||||
private long fileDataOffset;
|
||||
|
||||
private byte[] headerBytes;
|
||||
private int i;
|
||||
|
||||
Level6Header(byte[] headerBytes) throws Exception{
|
||||
this.headerBytes = headerBytes;
|
||||
if (headerBytes.length < 0x50)
|
||||
throw new Exception("Level 6 Header section is too small");
|
||||
headerLength = getNext();
|
||||
directoryHashTableOffset = getNext();
|
||||
directoryHashTableOffset <<= 32;
|
||||
directoryHashTableLength = getNext();
|
||||
directoryMetadataTableOffset = getNext();
|
||||
directoryMetadataTableLength = getNext();
|
||||
fileHashTableOffset = getNext();
|
||||
fileHashTableLength = getNext();
|
||||
fileMetadataTableOffset = getNext();
|
||||
fileMetadataTableLength = getNext();
|
||||
fileDataOffset = getNext();
|
||||
RainbowDump.hexDumpUTF8(Arrays.copyOfRange(headerBytes, 0, 0x50));
|
||||
}
|
||||
|
||||
private long getNext(){
|
||||
final long result = LoperConverter.getLEint(headerBytes, i);
|
||||
i += 0x8;
|
||||
return result;
|
||||
}
|
||||
|
||||
public long getHeaderLength() { return headerLength; }
|
||||
public long getDirectoryHashTableOffset() { return directoryHashTableOffset; }
|
||||
public long getDirectoryHashTableLength() { return directoryHashTableLength; }
|
||||
public long getDirectoryMetadataTableOffset() { return directoryMetadataTableOffset; }
|
||||
public long getDirectoryMetadataTableLength() { return directoryMetadataTableLength; }
|
||||
public long getFileHashTableOffset() { return fileHashTableOffset; }
|
||||
public long getFileHashTableLength() { return fileHashTableLength; }
|
||||
public long getFileMetadataTableOffset() { return fileMetadataTableOffset; }
|
||||
public long getFileMetadataTableLength() { return fileMetadataTableLength; }
|
||||
public long getFileDataOffset() { return fileDataOffset; }
|
||||
|
||||
public void printDebugInfo(){
|
||||
System.out.println("== Level 6 Header ==\n" +
|
||||
"Header Length (always 0x50 ?) "+ RainbowDump.formatDecHexString(headerLength)+" (size of this structure within first 0x200 block of LEVEL 6 part)\n" +
|
||||
"Directory Hash Table Offset "+ RainbowDump.formatDecHexString(directoryHashTableOffset)+" (against THIS block where HEADER contains)\n" +
|
||||
"Directory Hash Table Length "+ RainbowDump.formatDecHexString(directoryHashTableLength) + "\n" +
|
||||
"Directory Metadata Table Offset "+ RainbowDump.formatDecHexString(directoryMetadataTableOffset) + "\n" +
|
||||
"Directory Metadata Table Length "+ RainbowDump.formatDecHexString(directoryMetadataTableLength) + "\n" +
|
||||
"File Hash Table Offset "+ RainbowDump.formatDecHexString(fileHashTableOffset) + "\n" +
|
||||
"File Hash Table Length "+ RainbowDump.formatDecHexString(fileHashTableLength) + "\n" +
|
||||
"File Metadata Table Offset "+ RainbowDump.formatDecHexString(fileMetadataTableOffset) + "\n" +
|
||||
"File Metadata Table Length "+ RainbowDump.formatDecHexString(fileMetadataTableLength) + "\n" +
|
||||
"File Data Offset "+ RainbowDump.formatDecHexString(fileDataOffset) + "\n" +
|
||||
"-------------------------------------------------------------"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class RomFsDecryptedProvider implements IRomFsProvider{
|
||||
|
||||
private long level6Offset;
|
||||
|
||||
private File file;
|
||||
private Level6Header header;
|
||||
|
||||
private FileSystemEntry rootEntry;
|
||||
|
||||
public RomFsDecryptedProvider(File decryptedFsImageFile, long level6Offset) throws Exception{
|
||||
if (level6Offset < 0)
|
||||
throw new Exception("Incorrect Level 6 Offset");
|
||||
|
||||
this.file = decryptedFsImageFile;
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(decryptedFsImageFile));
|
||||
|
||||
this.level6Offset = level6Offset;
|
||||
|
||||
skipBytes(bis, level6Offset);
|
||||
|
||||
byte[] rawDataChunk = new byte[0x50];
|
||||
|
||||
if (bis.read(rawDataChunk) != 0x50)
|
||||
throw new Exception("Failed to read header (0x50)");
|
||||
|
||||
this.header = new Level6Header(rawDataChunk);
|
||||
/*
|
||||
// Print Dir Hash table as is:
|
||||
long seekTo = header.getDirectoryHashTableOffset() - 0x50;
|
||||
rawDataChunk = new byte[(int) header.getDirectoryHashTableLength()];
|
||||
skipTo(bis, seekTo);
|
||||
if (bis.read(rawDataChunk) != (int) header.getDirectoryHashTableLength())
|
||||
throw new Exception("Failed to read Dir hash table");
|
||||
RainbowDump.hexDumpUTF8(rawDataChunk);
|
||||
// Print Files Hash table as is:
|
||||
seekTo = header.getFileHashTableOffset() - header.getDirectoryMetadataTableOffset();
|
||||
rawDataChunk = new byte[(int) header.getFileHashTableLength()];
|
||||
skipTo(bis, seekTo);
|
||||
if (bis.read(rawDataChunk) != (int) header.getFileHashTableLength())
|
||||
throw new Exception("Failed to read Files hash table");
|
||||
RainbowDump.hexDumpUTF8(rawDataChunk);
|
||||
*/
|
||||
// Read directories metadata
|
||||
long locationInFile = header.getDirectoryMetadataTableOffset() - 0x50;
|
||||
|
||||
skipBytes(bis, locationInFile);
|
||||
|
||||
if (header.getDirectoryMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported operation.");
|
||||
|
||||
byte[] directoryMetadataTable = new byte[(int) header.getDirectoryMetadataTableLength()];
|
||||
|
||||
if (bis.read(directoryMetadataTable) != (int) header.getDirectoryMetadataTableLength())
|
||||
throw new Exception("Failed to read "+header.getDirectoryMetadataTableLength());
|
||||
// Read files metadata
|
||||
locationInFile = header.getFileMetadataTableOffset() - header.getFileHashTableOffset(); // TODO: replace to 'CurrentPosition'?
|
||||
|
||||
skipBytes(bis, locationInFile);
|
||||
|
||||
if (header.getFileMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported operation.");
|
||||
|
||||
byte[] fileMetadataTable = new byte[(int) header.getFileMetadataTableLength()];
|
||||
|
||||
if (bis.read(fileMetadataTable) != (int) header.getFileMetadataTableLength())
|
||||
throw new Exception("Failed to read "+header.getFileMetadataTableLength());
|
||||
|
||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||
//printDebug(directoryMetadataTable, fileMetadataTable);
|
||||
bis.close();
|
||||
}
|
||||
private void skipBytes(BufferedInputStream bis, long size) throws Exception{
|
||||
long mustSkip = size;
|
||||
long skipped = 0;
|
||||
while (mustSkip > 0){
|
||||
skipped += bis.skip(mustSkip);
|
||||
mustSkip = size - skipped;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public long getLevel6Offset() { return level6Offset; }
|
||||
@Override
|
||||
public Level6Header getHeader() { return header; }
|
||||
@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 doesn't make sense.");
|
||||
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||
try {
|
||||
long subFileRealPosition = level6Offset + header.getFileDataOffset() + entry.getFileOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
skipBytes(bis, subFileRealPosition);
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = entry.getFileSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize) {
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
@Override
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
||||
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
||||
rootEntry.printTreeForDebug();
|
||||
}
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
/*
|
||||
* Copyright 2019-2020 Dmitry Isaenko
|
||||
*
|
||||
* This file is part of Konogonka.
|
||||
*
|
||||
* Konogonka 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.
|
||||
*
|
||||
* Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package konogonka.Tools.RomFs;
|
||||
|
||||
import konogonka.ctraes.AesCtrDecryptSimple;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RomFsEncryptedProvider implements IRomFsProvider{
|
||||
|
||||
private long level6Offset;
|
||||
|
||||
private File file;
|
||||
private Level6Header header;
|
||||
|
||||
private FileSystemEntry rootEntry;
|
||||
|
||||
//--------------------------------
|
||||
|
||||
private long romFSoffsetPosition;
|
||||
private byte[] key;
|
||||
private byte[] sectionCTR;
|
||||
private long mediaStartOffset;
|
||||
private long mediaEndOffset;
|
||||
|
||||
public RomFsEncryptedProvider(long romFSoffsetPosition,
|
||||
long level6Offset,
|
||||
File fileWithEncPFS0,
|
||||
byte[] key,
|
||||
byte[] sectionCTR,
|
||||
long mediaStartOffset,
|
||||
long mediaEndOffset
|
||||
) throws Exception{
|
||||
this.file = fileWithEncPFS0;
|
||||
this.level6Offset = level6Offset;
|
||||
this.romFSoffsetPosition = romFSoffsetPosition;
|
||||
this.key = key;
|
||||
this.sectionCTR = sectionCTR;
|
||||
this.mediaStartOffset = mediaStartOffset;
|
||||
this.mediaEndOffset = mediaEndOffset;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||
raf.seek(abosluteOffsetPosition + level6Offset);
|
||||
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
//Go to Level 6 header
|
||||
decryptor.skipNext(level6Offset / 0x200);
|
||||
|
||||
// Decrypt data
|
||||
byte[] encryptedBlock = new byte[0x200];
|
||||
byte[] dectyptedBlock;
|
||||
if (raf.read(encryptedBlock) == 0x200)
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
else
|
||||
throw new Exception("Failed to read header header (0x200 - block)");
|
||||
|
||||
this.header = new Level6Header(dectyptedBlock);
|
||||
|
||||
header.printDebugInfo();
|
||||
|
||||
if (header.getDirectoryMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported: DirectoryMetadataTableLength < 0");
|
||||
|
||||
if (header.getFileMetadataTableLength() < 0)
|
||||
throw new Exception("Not supported: FileMetadataTableLength < 0");
|
||||
|
||||
/*---------------------------------*/
|
||||
|
||||
// Read directories metadata
|
||||
byte[] directoryMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||
header.getDirectoryMetadataTableOffset(),
|
||||
header.getDirectoryMetadataTableLength(),
|
||||
raf);
|
||||
|
||||
// Read files metadata
|
||||
byte[] fileMetadataTable = readMetaTable(abosluteOffsetPosition,
|
||||
header.getFileMetadataTableOffset(),
|
||||
header.getFileMetadataTableLength(),
|
||||
raf);
|
||||
|
||||
rootEntry = new FileSystemEntry(directoryMetadataTable, fileMetadataTable);
|
||||
|
||||
raf.close();
|
||||
}
|
||||
|
||||
private byte[] readMetaTable(long abosluteOffsetPosition,
|
||||
long metaOffset,
|
||||
long metaSize,
|
||||
RandomAccessFile raf) throws Exception{
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
byte[] metadataTable = new byte[(int) metaSize];
|
||||
//0
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
long startBlock = metaOffset / 0x200;
|
||||
|
||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||
|
||||
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||
|
||||
//1
|
||||
long ignoreBytes = metaOffset - startBlock * 0x200;
|
||||
long currentPosition = 0;
|
||||
|
||||
if (ignoreBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - ignoreBytes) > metaSize){
|
||||
metadataTable = Arrays.copyOfRange(dectyptedBlock, (int)ignoreBytes, 0x200);
|
||||
return metadataTable;
|
||||
}
|
||||
else {
|
||||
System.arraycopy(dectyptedBlock, (int) ignoreBytes, metadataTable, 0, 0x200 - (int) ignoreBytes);
|
||||
currentPosition = 0x200 - ignoreBytes;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory 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) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, 0x200);
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory 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) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
System.arraycopy(dectyptedBlock, 0, metadataTable, (int) currentPosition, Math.abs(extraData));
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
}
|
||||
|
||||
return metadataTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLevel6Offset() { return level6Offset; }
|
||||
@Override
|
||||
public Level6Header getHeader() { return header; }
|
||||
@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 doesn't make sense.");
|
||||
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Executing thread");
|
||||
try {
|
||||
|
||||
byte[] encryptedBlock;
|
||||
byte[] dectyptedBlock;
|
||||
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r");
|
||||
|
||||
//0
|
||||
AesCtrDecryptSimple decryptor = new AesCtrDecryptSimple(key, sectionCTR, mediaStartOffset * 0x200);
|
||||
|
||||
long startBlock = (entry.getFileOffset() + header.getFileDataOffset()) / 0x200;
|
||||
|
||||
decryptor.skipNext(level6Offset / 0x200 + startBlock);
|
||||
|
||||
long abosluteOffsetPosition = romFSoffsetPosition + (mediaStartOffset * 0x200);
|
||||
|
||||
raf.seek(abosluteOffsetPosition + level6Offset + startBlock * 0x200);
|
||||
|
||||
//1
|
||||
long ignoreBytes = (entry.getFileOffset() + header.getFileDataOffset()) - startBlock * 0x200;
|
||||
|
||||
if (ignoreBytes > 0) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
// If we have extra-small file that is less then a block and even more
|
||||
if ((0x200 - ignoreBytes) > entry.getFileSize()){
|
||||
streamOut.write(dectyptedBlock, (int)ignoreBytes, (int) entry.getFileSize()); // safe cast
|
||||
raf.close();
|
||||
streamOut.close();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
streamOut.write(dectyptedBlock, (int) ignoreBytes, 0x200 - (int) ignoreBytes);
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from 1st bock for Directory Metadata Table");
|
||||
}
|
||||
startBlock++;
|
||||
}
|
||||
long endBlock = (entry.getFileSize() + ignoreBytes) / 0x200 + startBlock; // <- pointing to place where any data related to this media-block ends
|
||||
|
||||
//2
|
||||
int extraData = (int) ((endBlock - startBlock)*0x200 - (entry.getFileSize() + ignoreBytes));
|
||||
|
||||
if (extraData < 0)
|
||||
endBlock--;
|
||||
//3
|
||||
while ( startBlock < endBlock ) {
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock);
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
|
||||
startBlock++;
|
||||
}
|
||||
|
||||
//4
|
||||
if (extraData != 0){ // In case we didn't get what we want
|
||||
encryptedBlock = new byte[0x200];
|
||||
if (raf.read(encryptedBlock) == 0x200) {
|
||||
dectyptedBlock = decryptor.dectyptNext(encryptedBlock);
|
||||
streamOut.write(dectyptedBlock, 0, Math.abs(extraData));
|
||||
}
|
||||
else
|
||||
throw new Exception("RomFsEncryptedProvider(): Unable to get 512 bytes from block for Directory Metadata Table");
|
||||
}
|
||||
raf.close();
|
||||
streamOut.close();
|
||||
} catch (Exception e) {
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Unable to provide stream");
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.out.println("RomFsDecryptedProvider -> getContent(): Thread is dead");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
private void printDebug(byte[] directoryMetadataTable, byte[] fileMetadataTable){
|
||||
new FolderMeta4Debug(header.getDirectoryMetadataTableLength(), directoryMetadataTable);
|
||||
new FileMeta4Debug(header.getFileMetadataTableLength(), fileMetadataTable);
|
||||
rootEntry.printTreeForDebug();
|
||||
}
|
||||
}
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.TIK;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
/*
|
||||
|
||||
DON'T TRUST WIKI. Ticket size always (?) equal 0x02c0 (704 bytes)
|
||||
|
||||
File structure byte-by-byte parsing
|
||||
|
||||
Starting:
|
||||
0x4 - Signature type
|
||||
Signature type == 00 00 01 00 ?
|
||||
Next:
|
||||
0x200 - Signature size
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x100 - Signature size
|
||||
Signature type == 02 00 01 00 ?
|
||||
0x3c - Signature size
|
||||
Signature type == 03 00 01 00 ?
|
||||
0x200 - Signature size
|
||||
Signature type == 04 00 01 00 ?
|
||||
0x100 - Signature size
|
||||
Signature type == 05 00 01 00 ?
|
||||
0x3c - Signature size
|
||||
Next:
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x3c - padding
|
||||
Signature type == 01 00 01 00 ?
|
||||
0x3c - padding
|
||||
Signature type == 02 00 01 00 ?
|
||||
0x40 - padding
|
||||
Signature type == 03 00 01 00 ?
|
||||
0c3c - padding
|
||||
Signature type == 04 00 01 00 ?
|
||||
0c3c - padding
|
||||
Signature type == 05 00 01 00 ?
|
||||
0x40 - padding
|
||||
Next:
|
||||
0x02c0 - Signature data ????? WTF? MUST BE AND IMPLEMENTED AS 0x180
|
||||
*/
|
||||
/**
|
||||
* TIKProvider is not a container, thus not a content-provider but provider of values-only
|
||||
* */
|
||||
public class TIKProvider {
|
||||
// Signature-related
|
||||
private byte[] sigType;
|
||||
private byte[] signature;
|
||||
// Ticket
|
||||
private String Issuer;
|
||||
private byte[] TitleKeyBlockStartingBytes; // Actually 32 bytes.
|
||||
private byte[] TitleKeyBlockEndingBytes; // Anything else
|
||||
private byte Unknown1;
|
||||
private byte TitleKeyType;
|
||||
private byte[] Unknown2;
|
||||
private byte MasterKeyRevision;
|
||||
private byte[] Unknown3;
|
||||
private byte[] TicketId;
|
||||
private byte[] DeviceId;
|
||||
private byte[] RightsId;
|
||||
private byte[] RightsIdEndingBytes;
|
||||
private byte[] AccountId;
|
||||
private byte[] Unknown4;
|
||||
|
||||
public TIKProvider(File file) throws Exception{ this(file, 0); }
|
||||
|
||||
public TIKProvider(File file, long offset) throws Exception {
|
||||
|
||||
if (file.length() - offset < 0x02c0)
|
||||
throw new Exception("TIKProvider: File is too small.");
|
||||
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(offset) != offset) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to skip requested range - " + offset);
|
||||
}
|
||||
|
||||
sigType = new byte[0x4];
|
||||
if (bis.read(sigType) != 4) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - " + offset);
|
||||
}
|
||||
|
||||
byte[] readChunk;
|
||||
|
||||
switch (getLEint(sigType, 0)){
|
||||
case 65536: // RSA_4096 SHA1
|
||||
case 65539: // RSA_4096 SHA256
|
||||
readChunk = new byte[0x23c];
|
||||
if (bis.read(readChunk) != 0x23c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x23c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x200);
|
||||
break;
|
||||
case 65537: // RSA_2048 SHA1
|
||||
case 65540: // RSA_2048 SHA256
|
||||
readChunk = new byte[0x13c];
|
||||
if (bis.read(readChunk) != 0x13c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x13c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x100);
|
||||
break;
|
||||
case 65538: // ECDSA SHA1
|
||||
case 65541: // ECDSA SHA256
|
||||
readChunk = new byte[0x7c];
|
||||
if (bis.read(readChunk) != 0x7c) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - 0x7c");
|
||||
}
|
||||
signature = Arrays.copyOfRange(readChunk, 0, 0x3c);
|
||||
break;
|
||||
default:
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unknown ticket (Signature) type. Aborting.");
|
||||
}
|
||||
// Let's read ticket body itself
|
||||
readChunk = new byte[0x180];
|
||||
|
||||
if (bis.read(readChunk) != 0x180) {
|
||||
bis.close();
|
||||
throw new Exception("TIKProvider: Unable to read requested range - Ticket data");
|
||||
}
|
||||
bis.close();
|
||||
|
||||
Issuer = new String(readChunk, 0, 0x40, StandardCharsets.UTF_8);
|
||||
TitleKeyBlockStartingBytes = Arrays.copyOfRange(readChunk, 0x40, 0x50);
|
||||
TitleKeyBlockEndingBytes = Arrays.copyOfRange(readChunk, 0x50, 0x140);
|
||||
Unknown1 = readChunk[0x140];
|
||||
TitleKeyType = readChunk[0x141];
|
||||
Unknown2 = Arrays.copyOfRange(readChunk, 0x142, 0x145);
|
||||
MasterKeyRevision = readChunk[0x145];
|
||||
Unknown3 = Arrays.copyOfRange(readChunk, 0x146, 0x150);
|
||||
TicketId = Arrays.copyOfRange(readChunk, 0x150, 0x158);
|
||||
DeviceId = Arrays.copyOfRange(readChunk, 0x158, 0x160);
|
||||
RightsId = Arrays.copyOfRange(readChunk, 0x160, 0x170);
|
||||
AccountId = Arrays.copyOfRange(readChunk,0x170, 0x174);
|
||||
Unknown4 = Arrays.copyOfRange(readChunk, 0x174, 0x180);
|
||||
}
|
||||
|
||||
public byte[] getSigType() { return sigType; }
|
||||
public byte[] getSignature() { return signature; }
|
||||
|
||||
public String getIssuer() { return Issuer; }
|
||||
public byte[] getTitleKeyBlockStartingBytes() { return TitleKeyBlockStartingBytes; }
|
||||
public byte[] getTitleKeyBlockEndingBytes() { return TitleKeyBlockEndingBytes; }
|
||||
public byte getUnknown1() { return Unknown1; }
|
||||
public byte getTitleKeyType() { return TitleKeyType; }
|
||||
public byte[] getUnknown2() { return Unknown2; }
|
||||
public byte getMasterKeyRevision() { return MasterKeyRevision; }
|
||||
public byte[] getUnknown3() { return Unknown3; }
|
||||
public byte[] getTicketId() { return TicketId; }
|
||||
public byte[] getDeviceId() { return DeviceId; }
|
||||
public byte[] getRightsId() { return RightsId; }
|
||||
public byte[] getAccountId() { return AccountId; }
|
||||
public byte[] getUnknown4() { return Unknown4; }
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
public class HFS0File {
|
||||
private String name;
|
||||
private long offset;
|
||||
private long size;
|
||||
private long hashedRegionSize;
|
||||
private boolean padding;
|
||||
private byte[] SHA256Hash;
|
||||
|
||||
public HFS0File(String name, long offset, long size, long hashedRegionSize, boolean padding, byte[] SHA256Hash){
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
this.hashedRegionSize = hashedRegionSize;
|
||||
this.padding = padding;
|
||||
this.SHA256Hash = SHA256Hash;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public long getOffset() { return offset; }
|
||||
public long getSize() { return size; }
|
||||
public long getHashedRegionSize() { return hashedRegionSize; }
|
||||
public boolean isPadding() { return padding; }
|
||||
public byte[] getSHA256Hash() { return SHA256Hash; }
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.*;
|
||||
|
||||
/**
|
||||
* HFS0
|
||||
* */
|
||||
public class HFS0Provider implements ISuperProvider {
|
||||
|
||||
private boolean magicHFS0;
|
||||
private int filesCnt;
|
||||
private boolean paddingHfs0;
|
||||
private int stringTableSize;
|
||||
private long rawFileDataStart;
|
||||
|
||||
private HFS0File[] hfs0Files;
|
||||
|
||||
private File file;
|
||||
|
||||
HFS0Provider(long hfsOffsetPosition, RandomAccessFile raf, File file) throws Exception{
|
||||
this.file = file; // Will be used @ getHfs0FilePipedInpStream. It's a bad implementation.
|
||||
byte[] hfs0bytes = new byte[16];
|
||||
try{
|
||||
raf.seek(hfsOffsetPosition);
|
||||
if (raf.read(hfs0bytes) != 16){
|
||||
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];
|
||||
|
||||
// TODO: IF NOT EMPTY TABLE:
|
||||
|
||||
long[] offsetHfs0files = new long[filesCnt];
|
||||
long[] sizeHfs0files = new long[filesCnt];
|
||||
int[] hashedRegionSizeHfs0Files = new int[filesCnt];
|
||||
boolean[] paddingHfs0Files = new boolean[filesCnt];
|
||||
byte[][] SHA256HashHfs0Files = new byte[filesCnt][];
|
||||
int[] strTableOffsets = new int[filesCnt];
|
||||
|
||||
try {
|
||||
// Populate meta information regarding each file inside (?) HFS0
|
||||
byte[] metaInfoBytes = new byte[64];
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
if (raf.read(metaInfoBytes) != 64) {
|
||||
throw new Exception("Read HFS0 File Entry Table failure for file # "+i);
|
||||
}
|
||||
offsetHfs0files[i] = getLElong(metaInfoBytes, 0);
|
||||
sizeHfs0files[i] = getLElong(metaInfoBytes, 8);
|
||||
hashedRegionSizeHfs0Files[i] = getLEint(metaInfoBytes, 20);
|
||||
paddingHfs0Files[i] = Arrays.equals(Arrays.copyOfRange(metaInfoBytes, 24, 32), new byte[8]);
|
||||
SHA256HashHfs0Files[i] = Arrays.copyOfRange(metaInfoBytes, 32, 64);
|
||||
|
||||
strTableOffsets[i] = getLEint(metaInfoBytes, 16);
|
||||
}
|
||||
// Define location of actual data for this HFS0
|
||||
rawFileDataStart = raf.getFilePointer()+stringTableSize;
|
||||
if (stringTableSize <= 0)
|
||||
throw new Exception("String table size of HFS0 less or equal to zero");
|
||||
byte[] stringTbl = new byte[stringTableSize];
|
||||
if (raf.read(stringTbl) != stringTableSize){
|
||||
throw new Exception("Read HFS0 String table failure. Can't read requested string table size ("+stringTableSize+")");
|
||||
}
|
||||
String[] namesHfs0files = new String[filesCnt];
|
||||
// Parse string table
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
int j = 0;
|
||||
while (stringTbl[strTableOffsets[i]+j] != (byte)0x00)
|
||||
j++;
|
||||
namesHfs0files[i] = new String(stringTbl, strTableOffsets[i], j, StandardCharsets.UTF_8);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------------
|
||||
// Set files
|
||||
for (int i=0; i < filesCnt; i++){
|
||||
hfs0Files[i] = new HFS0File(
|
||||
namesHfs0files[i],
|
||||
offsetHfs0files[i],
|
||||
sizeHfs0files[i],
|
||||
hashedRegionSizeHfs0Files[i],
|
||||
paddingHfs0Files[i],
|
||||
SHA256HashHfs0Files[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (IOException ioe){
|
||||
throw new Exception("Read HFS0 structure failure: "+ioe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isMagicHFS0() { return magicHFS0; }
|
||||
public int getFilesCnt() { return filesCnt; }
|
||||
public boolean isPaddingHfs0() { return paddingHfs0; }
|
||||
public int getStringTableSize() { return stringTableSize; }
|
||||
@Override
|
||||
public long getRawFileDataStart() { return rawFileDataStart; }
|
||||
public HFS0File[] getHfs0Files() { return hfs0Files; }
|
||||
@Override
|
||||
public File getFile(){ return file; }
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(int subFileNumber) throws Exception{
|
||||
PipedOutputStream streamOut = new PipedOutputStream();
|
||||
Thread workerThread;
|
||||
if (subFileNumber >= hfs0Files.length) {
|
||||
throw new Exception("HFS0Provider -> getHfs0FilePipedInpStream(): Requested sub file doesn't exists");
|
||||
}
|
||||
PipedInputStream streamIn = new PipedInputStream(streamOut);
|
||||
|
||||
workerThread = new Thread(() -> {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Executing thread");
|
||||
try{
|
||||
long subFileRealPosition = rawFileDataStart + hfs0Files[subFileNumber].getOffset();
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
if (bis.skip(subFileRealPosition) != subFileRealPosition) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to skip requested offset");
|
||||
return;
|
||||
}
|
||||
|
||||
int readPice = 8388608; // 8mb NOTE: consider switching to 1mb 1048576
|
||||
|
||||
long readFrom = 0;
|
||||
long realFileSize = hfs0Files[subFileNumber].getSize();
|
||||
|
||||
byte[] readBuf;
|
||||
|
||||
while (readFrom < realFileSize){
|
||||
if (realFileSize - readFrom < readPice)
|
||||
readPice = Math.toIntExact(realFileSize - readFrom); // it's safe, I guarantee
|
||||
readBuf = new byte[readPice];
|
||||
if (bis.read(readBuf) != readPice) {
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to read requested size from file.");
|
||||
return;
|
||||
}
|
||||
streamOut.write(readBuf, 0, readPice);
|
||||
readFrom += readPice;
|
||||
}
|
||||
bis.close();
|
||||
streamOut.close();
|
||||
}
|
||||
catch (IOException ioe){
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Unable to provide stream");
|
||||
ioe.printStackTrace();
|
||||
}
|
||||
System.out.println("HFS0Provider -> getHfs0FilePipedInpStream(): Thread died");
|
||||
});
|
||||
workerThread.start();
|
||||
return streamIn;
|
||||
}
|
||||
/**
|
||||
* Sugar
|
||||
* */
|
||||
@Override
|
||||
public PipedInputStream getProviderSubFilePipedInpStream(String subFileName) throws Exception {
|
||||
for (int i = 0; i < hfs0Files.length; i++){
|
||||
if (hfs0Files[i].getName().equals(subFileName))
|
||||
return getProviderSubFilePipedInpStream(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Gamecard Cert
|
||||
* */
|
||||
public class XCIGamecardCert {
|
||||
private byte[] rsa2048PKCS1sig;
|
||||
private byte[] magicCert;
|
||||
private byte[] unknown1;
|
||||
private byte kekIndex;
|
||||
private byte[] unknown2;
|
||||
private byte[] deviceID;
|
||||
private byte[] unknown3;
|
||||
private byte[] encryptedData;
|
||||
|
||||
XCIGamecardCert(byte[] certBytes) throws Exception{
|
||||
if (certBytes.length != 512)
|
||||
throw new Exception("XCIGamecardCert Incorrect array size. Expected 512 bytes while received "+certBytes.length);
|
||||
rsa2048PKCS1sig = Arrays.copyOfRange(certBytes, 0, 256);
|
||||
magicCert = Arrays.copyOfRange(certBytes, 256, 260);
|
||||
unknown1 = Arrays.copyOfRange(certBytes, 260, 264);
|
||||
kekIndex = certBytes[264];
|
||||
unknown2 = Arrays.copyOfRange(certBytes, 265, 272);
|
||||
deviceID = Arrays.copyOfRange(certBytes, 272, 288);
|
||||
unknown3 = Arrays.copyOfRange(certBytes, 288, 298);
|
||||
encryptedData = Arrays.copyOfRange(certBytes, 298, 512);
|
||||
/*
|
||||
RainbowHexDump.hexDumpUTF8(rsa2048PKCS1sig);
|
||||
RainbowHexDump.hexDumpUTF8(magicCert);
|
||||
RainbowHexDump.hexDumpUTF8(unknown1);
|
||||
System.out.println(kekIndex);
|
||||
RainbowHexDump.hexDumpUTF8(unknown2);
|
||||
RainbowHexDump.hexDumpUTF8(deviceID);
|
||||
RainbowHexDump.hexDumpUTF8(unknown3);
|
||||
RainbowHexDump.hexDumpUTF8(encryptedData);
|
||||
*/
|
||||
}
|
||||
public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; }
|
||||
public byte[] getMagicCert() { return magicCert; }
|
||||
public boolean isMagicCertOk(){ return Arrays.equals(magicCert, new byte[]{0x48, 0x45, 0x41, 0x44}); }
|
||||
public byte[] getUnknown1() { return unknown1; }
|
||||
public byte getKekIndex() { return kekIndex; }
|
||||
public byte[] getUnknown2() { return unknown2; }
|
||||
public byte[] getDeviceID() { return deviceID; }
|
||||
public byte[] getUnknown3() { return unknown3; }
|
||||
public byte[] getEncryptedData() { return encryptedData; }
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
/**
|
||||
* Header information
|
||||
* */
|
||||
public class XCIGamecardHeader{
|
||||
private byte[] rsa2048PKCS1sig;
|
||||
private boolean magicHead;
|
||||
private byte[] SecureAreaStartAddr;
|
||||
private boolean bkupAreaStartAddr;
|
||||
private byte titleKEKIndexBoth;
|
||||
private byte titleKEKIndex;
|
||||
private byte KEKIndex;
|
||||
private byte gcSize;
|
||||
private byte gcVersion;
|
||||
private byte gcFlags;
|
||||
private byte[] pkgID;
|
||||
private long valDataEndAddr;
|
||||
private byte[] gcInfoIV;
|
||||
private long hfs0partOffset;
|
||||
private long hfs0headerSize;
|
||||
private byte[] hfs0headerSHA256;
|
||||
private byte[] hfs0initDataSHA256 ;
|
||||
private int secureModeFlag;
|
||||
private int titleKeyFlag;
|
||||
private int keyFlag;
|
||||
private byte[] normAreaEndAddr;
|
||||
|
||||
XCIGamecardHeader(byte[] headerBytes) throws Exception{
|
||||
if (headerBytes.length != 400)
|
||||
throw new Exception("XCIGamecardHeader Incorrect array size. Expected 400 bytes while received "+headerBytes.length);
|
||||
rsa2048PKCS1sig = Arrays.copyOfRange(headerBytes, 0, 256);
|
||||
magicHead = Arrays.equals(Arrays.copyOfRange(headerBytes, 256, 260), new byte[]{0x48, 0x45, 0x41, 0x44});
|
||||
SecureAreaStartAddr = Arrays.copyOfRange(headerBytes, 260, 264);
|
||||
bkupAreaStartAddr = Arrays.equals(Arrays.copyOfRange(headerBytes, 264, 268), new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
|
||||
titleKEKIndexBoth = headerBytes[268];
|
||||
titleKEKIndex = (byte) ((titleKEKIndexBoth >> 4) & (byte) 0x0F);
|
||||
KEKIndex = (byte) (titleKEKIndexBoth & 0x0F);
|
||||
gcSize = headerBytes[269];
|
||||
gcVersion = headerBytes[270];
|
||||
gcFlags = headerBytes[271];
|
||||
pkgID = Arrays.copyOfRange(headerBytes, 272, 280);
|
||||
valDataEndAddr = getLElong(headerBytes, 280); //TODO: FIX/simplify //
|
||||
gcInfoIV = reverseBytes(Arrays.copyOfRange(headerBytes, 288, 304));
|
||||
hfs0partOffset = getLElong(headerBytes, 304);
|
||||
hfs0headerSize = getLElong(headerBytes, 312);
|
||||
hfs0headerSHA256 = Arrays.copyOfRange(headerBytes, 320, 352);
|
||||
hfs0initDataSHA256 = Arrays.copyOfRange(headerBytes, 352, 384);
|
||||
secureModeFlag = getLEint(headerBytes, 384);
|
||||
titleKeyFlag = getLEint(headerBytes, 388);
|
||||
keyFlag = getLEint(headerBytes, 392);
|
||||
normAreaEndAddr = Arrays.copyOfRange(headerBytes, 396, 400);
|
||||
}
|
||||
|
||||
public byte[] getRsa2048PKCS1sig() { return rsa2048PKCS1sig; }
|
||||
public boolean isMagicHeadOk() { return magicHead; }
|
||||
public byte[] getSecureAreaStartAddr() { return SecureAreaStartAddr; }
|
||||
public boolean isBkupAreaStartAddrOk() { return bkupAreaStartAddr; }
|
||||
public byte getTitleKEKIndexBoth() { return titleKEKIndexBoth; }
|
||||
public byte getTitleKEKIndex() { return titleKEKIndex; }
|
||||
public byte getKEKIndex() { return KEKIndex; }
|
||||
|
||||
public byte getGcSize() {
|
||||
return gcSize;
|
||||
}
|
||||
public byte getGcVersion() {
|
||||
return gcVersion;
|
||||
}
|
||||
public byte getGcFlags() {
|
||||
return gcFlags;
|
||||
}
|
||||
public byte[] getPkgID() {
|
||||
return pkgID;
|
||||
}
|
||||
public long getValDataEndAddr() {
|
||||
return valDataEndAddr;
|
||||
}
|
||||
public byte[] getGcInfoIV() {
|
||||
return gcInfoIV;
|
||||
}
|
||||
public long getHfs0partOffset() {
|
||||
return hfs0partOffset;
|
||||
}
|
||||
public long getHfs0headerSize() {
|
||||
return hfs0headerSize;
|
||||
}
|
||||
public byte[] getHfs0headerSHA256() {
|
||||
return hfs0headerSHA256;
|
||||
}
|
||||
public byte[] getHfs0initDataSHA256() {
|
||||
return hfs0initDataSHA256;
|
||||
}
|
||||
public int getSecureModeFlag() {
|
||||
return secureModeFlag;
|
||||
}
|
||||
public boolean isSecureModeFlagOk(){
|
||||
return secureModeFlag == 1;
|
||||
}
|
||||
public int getTitleKeyFlag() {
|
||||
return titleKeyFlag;
|
||||
}
|
||||
public boolean istitleKeyFlagOk(){
|
||||
return titleKeyFlag == 2;
|
||||
}
|
||||
public int getKeyFlag() {
|
||||
return keyFlag;
|
||||
}
|
||||
public boolean iskeyFlagOk(){
|
||||
return keyFlag == 0;
|
||||
}
|
||||
public byte[] getNormAreaEndAddr() {
|
||||
return normAreaEndAddr;
|
||||
}
|
||||
|
||||
private byte[] reverseBytes(byte[] bArr){
|
||||
Byte[] objArr = new Byte[bArr.length];
|
||||
for (int i=0;i < bArr.length; i++)
|
||||
objArr[i] = bArr[i];
|
||||
List<Byte> bytesList = Arrays.asList(objArr);
|
||||
Collections.reverse(bytesList);
|
||||
for (int i=0;i < bArr.length; i++)
|
||||
bArr[i] = objArr[i];
|
||||
return bArr;
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static konogonka.LoperConverter.getLEint;
|
||||
import static konogonka.LoperConverter.getLElong;
|
||||
|
||||
/**
|
||||
* Gamecard Info
|
||||
* */
|
||||
public class XCIGamecardInfo{
|
||||
|
||||
private long fwVersion;
|
||||
private byte[] accessCtrlFlags; // 0x00A10011 for 25MHz access or 0x00A10010 for 50MHz access
|
||||
private int readWaitTime1;
|
||||
private int readWaitTime2;
|
||||
private int writeWaitTime1;
|
||||
private int writeWaitTime2;
|
||||
private byte[] fwMode;
|
||||
private byte[] cupVersion;
|
||||
private byte[] emptyPadding1;
|
||||
private byte[] updPartHash;
|
||||
private byte[] cupID;
|
||||
private byte[] emptyPadding2;
|
||||
// todo: Add factory function instead
|
||||
XCIGamecardInfo(byte[] infoBytes, byte[] IV, String XCI_HEADER_KEY) throws Exception {
|
||||
if (XCI_HEADER_KEY.trim().isEmpty())
|
||||
return;
|
||||
if (infoBytes.length != 112)
|
||||
throw new Exception("XCIGamecardInfo Incorrect array size. Expected 112 bytes while received "+infoBytes.length);
|
||||
|
||||
IvParameterSpec gciIV = new IvParameterSpec(IV);
|
||||
SecretKeySpec skSpec = new SecretKeySpec(hexStrToByteArray(XCI_HEADER_KEY), "AES");
|
||||
|
||||
try {
|
||||
// NOTE: CBC
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skSpec, gciIV);
|
||||
|
||||
byte[] decrypted = cipher.doFinal(infoBytes);
|
||||
|
||||
fwVersion = getLElong(decrypted, 0);
|
||||
accessCtrlFlags = Arrays.copyOfRange(decrypted, 8, 12);
|
||||
readWaitTime1 = getLEint(decrypted, 12);
|
||||
readWaitTime2 = getLEint(decrypted, 16);
|
||||
writeWaitTime1 = getLEint(decrypted, 20);
|
||||
writeWaitTime2 = getLEint(decrypted, 24);
|
||||
fwMode = Arrays.copyOfRange(decrypted, 28, 32);
|
||||
cupVersion = Arrays.copyOfRange(decrypted, 32, 36);
|
||||
emptyPadding1 = Arrays.copyOfRange(decrypted, 36, 40);
|
||||
updPartHash = Arrays.copyOfRange(decrypted, 40, 48);
|
||||
cupID = Arrays.copyOfRange(decrypted, 48, 56);
|
||||
emptyPadding2 = Arrays.copyOfRange(decrypted, 56, 112);
|
||||
/*
|
||||
System.out.println(fwVersion);
|
||||
RainbowHexDump.hexDumpUTF8(accessCtrlFlags);
|
||||
System.out.println(readWaitTime1);
|
||||
System.out.println(readWaitTime2);
|
||||
System.out.println(writeWaitTime1);
|
||||
System.out.println(writeWaitTime2);
|
||||
RainbowHexDump.hexDumpUTF8(fwMode);
|
||||
RainbowHexDump.hexDumpUTF8(cupVersion);
|
||||
RainbowHexDump.hexDumpUTF8(emptyPadding1);
|
||||
RainbowHexDump.hexDumpUTF8(updPartHash);
|
||||
RainbowHexDump.hexDumpUTF8(cupID);
|
||||
RainbowHexDump.hexDumpUTF8(emptyPadding2);
|
||||
*/
|
||||
} catch (Exception e) {
|
||||
throw new Exception("XCIGamecardInfo Decryption failed: \n "+e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
private byte[] hexStrToByteArray(String s) { // thanks stackoverflow
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public long getFwVersion() { return fwVersion; }
|
||||
public byte[] getAccessCtrlFlags() { return accessCtrlFlags; }
|
||||
public int getReadWaitTime1() { return readWaitTime1; }
|
||||
public int getReadWaitTime2() { return readWaitTime2; }
|
||||
public int getWriteWaitTime1() { return writeWaitTime1; }
|
||||
public int getWriteWaitTime2() { return writeWaitTime2; }
|
||||
|
||||
public byte[] getFwMode() { return fwMode; }
|
||||
public byte[] getCupVersion() { return cupVersion; }
|
||||
public boolean isEmptyPadding1() { return Arrays.equals(emptyPadding1, new byte[4]); }
|
||||
public byte[] getEmptyPadding1() { return emptyPadding1; }
|
||||
public byte[] getUpdPartHash() { return updPartHash; }
|
||||
public byte[] getCupID() { return cupID; }
|
||||
public boolean isEmptyPadding2() { return Arrays.equals(emptyPadding2, new byte[56]); }
|
||||
public byte[] getEmptyPadding2() { return emptyPadding2; }
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.Tools.XCI;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class XCIProvider{
|
||||
// TODO: Since LOGO partition added, we have to handle it properly. Is it works??
|
||||
|
||||
//private BufferedInputStream xciBIS;
|
||||
private XCIGamecardHeader xciGamecardHeader;
|
||||
private XCIGamecardInfo xciGamecardInfo;
|
||||
private XCIGamecardCert xciGamecardCert;
|
||||
private HFS0Provider hfs0ProviderMain,
|
||||
hfs0ProviderUpdate,
|
||||
hfs0ProviderNormal,
|
||||
hfs0ProviderSecure,
|
||||
hfs0ProviderLogo;
|
||||
|
||||
public XCIProvider(File file, String XCI_HEADER_KEY) throws Exception{ // TODO: ADD FILE SIZE CHECK !!! Check xciHdrKey
|
||||
RandomAccessFile raf;
|
||||
|
||||
try {
|
||||
//xciBIS = new BufferedInputStream(new FileInputStream(file));
|
||||
raf = new RandomAccessFile(file, "r");
|
||||
}
|
||||
catch (FileNotFoundException fnfe){
|
||||
throw new Exception("XCI File not found: \n "+fnfe.getMessage());
|
||||
}
|
||||
|
||||
if (file.length() < 0xf010)
|
||||
throw new Exception("XCI File is too small.");
|
||||
|
||||
try{
|
||||
byte[] gamecardHeaderBytes = new byte[400];
|
||||
byte[] gamecardInfoBytes = new byte[112];
|
||||
byte[] gamecardCertBytes = new byte[512];
|
||||
|
||||
// Creating GC Header class
|
||||
if (raf.read(gamecardHeaderBytes) != 400) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard Header bytes.");
|
||||
}
|
||||
xciGamecardHeader = new XCIGamecardHeader(gamecardHeaderBytes); // throws exception
|
||||
// Creating GC Info class
|
||||
if (raf.read(gamecardInfoBytes) != 112) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard Header bytes.");
|
||||
}
|
||||
xciGamecardInfo = new XCIGamecardInfo(gamecardInfoBytes, xciGamecardHeader.getGcInfoIV(), XCI_HEADER_KEY);
|
||||
// Creating GC Cerfificate class
|
||||
raf.seek(0x7000);
|
||||
if (raf.read(gamecardCertBytes) != 512) {
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
||||
}
|
||||
xciGamecardCert = new XCIGamecardCert(gamecardCertBytes);
|
||||
|
||||
hfs0ProviderMain = new HFS0Provider(0xf000, raf, file);
|
||||
if (hfs0ProviderMain.getFilesCnt() < 3){
|
||||
raf.close();
|
||||
throw new Exception("XCI Can't read Gamecard certificate bytes.");
|
||||
}
|
||||
// Get all partitions from the main HFS0 file
|
||||
String partition;
|
||||
for (HFS0File hfs0File: hfs0ProviderMain.getHfs0Files()){
|
||||
partition = hfs0File.getName();
|
||||
if (partition.equals("update")) {
|
||||
hfs0ProviderUpdate = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("normal")) {
|
||||
hfs0ProviderNormal = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("secure")) {
|
||||
hfs0ProviderSecure = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
continue;
|
||||
}
|
||||
if (partition.equals("logo")) {
|
||||
hfs0ProviderLogo = new HFS0Provider(hfs0ProviderMain.getRawFileDataStart() + hfs0File.getOffset(), raf, file);
|
||||
}
|
||||
}
|
||||
raf.close();
|
||||
}
|
||||
catch (IOException ioe){
|
||||
throw new Exception("XCI Failed file analyze for ["+file.getName()+"]\n "+ioe.getMessage());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Getters
|
||||
* */
|
||||
public XCIGamecardHeader getGCHeader(){ return this.xciGamecardHeader; }
|
||||
public XCIGamecardInfo getGCInfo(){ return this.xciGamecardInfo; }
|
||||
public XCIGamecardCert getGCCert(){ return this.xciGamecardCert; }
|
||||
public HFS0Provider getHfs0ProviderMain() { return this.hfs0ProviderMain; }
|
||||
public HFS0Provider getHfs0ProviderUpdate() { return this.hfs0ProviderUpdate; }
|
||||
public HFS0Provider getHfs0ProviderNormal() { return this.hfs0ProviderNormal; }
|
||||
public HFS0Provider getHfs0ProviderSecure() { return this.hfs0ProviderSecure; }
|
||||
public HFS0Provider getHfs0ProviderLogo() { return this.hfs0ProviderLogo; }
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -21,13 +21,13 @@ package konogonka.Workers;
|
|||
import javafx.concurrent.Task;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import konogonka.Tools.NCA.NCAProvider;
|
||||
import konogonka.Tools.NPDM.NPDMProvider;
|
||||
import konogonka.Tools.PFS0.PFS0Provider;
|
||||
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||
import konogonka.Tools.TIK.TIKProvider;
|
||||
import konogonka.Tools.XCI.XCIProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.NCA.NCAProvider;
|
||||
import libKonogonka.Tools.NPDM.NPDMProvider;
|
||||
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||
import libKonogonka.Tools.TIK.TIKProvider;
|
||||
import libKonogonka.Tools.XCI.XCIProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
|
|
@ -3,7 +3,7 @@ package konogonka.Workers;
|
|||
import javafx.concurrent.Task;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.NCA.NCAContent;
|
||||
import libKonogonka.Tools.NCA.NCAContent;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
|
|
|
@ -3,8 +3,8 @@ package konogonka.Workers;
|
|||
import javafx.concurrent.Task;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
||||
import konogonka.Tools.RomFs.IRomFsProvider;
|
||||
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
Copyright 2019-2022 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
|
@ -22,7 +22,7 @@ import javafx.concurrent.Task;
|
|||
import konogonka.Controllers.IRowModel;
|
||||
import konogonka.ModelControllers.EMsgType;
|
||||
import konogonka.ModelControllers.LogPrinter;
|
||||
import konogonka.Tools.ISuperProvider;
|
||||
import libKonogonka.Tools.ISuperProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.List;
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.ctraes;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.Security;
|
||||
|
||||
public class AesCtr {
|
||||
|
||||
private static boolean BCinitialized = false;
|
||||
|
||||
private void initBCProvider(){
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
BCinitialized = true;
|
||||
}
|
||||
|
||||
private Cipher cipher;
|
||||
private SecretKeySpec key;
|
||||
|
||||
public AesCtr(byte[] keyArray) throws Exception{
|
||||
if ( ! BCinitialized)
|
||||
initBCProvider();
|
||||
|
||||
key = new SecretKeySpec(keyArray, "AES");
|
||||
cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] IVarray) throws Exception{
|
||||
IvParameterSpec iv = new IvParameterSpec(IVarray);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, iv);
|
||||
return cipher.doFinal(encryptedData);
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
Copyright 2019-2020 Dmitry Isaenko
|
||||
|
||||
This file is part of Konogonka.
|
||||
|
||||
Konogonka 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.
|
||||
|
||||
Konogonka 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 Konogonka. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package konogonka.ctraes;
|
||||
|
||||
import konogonka.LoperConverter;
|
||||
/**
|
||||
* Simplify decryption of the CTR
|
||||
*/
|
||||
public class AesCtrDecryptSimple {
|
||||
|
||||
private long realMediaOffset;
|
||||
private byte[] IVarray;
|
||||
private AesCtr aesCtr;
|
||||
|
||||
public AesCtrDecryptSimple(byte[] key, byte[] sectionCTR, long realMediaOffset) throws Exception{
|
||||
this.realMediaOffset = realMediaOffset;
|
||||
aesCtr = new AesCtr(key);
|
||||
// IV for CTR == 16 bytes
|
||||
IVarray = new byte[0x10];
|
||||
// Populate first 8 bytes taken from Header's section Block CTR
|
||||
System.arraycopy(LoperConverter.flip(sectionCTR), 0x0, IVarray, 0x0, 0x8);
|
||||
}
|
||||
|
||||
public void skipNext(){
|
||||
realMediaOffset += 0x200;
|
||||
}
|
||||
|
||||
public void skipNext(long blocksNum){
|
||||
if (blocksNum > 0)
|
||||
realMediaOffset += blocksNum * 0x200;
|
||||
}
|
||||
|
||||
public byte[] dectyptNext(byte[] enctyptedBlock) throws Exception{
|
||||
updateIV(realMediaOffset);
|
||||
byte[] decryptedBlock = aesCtr.decrypt(enctyptedBlock, IVarray);
|
||||
realMediaOffset += 0x200;
|
||||
return decryptedBlock;
|
||||
}
|
||||
// Populate last 8 bytes calculated. Thanks hactool project!
|
||||
private void updateIV(long offset){
|
||||
offset >>= 4;
|
||||
for (int i = 0; i < 0x8; i++){
|
||||
IVarray[0x10-i-1] = (byte)(offset & 0xff); // Note: issues could be here
|
||||
offset >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package konogonka.exceptions;
|
||||
|
||||
public class EmptySectionException extends NullPointerException {
|
||||
public EmptySectionException(String message){
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package konogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS-AES implemented as a BlockCipher.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class XTSAESBlockCipher implements BlockCipher {
|
||||
|
||||
private final XTSCore core;
|
||||
private final int blockSize;
|
||||
private final int dataUnitSize;
|
||||
private long dataUnit;
|
||||
private int index;
|
||||
|
||||
XTSAESBlockCipher(
|
||||
XTSCore core,
|
||||
int blockSize,
|
||||
int dataUnitSize,
|
||||
long dataUnit,
|
||||
int index) {
|
||||
|
||||
this.core = Objects.requireNonNull(core, "core");
|
||||
this.blockSize = blockSize;
|
||||
this.dataUnitSize = dataUnitSize;
|
||||
this.dataUnit = dataUnit;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
XTSAESBlockCipher(XTSCore core, LongFunction<byte[]> tweakValueFunction, int dataUnitSize) {
|
||||
this(new XTSCore(new XTSTweak(tweakValueFunction)), core.getBlockSize(), dataUnitSize, 0, 0);
|
||||
}
|
||||
|
||||
public XTSAESBlockCipher(boolean isDefault, LongFunction<byte[]> tweakValueFunction, int dataUnitSize) {
|
||||
this(new XTSCore(isDefault), tweakValueFunction, dataUnitSize);
|
||||
}
|
||||
|
||||
public XTSAESBlockCipher(boolean isDefault, int dataUnitSize) {
|
||||
this(new XTSCore(isDefault), isDefault?XTSTweak::defaultTweakFunction:XTSTweak::nintTweakFunction, dataUnitSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException {
|
||||
if (params instanceof KeyParameter) {
|
||||
core.init(forEncryption, (KeyParameter) params);
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("invalid params: " + params.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAlgorithmName() {
|
||||
return core.getAlgorithmName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockSize() {
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
if (index == 0) {
|
||||
core.reset(dataUnit);
|
||||
}
|
||||
|
||||
if ((index += blockSize) == dataUnitSize) {
|
||||
dataUnit++;
|
||||
index = 0;
|
||||
}
|
||||
|
||||
return core.processBlock(in, inOff, out, outOff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
index = 0;
|
||||
dataUnit = 0;
|
||||
}
|
||||
}
|
||||
// TODO consider passing initial data unit sequence number in addition to key via CipherParameters subclass
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package konogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS-AES cipher with arbitrary (non 128-bit aligned) data unit lengths.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
public class XTSAESCipher {
|
||||
|
||||
private final XTSCore core;
|
||||
private final int blockSize;
|
||||
|
||||
XTSAESCipher(XTSCore core) {
|
||||
this.core = Objects.requireNonNull(core, "core");
|
||||
blockSize = core.getBlockSize();
|
||||
}
|
||||
|
||||
public XTSAESCipher(LongFunction<byte[]> tweakFunction) {
|
||||
this(new XTSCore(new XTSTweak(tweakFunction)));
|
||||
}
|
||||
|
||||
public XTSAESCipher(boolean isDefault) {
|
||||
this(new XTSCore(isDefault));
|
||||
}
|
||||
|
||||
String getAlgorithmName() {
|
||||
return core.getAlgorithmName();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
return blockSize;
|
||||
}
|
||||
|
||||
public XTSAESCipher init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException {
|
||||
core.init(forEncryption, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
public XTSAESCipher init(boolean forEncryption, KeyParameter key1, KeyParameter key2)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
core.init(forEncryption, key1, key2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int processDataUnit(byte[] in, int inOff, int length, byte[] out, int outOff, long sequenceNumber)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
core.reset(sequenceNumber);
|
||||
return process(in, inOff, length, out, outOff);
|
||||
}
|
||||
|
||||
int process(byte[] in, int inOff, int length, byte[] out, int outOff)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
if (length < blockSize) {
|
||||
throw new DataLengthException("data unit size too small: " + length);
|
||||
}
|
||||
|
||||
if (inOff + length > in.length) {
|
||||
throw new DataLengthException("input buffer too small for data unit size: " + length);
|
||||
}
|
||||
|
||||
if (outOff + length > out.length) {
|
||||
throw new DataLengthException("output buffer too small for data unit size: " + length);
|
||||
}
|
||||
|
||||
int to = length % blockSize == 0
|
||||
? length
|
||||
: length - (blockSize * 2);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < to; i += blockSize) {
|
||||
core.processBlock(in, inOff + i, out, outOff + i);
|
||||
}
|
||||
|
||||
if (length > i) {
|
||||
core.processPartial(in, inOff + i, out, outOff + i, length - i);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package konogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* XTS core functions.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
class XTSCore {
|
||||
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private final BlockCipher cipher;
|
||||
private final XTSTweak tweak;
|
||||
private boolean forEncryption;
|
||||
|
||||
XTSCore(BlockCipher cipher, XTSTweak tweak) {
|
||||
this.cipher = Objects.requireNonNull(cipher, "cipher");
|
||||
this.tweak = Objects.requireNonNull(tweak, "tweak");
|
||||
}
|
||||
|
||||
XTSCore(XTSTweak tweak) {
|
||||
this(new AESFastEngine(), tweak);
|
||||
}
|
||||
|
||||
XTSCore(boolean isDefault) {
|
||||
this(new XTSTweak(isDefault));
|
||||
}
|
||||
|
||||
XTSCore init(boolean forEncryption, KeyParameter key) throws IllegalArgumentException {
|
||||
byte[] k = ((KeyParameter) key).getKey();
|
||||
if (k.length != 32 && k.length != 64) {
|
||||
throw new IllegalArgumentException("bad key length: " + k.length);
|
||||
}
|
||||
|
||||
byte[] key1 = Arrays.copyOfRange(k, 0, k.length / 2);
|
||||
byte[] key2 = Arrays.copyOfRange(k, k.length / 2, k.length);
|
||||
|
||||
return init(forEncryption, new KeyParameter(key1), new KeyParameter(key2));
|
||||
}
|
||||
|
||||
XTSCore init(boolean forEncryption, KeyParameter key1, KeyParameter key2) throws IllegalArgumentException {
|
||||
cipher.init(forEncryption, key1);
|
||||
tweak.init(key2);
|
||||
this.forEncryption = forEncryption;
|
||||
return this;
|
||||
}
|
||||
|
||||
XTSCore reset(long tweakValue) throws DataLengthException, IllegalStateException {
|
||||
tweak.reset(tweakValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
String getAlgorithmName() {
|
||||
return cipher.getAlgorithmName();
|
||||
}
|
||||
|
||||
int getBlockSize() {
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
int processBlock(byte[] in, int inOff, byte[] out, int outOff) throws DataLengthException, IllegalStateException {
|
||||
byte[] tweakValue = tweak.value();
|
||||
doProcessBlock(in, inOff, out, outOff, tweakValue);
|
||||
tweak.next();
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
int doProcessBlock(byte[] in, int inOff, byte[] out, int outOff, byte[] tweakValue)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
|
||||
merge(in, inOff, out, outOff, tweakValue);
|
||||
cipher.processBlock(out, outOff, out, outOff);
|
||||
merge(out, outOff, out, outOff, tweakValue);
|
||||
return BLOCK_SIZE;
|
||||
}
|
||||
|
||||
void merge(byte[] in, int inOff, byte[] out, int outOff, byte[] tweak) {
|
||||
for (int i = 0; i < BLOCK_SIZE; i++) {
|
||||
out[i + outOff] = (byte) (in[i + inOff] ^ tweak[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int processPartial(byte[] in, int inOff, byte[] out, int outOff, int length) {
|
||||
if (length <= BLOCK_SIZE) {
|
||||
throw new DataLengthException("input buffer too small/ missing last two blocks: " + length);
|
||||
}
|
||||
|
||||
if (length >= BLOCK_SIZE * 2) {
|
||||
throw new DataLengthException("input buffer too large/ non-partial final block: " + length);
|
||||
}
|
||||
|
||||
byte[] tweakA = tweak.value();
|
||||
byte[] tweakB = tweak.next().value();
|
||||
|
||||
return forEncryption
|
||||
? XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakA, tweakB)
|
||||
: XTSCore.this.doProcessPartial(in, inOff, out, outOff, length, tweakB, tweakA);
|
||||
}
|
||||
|
||||
int doProcessPartial(byte[] in, int inOff, byte[] out, int outOff, int length, byte[] tweakA, byte[] tweakB)
|
||||
throws DataLengthException, IllegalStateException {
|
||||
// M-1 block
|
||||
doProcessBlock(in, inOff, out, outOff, tweakA);
|
||||
|
||||
// Cipher stealing
|
||||
byte[] buffer = Arrays.copyOfRange(out, outOff, outOff + BLOCK_SIZE);
|
||||
System.arraycopy(in, inOff + BLOCK_SIZE, buffer, 0, length - BLOCK_SIZE);
|
||||
|
||||
// M block
|
||||
doProcessBlock(buffer, 0, buffer, 0, tweakB);
|
||||
|
||||
// Copy over final block pair
|
||||
System.arraycopy(out, outOff, out, outOff + BLOCK_SIZE, length - BLOCK_SIZE);
|
||||
System.arraycopy(buffer, 0, out, outOff, BLOCK_SIZE);
|
||||
return length;
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2016 Ahseya.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package konogonka.xtsaes;
|
||||
|
||||
import net.jcip.annotations.NotThreadSafe;
|
||||
import org.bouncycastle.crypto.BlockCipher;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.engines.AESFastEngine;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.util.Pack;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.function.LongFunction;
|
||||
|
||||
/**
|
||||
* XTS tweak with pluggable tweak function.
|
||||
*
|
||||
* @author Ahseya
|
||||
*/
|
||||
|
||||
/**
|
||||
* Updated for special usage by Dmitry Isaenko.
|
||||
* */
|
||||
@NotThreadSafe
|
||||
class XTSTweak {
|
||||
static byte[] nintTweakFunction(long tweakValue) {
|
||||
byte[] bs = new byte[BLOCK_SIZE];
|
||||
byte[] twk = Pack.longToBigEndian(tweakValue);
|
||||
int j = BLOCK_SIZE - twk.length;
|
||||
for (byte b: twk){
|
||||
bs[j++] = b;
|
||||
}
|
||||
return bs;
|
||||
}
|
||||
|
||||
static byte[] defaultTweakFunction(long tweakValue) {
|
||||
byte[] bs = Pack.longToLittleEndian(tweakValue);
|
||||
bs = Arrays.copyOfRange(bs, 0, BLOCK_SIZE);
|
||||
return bs;
|
||||
}
|
||||
|
||||
private static final long FDBK = 0x87;
|
||||
private static final long MSB = 0x8000000000000000L;
|
||||
private static final int BLOCK_SIZE = 16;
|
||||
|
||||
private final BlockCipher cipher;
|
||||
private final LongFunction<byte[]> tweakFunction;
|
||||
private final byte[] tweak;
|
||||
|
||||
XTSTweak(BlockCipher cipher, LongFunction<byte[]> tweakFunction, byte[] tweak) {
|
||||
this.cipher = Objects.requireNonNull(cipher, "cipher");
|
||||
this.tweakFunction = Objects.requireNonNull(tweakFunction, "tweakFunction");
|
||||
this.tweak = Objects.requireNonNull(tweak, "tweak");
|
||||
|
||||
if (cipher.getBlockSize() != BLOCK_SIZE) {
|
||||
throw new IllegalArgumentException("bad block size: " + cipher.getBlockSize());
|
||||
}
|
||||
}
|
||||
|
||||
XTSTweak(BlockCipher cipher, LongFunction<byte[]> tweakFunction) {
|
||||
this(cipher, tweakFunction, new byte[cipher.getBlockSize()]);
|
||||
}
|
||||
|
||||
XTSTweak(LongFunction<byte[]> tweakFunction) {
|
||||
this(new AESFastEngine(), tweakFunction);
|
||||
}
|
||||
|
||||
XTSTweak(boolean isDefault) {
|
||||
this(isDefault
|
||||
? XTSTweak::defaultTweakFunction
|
||||
: XTSTweak::nintTweakFunction);
|
||||
}
|
||||
|
||||
XTSTweak init(KeyParameter key) throws IllegalArgumentException {
|
||||
cipher.init(true, key);
|
||||
return this;
|
||||
}
|
||||
|
||||
XTSTweak reset(long tweakValue) throws DataLengthException, IllegalStateException {
|
||||
return reset(tweakFunction.apply(tweakValue));
|
||||
}
|
||||
|
||||
XTSTweak reset(byte[] tweakBytes) throws DataLengthException, IllegalStateException {
|
||||
cipher.processBlock(tweakBytes, 0, tweak, 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
byte[] value() {
|
||||
return Arrays.copyOf(tweak, tweak.length);
|
||||
}
|
||||
|
||||
XTSTweak next() {
|
||||
long lo = Pack.littleEndianToLong(tweak, 0);
|
||||
long hi = Pack.littleEndianToLong(tweak, 8);
|
||||
|
||||
long fdbk = (hi & MSB) == 0
|
||||
? 0L
|
||||
: FDBK;
|
||||
|
||||
hi = (hi << 1) | (lo >>> 63);
|
||||
lo = (lo << 1) ^ fdbk;
|
||||
|
||||
Pack.longToLittleEndian(lo, tweak, 0);
|
||||
Pack.longToLittleEndian(hi, tweak, 8);
|
||||
return this;
|
||||
}
|
||||
}
|
1
src/main/resources-filtered/app.properties
Normal file
1
src/main/resources-filtered/app.properties
Normal file
|
@ -0,0 +1 @@
|
|||
_version=v${project.version}
|
|
@ -4,7 +4,7 @@
|
|||
<?import javafx.scene.layout.AnchorPane?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019-2020 Dmitry Isaenko
|
||||
~ Copyright 2019-2022 Dmitry Isaenko
|
||||
~
|
||||
~ This file is part of Konogonka.
|
||||
~
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019-2020 Dmitry Isaenko
|
||||
~ Copyright 2019-2022 Dmitry Isaenko
|
||||
~
|
||||
~ This file is part of Konogonka.
|
||||
~
|
||||
|
|
Loading…
Reference in a new issue