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.
|
Deep WIP multi-tool to work with NS-specific files / filesystem images.
|
||||||
|
|
||||||
|
Front end to libKonogonka
|
||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
[GNU General Public License v3+](https://github.com/developersu/konogonka/blob/master/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"/>
|
<img src="screenshots/7.png" alt="drawing" width="250"/>
|
||||||
|
|
||||||
### Used libraries & resources
|
### 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)
|
* [OpenJFX](https://wiki.openjdk.java.net/display/OpenJFX/Main)
|
||||||
* Few icons taken from: [materialdesignicons.com](http://materialdesignicons.com/)
|
* Few icons taken from: [materialdesignicons.com](http://materialdesignicons.com/)
|
||||||
|
* See libKonogonka project for details
|
||||||
#### 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!
|
|
||||||
|
|
||||||
### System requirements
|
### System requirements
|
||||||
|
|
||||||
|
@ -42,9 +38,14 @@ JRE/JDK 8u60 or higher.
|
||||||
|
|
||||||
1. Install JDK
|
1. Install JDK
|
||||||
2. Install Maven
|
2. Install Maven
|
||||||
3. $ git clone https://github.com/developersu/konogonka.git
|
3. Install libKonogonka to local repository:
|
||||||
4. $ mvn -B -DskipTests clean package
|
4. $ git clone https://git.redrise.ru/desu/libKonogonka
|
||||||
5. $ java -jar target/konogonka-0.x.x-jar-with-dependencies.jar
|
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
|
### Checklist
|
||||||
|
|
||||||
|
|
28
pom.xml
28
pom.xml
|
@ -8,7 +8,7 @@
|
||||||
<name>konogonka</name>
|
<name>konogonka</name>
|
||||||
|
|
||||||
<artifactId>konogonka</artifactId>
|
<artifactId>konogonka</artifactId>
|
||||||
<version>0.0.3-SNAPSHOT</version>
|
<version>0.0.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<url>https://github.com/developersu/${project.name}}/</url>
|
<url>https://github.com/developersu/${project.name}}/</url>
|
||||||
<description>
|
<description>
|
||||||
|
@ -137,19 +137,14 @@
|
||||||
<classifier>mac</classifier>
|
<classifier>mac</classifier>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- For AES XTS using bouncycastle -->
|
<!-- Local entry of the libKonogonka: replace once deployed to maven central -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>ru.redrise</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>libKonogonka</artifactId>
|
||||||
<version>1.54</version>
|
<version>0.1</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>net.jcip</groupId>
|
|
||||||
<artifactId>jcip-annotations</artifactId>
|
|
||||||
<version>1.0</version>
|
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Consider removing
|
<!-- Consider removing
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
|
@ -166,6 +161,17 @@
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
|
<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>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<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.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import javafx.stage.Stage;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Controllers.XML.XMLController;
|
import konogonka.Controllers.XML.XMLController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
package konogonka.Controllers;
|
package konogonka.Controllers;
|
||||||
|
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ import konogonka.Controllers.XCI.XCIController;
|
||||||
import konogonka.Controllers.XML.XMLController;
|
import konogonka.Controllers.XML.XMLController;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Settings.SettingsWindow;
|
import konogonka.Settings.SettingsWindow;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -24,9 +24,9 @@ import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.NCA.NCAContent;
|
import libKonogonka.Tools.NCA.NCAContent;
|
||||||
import konogonka.Tools.NCA.NCAProvider;
|
import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -34,7 +34,7 @@ import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
|
|
||||||
public class NCAController implements ITabController {
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@ import javafx.scene.layout.VBox;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.NSP.NSPController;
|
import konogonka.Controllers.NSP.NSPController;
|
||||||
import konogonka.Controllers.RFS.RomFsController;
|
import konogonka.Controllers.RFS.RomFsController;
|
||||||
import konogonka.LoperConverter;
|
import libKonogonka.LoperConverter;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.NCA.NCAContent;
|
import libKonogonka.Tools.NCA.NCAContent;
|
||||||
import konogonka.Workers.DumbNCA3ContentExtractor;
|
import konogonka.Workers.DumbNCA3ContentExtractor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -22,9 +22,9 @@ import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.control.TitledPane;
|
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 {
|
public class NCASectionHeaderBlockController {
|
||||||
@FXML
|
@FXML
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -20,9 +20,9 @@ package konogonka.Controllers.NCA;
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
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 {
|
public class NCATableController {
|
||||||
@FXML
|
@FXML
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.Tools.NPDM.ACID.FSAccessControlProvider;
|
import libKonogonka.Tools.NPDM.ACID.FSAccessControlProvider;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
import static konogonka.LoperConverter.longToOctString;
|
import static libKonogonka.LoperConverter.longToOctString;
|
||||||
|
|
||||||
public class FSAccessControlController implements Initializable {
|
public class FSAccessControlController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
|
import libKonogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
import static konogonka.LoperConverter.longToOctString;
|
import static libKonogonka.LoperConverter.longToOctString;
|
||||||
|
|
||||||
public class FSAccessHeaderController implements Initializable {
|
public class FSAccessHeaderController implements Initializable {
|
||||||
@FXML
|
@FXML
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.Separator;
|
import javafx.scene.control.Separator;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import konogonka.Tools.NPDM.KernelAccessControlProvider;
|
import libKonogonka.Tools.NPDM.KernelAccessControlProvider;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -23,17 +23,17 @@ import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.NPDM.ACI0.ACI0Provider;
|
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
|
||||||
import konogonka.Tools.NPDM.ACID.ACIDProvider;
|
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
|
||||||
import konogonka.Tools.NPDM.NPDMProvider;
|
import libKonogonka.Tools.NPDM.NPDMProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
|
|
||||||
public class NPDMController implements ITabController {
|
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.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||||
import konogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
import konogonka.Workers.Extractor;
|
import konogonka.Workers.Extractor;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
|
|
||||||
public class NSPController implements ITabController {
|
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.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ import javafx.scene.input.KeyEvent;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.PFS0.IPFS0Provider;
|
import libKonogonka.Tools.PFS0.IPFS0Provider;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ import javafx.scene.input.KeyCode;
|
||||||
import javafx.scene.input.MouseButton;
|
import javafx.scene.input.MouseButton;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
package konogonka.Controllers.RFS;
|
package konogonka.Controllers.RFS;
|
||||||
|
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
|
|
||||||
public class RFSModelEntry implements IRowModel {
|
public class RFSModelEntry implements IRowModel {
|
||||||
private FileSystemEntry fileSystemEntry;
|
private FileSystemEntry fileSystemEntry;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ import javafx.scene.layout.Region;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
import konogonka.Tools.RomFs.IRomFsProvider;
|
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||||
import konogonka.Tools.RomFs.Level6Header;
|
import libKonogonka.Tools.RomFs.Level6Header;
|
||||||
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
import konogonka.Workers.DumbRomFsExtractor;
|
import konogonka.Workers.DumbRomFsExtractor;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -25,15 +25,15 @@ import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.TIK.TIKProvider;
|
import libKonogonka.Tools.TIK.TIKProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
|
|
||||||
public class TIKController implements ITabController {
|
public class TIKController implements ITabController {
|
||||||
@FXML
|
@FXML
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -26,8 +26,8 @@ import javafx.scene.control.TitledPane;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.XCI.HFS0Provider;
|
import libKonogonka.Tools.XCI.HFS0Provider;
|
||||||
import konogonka.Workers.Extractor;
|
import konogonka.Workers.Extractor;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -38,8 +38,8 @@ import javafx.scene.input.MouseEvent;
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.XCI.HFS0Provider;
|
import libKonogonka.Tools.XCI.HFS0Provider;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -24,15 +24,15 @@ import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextField;
|
import javafx.scene.control.TextField;
|
||||||
import konogonka.AppPreferences;
|
import konogonka.AppPreferences;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.XCI.XCIProvider;
|
import libKonogonka.Tools.XCI.XCIProvider;
|
||||||
import konogonka.Workers.Analyzer;
|
import konogonka.Workers.Analyzer;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import static konogonka.LoperConverter.byteArrToHexString;
|
import static libKonogonka.LoperConverter.byteArrToHexString;
|
||||||
|
|
||||||
public class XCIController implements ITabController {
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.TextArea;
|
import javafx.scene.control.TextArea;
|
||||||
import konogonka.Controllers.ITabController;
|
import konogonka.Controllers.ITabController;
|
||||||
import konogonka.MediatorControl;
|
import konogonka.MediatorControl;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
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.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -27,10 +27,10 @@ import javafx.stage.Stage;
|
||||||
import konogonka.Controllers.MainController;
|
import konogonka.Controllers.MainController;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
public class MainFx extends Application {
|
public class MainFx extends Application {
|
||||||
public static final String appVersion = "v0.0.3";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception{
|
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
|
Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); // NOTE: user locale based on ISO3 Language codes
|
||||||
ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale);
|
ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale);
|
||||||
|
|
||||||
|
ResourceBundle rbFiltered = ResourceBundle.getBundle("app", userLocale);
|
||||||
|
|
||||||
|
|
||||||
loader.setResources(rb);
|
loader.setResources(rb);
|
||||||
|
|
||||||
Parent root = loader.load();
|
Parent root = loader.load();
|
||||||
|
|
||||||
primaryStage.getIcons().addAll(
|
primaryStage.getIcons().addAll(
|
||||||
new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")),
|
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon32x32.png"))),
|
||||||
new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")),
|
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon48x48.png"))),
|
||||||
new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")),
|
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon64x64.png"))),
|
||||||
new Image(getClass().getResourceAsStream("/res/app_icon128x128.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);
|
Scene mainScene = new Scene(root, 1200, 800);
|
||||||
mainScene.getStylesheets().add("/res/app_light.css");
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@ package konogonka.Workers;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import konogonka.ModelControllers.EMsgType;
|
import konogonka.ModelControllers.EMsgType;
|
||||||
import konogonka.ModelControllers.LogPrinter;
|
import konogonka.ModelControllers.LogPrinter;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
import konogonka.Tools.NCA.NCAProvider;
|
import libKonogonka.Tools.NCA.NCAProvider;
|
||||||
import konogonka.Tools.NPDM.NPDMProvider;
|
import libKonogonka.Tools.NPDM.NPDMProvider;
|
||||||
import konogonka.Tools.PFS0.PFS0Provider;
|
import libKonogonka.Tools.PFS0.PFS0Provider;
|
||||||
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
|
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
|
||||||
import konogonka.Tools.TIK.TIKProvider;
|
import libKonogonka.Tools.TIK.TIKProvider;
|
||||||
import konogonka.Tools.XCI.XCIProvider;
|
import libKonogonka.Tools.XCI.XCIProvider;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
|
@ -3,7 +3,7 @@ package konogonka.Workers;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import konogonka.ModelControllers.EMsgType;
|
import konogonka.ModelControllers.EMsgType;
|
||||||
import konogonka.ModelControllers.LogPrinter;
|
import konogonka.ModelControllers.LogPrinter;
|
||||||
import konogonka.Tools.NCA.NCAContent;
|
import libKonogonka.Tools.NCA.NCAContent;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -3,8 +3,8 @@ package konogonka.Workers;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import konogonka.ModelControllers.EMsgType;
|
import konogonka.ModelControllers.EMsgType;
|
||||||
import konogonka.ModelControllers.LogPrinter;
|
import konogonka.ModelControllers.LogPrinter;
|
||||||
import konogonka.Tools.RomFs.FileSystemEntry;
|
import libKonogonka.Tools.RomFs.FileSystemEntry;
|
||||||
import konogonka.Tools.RomFs.IRomFsProvider;
|
import libKonogonka.Tools.RomFs.IRomFsProvider;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Dmitry Isaenko
|
Copyright 2019-2022 Dmitry Isaenko
|
||||||
|
|
||||||
This file is part of Konogonka.
|
This file is part of Konogonka.
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import javafx.concurrent.Task;
|
||||||
import konogonka.Controllers.IRowModel;
|
import konogonka.Controllers.IRowModel;
|
||||||
import konogonka.ModelControllers.EMsgType;
|
import konogonka.ModelControllers.EMsgType;
|
||||||
import konogonka.ModelControllers.LogPrinter;
|
import konogonka.ModelControllers.LogPrinter;
|
||||||
import konogonka.Tools.ISuperProvider;
|
import libKonogonka.Tools.ISuperProvider;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.List;
|
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?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
~ Copyright 2019-2020 Dmitry Isaenko
|
~ Copyright 2019-2022 Dmitry Isaenko
|
||||||
~
|
~
|
||||||
~ This file is part of Konogonka.
|
~ This file is part of Konogonka.
|
||||||
~
|
~
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
~ Copyright 2019-2020 Dmitry Isaenko
|
~ Copyright 2019-2022 Dmitry Isaenko
|
||||||
~
|
~
|
||||||
~ This file is part of Konogonka.
|
~ This file is part of Konogonka.
|
||||||
~
|
~
|
||||||
|
|
Loading…
Reference in a new issue