Remove all back end things to separate library. Update copyrights.
continuous-integration/drone/push Build is passing Details

master
Dmitry Isaenko 2022-08-10 20:20:25 +03:00
parent 6623a553d0
commit 27b7ba5e7f
87 changed files with 138 additions and 5307 deletions

View File

@ -8,6 +8,8 @@ Nightly builds could be found somewhere on [redrise.ru](https://redrise.ru)
Deep WIP multi-tool to work with NS-specific files / filesystem images.
Front end to libKonogonka
### License
[GNU General Public License v3+](https://github.com/developersu/konogonka/blob/master/LICENSE)
@ -19,15 +21,9 @@ Deep WIP multi-tool to work with NS-specific files / filesystem images.
<img src="screenshots/7.png" alt="drawing" width="250"/>
### Used libraries & resources
* [Bouncy Castle](https://www.bouncycastle.org/) for Java.
* [Java-XTS-AES](https://github.com/horrorho/Java-XTS-AES) by horrorho with minimal changes.
* [OpenJFX](https://wiki.openjdk.java.net/display/OpenJFX/Main)
* Few icons taken from: [materialdesignicons.com](http://materialdesignicons.com/)
#### Thanks
* Switch brew wiki
* Original ScriesM software
* roothorick, [shchmue](https://github.com/shchmue/), He, other Team AtlasNX discord members for their advices, notes and examples!
* See libKonogonka project for details
### System requirements
@ -42,9 +38,14 @@ JRE/JDK 8u60 or higher.
1. Install JDK
2. Install Maven
3. $ git clone https://github.com/developersu/konogonka.git
4. $ mvn -B -DskipTests clean package
5. $ java -jar target/konogonka-0.x.x-jar-with-dependencies.jar
3. Install libKonogonka to local repository:
4. $ git clone https://git.redrise.ru/desu/libKonogonka
5. $ mvn -B -DskipTests clean package
6. $ mvn install:install-file -Dfile=./target/libKonogonka-*-jar-with-dependencies.jar -DgroupId=ru.redrise -DartifactId=libKonogonka -Dversion=`grep -m 1 '<version>' pom.xml| sed -e 's/\s*.\/\?version>//g'
` -Dpackaging=jar -DgeneratePom=true;
7. $ git clone https://github.com/developersu/konogonka.git
8. $ mvn -B -DskipTests clean package
9. $ java -jar target/konogonka-0.x.x-jar-with-dependencies.jar
### Checklist

28
pom.xml
View File

@ -8,7 +8,7 @@
<name>konogonka</name>
<artifactId>konogonka</artifactId>
<version>0.0.3-SNAPSHOT</version>
<version>0.0.4-SNAPSHOT</version>
<url>https://github.com/developersu/${project.name}}/</url>
<description>
@ -137,19 +137,14 @@
<classifier>mac</classifier>
<scope>compile</scope>
</dependency>
<!-- For AES XTS using bouncycastle -->
<!-- Local entry of the libKonogonka: replace once deployed to maven central -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.54</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.jcip</groupId>
<artifactId>jcip-annotations</artifactId>
<version>1.0</version>
<groupId>ru.redrise</groupId>
<artifactId>libKonogonka</artifactId>
<version>0.1</version>
<scope>compile</scope>
</dependency>
<!-- Consider removing
<dependency>
<groupId>org.hamcrest</groupId>
@ -166,6 +161,17 @@
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}-${maven.build.timestamp}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources-filtered</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -26,7 +26,7 @@ import javafx.stage.Stage;
import konogonka.Controllers.IRowModel;
import konogonka.Controllers.ITabController;
import konogonka.Controllers.XML.XMLController;
import konogonka.Tools.ISuperProvider;
import libKonogonka.Tools.ISuperProvider;
import java.io.IOException;
import java.util.Locale;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -19,7 +19,7 @@
package konogonka.Controllers;
import javafx.fxml.Initializable;
import konogonka.Tools.ISuperProvider;
import libKonogonka.Tools.ISuperProvider;
import java.io.File;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -36,7 +36,7 @@ import konogonka.Controllers.XCI.XCIController;
import konogonka.Controllers.XML.XMLController;
import konogonka.MediatorControl;
import konogonka.Settings.SettingsWindow;
import konogonka.Tools.ISuperProvider;
import libKonogonka.Tools.ISuperProvider;
import java.io.*;
import java.net.URL;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -24,9 +24,9 @@ import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.AppPreferences;
import konogonka.Controllers.ITabController;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.NCA.NCAContent;
import konogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.NCA.NCAContent;
import libKonogonka.Tools.NCA.NCAProvider;
import konogonka.Workers.Analyzer;
import java.io.File;
@ -34,7 +34,7 @@ import java.net.URL;
import java.util.HashMap;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class NCAController implements ITabController {

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -28,9 +28,9 @@ import javafx.scene.layout.VBox;
import konogonka.AppPreferences;
import konogonka.Controllers.NSP.NSPController;
import konogonka.Controllers.RFS.RomFsController;
import konogonka.LoperConverter;
import libKonogonka.LoperConverter;
import konogonka.MediatorControl;
import konogonka.Tools.NCA.NCAContent;
import libKonogonka.Tools.NCA.NCAContent;
import konogonka.Workers.DumbNCA3ContentExtractor;
import java.io.File;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -22,9 +22,9 @@ import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import konogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
import libKonogonka.Tools.NCA.NCASectionTableBlock.NCASectionBlock;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class NCASectionHeaderBlockController {
@FXML

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -20,9 +20,9 @@ package konogonka.Controllers.NCA;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import konogonka.Tools.NCA.NCAHeaderTableEntry;
import libKonogonka.Tools.NCA.NCAHeaderTableEntry;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class NCATableController {
@FXML

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.Tools.NPDM.ACID.FSAccessControlProvider;
import libKonogonka.Tools.NPDM.ACID.FSAccessControlProvider;
import java.net.URL;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static konogonka.LoperConverter.longToOctString;
import static libKonogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.longToOctString;
public class FSAccessControlController implements Initializable {
@FXML

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -22,13 +22,13 @@ import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
import libKonogonka.Tools.NPDM.ACI0.FSAccessHeaderProvider;
import java.net.URL;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static konogonka.LoperConverter.longToOctString;
import static libKonogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.longToOctString;
public class FSAccessHeaderController implements Initializable {
@FXML

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -24,7 +24,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import konogonka.Tools.NPDM.KernelAccessControlProvider;
import libKonogonka.Tools.NPDM.KernelAccessControlProvider;
import java.util.Map;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -23,17 +23,17 @@ import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.Controllers.ITabController;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.NPDM.ACI0.ACI0Provider;
import konogonka.Tools.NPDM.ACID.ACIDProvider;
import konogonka.Tools.NPDM.NPDMProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.NPDM.ACI0.ACI0Provider;
import libKonogonka.Tools.NPDM.ACID.ACIDProvider;
import libKonogonka.Tools.NPDM.NPDMProvider;
import konogonka.Workers.Analyzer;
import java.io.File;
import java.net.URL;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class NPDMController implements ITabController {

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -26,9 +26,9 @@ import konogonka.AppPreferences;
import konogonka.Controllers.IRowModel;
import konogonka.Controllers.ITabController;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.PFS0.IPFS0Provider;
import konogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import konogonka.Workers.Analyzer;
import konogonka.Workers.Extractor;
@ -37,7 +37,7 @@ import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class NSPController implements ITabController {

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -34,8 +34,8 @@ import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import konogonka.Controllers.IRowModel;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.PFS0.IPFS0Provider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.PFS0.IPFS0Provider;
import java.net.URL;
import java.util.ArrayList;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -34,7 +34,7 @@ import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import konogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import java.net.URL;
import java.util.*;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -19,7 +19,7 @@
package konogonka.Controllers.RFS;
import konogonka.Controllers.IRowModel;
import konogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.FileSystemEntry;
public class RFSModelEntry implements IRowModel {
private FileSystemEntry fileSystemEntry;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -25,11 +25,11 @@ import javafx.scene.layout.Region;
import konogonka.AppPreferences;
import konogonka.Controllers.ITabController;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.RomFs.FileSystemEntry;
import konogonka.Tools.RomFs.IRomFsProvider;
import konogonka.Tools.RomFs.Level6Header;
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.IRomFsProvider;
import libKonogonka.Tools.RomFs.Level6Header;
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
import konogonka.Workers.Analyzer;
import konogonka.Workers.DumbRomFsExtractor;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -25,15 +25,15 @@ import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.AppPreferences;
import konogonka.Controllers.ITabController;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.TIK.TIKProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.TIK.TIKProvider;
import konogonka.Workers.Analyzer;
import java.io.File;
import java.net.URL;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class TIKController implements ITabController {
@FXML

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -26,8 +26,8 @@ import javafx.scene.control.TitledPane;
import konogonka.AppPreferences;
import konogonka.Controllers.IRowModel;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.XCI.HFS0Provider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.XCI.HFS0Provider;
import konogonka.Workers.Extractor;
import java.io.File;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -38,8 +38,8 @@ import javafx.scene.input.MouseEvent;
import javafx.util.Callback;
import konogonka.Controllers.IRowModel;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.XCI.HFS0Provider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.XCI.HFS0Provider;
import java.net.URL;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -24,15 +24,15 @@ import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import konogonka.AppPreferences;
import konogonka.Controllers.ITabController;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.XCI.XCIProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.XCI.XCIProvider;
import konogonka.Workers.Analyzer;
import java.io.File;
import java.net.URL;
import java.util.ResourceBundle;
import static konogonka.LoperConverter.byteArrToHexString;
import static libKonogonka.LoperConverter.byteArrToHexString;
public class XCIController implements ITabController {

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -22,7 +22,7 @@ import javafx.fxml.FXML;
import javafx.scene.control.TextArea;
import konogonka.Controllers.ITabController;
import konogonka.MediatorControl;
import konogonka.Tools.ISuperProvider;
import libKonogonka.Tools.ISuperProvider;
import java.io.BufferedInputStream;
import java.io.File;

View 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;
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -27,10 +27,10 @@ import javafx.stage.Stage;
import konogonka.Controllers.MainController;
import java.util.Locale;
import java.util.Objects;
import java.util.ResourceBundle;
public class MainFx extends Application {
public static final String appVersion = "v0.0.3";
@Override
public void start(Stage primaryStage) throws Exception{
@ -40,17 +40,20 @@ public class MainFx extends Application {
Locale userLocale = new Locale(Locale.getDefault().getISO3Language()); // NOTE: user locale based on ISO3 Language codes
ResourceBundle rb = ResourceBundle.getBundle("locale", userLocale);
ResourceBundle rbFiltered = ResourceBundle.getBundle("app", userLocale);
loader.setResources(rb);
Parent root = loader.load();
primaryStage.getIcons().addAll(
new Image(getClass().getResourceAsStream("/res/app_icon32x32.png")),
new Image(getClass().getResourceAsStream("/res/app_icon48x48.png")),
new Image(getClass().getResourceAsStream("/res/app_icon64x64.png")),
new Image(getClass().getResourceAsStream("/res/app_icon128x128.png"))
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon32x32.png"))),
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon48x48.png"))),
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon64x64.png"))),
new Image(Objects.requireNonNull(getClass().getResourceAsStream("/res/app_icon128x128.png")))
);
primaryStage.setTitle("konogonka "+appVersion);
primaryStage.setTitle("konogonka "+rbFiltered.getString("_version"));
Scene mainScene = new Scene(root, 1200, 800);
mainScene.getStylesheets().add("/res/app_light.css");

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -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);
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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();
}
}

View File

@ -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; }
}

View File

@ -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;
}
}
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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" +
"-------------------------------------------------------------"
);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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; }
}

View File

@ -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;
}
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -21,13 +21,13 @@ package konogonka.Workers;
import javafx.concurrent.Task;
import konogonka.ModelControllers.EMsgType;
import konogonka.ModelControllers.LogPrinter;
import konogonka.Tools.ISuperProvider;
import konogonka.Tools.NCA.NCAProvider;
import konogonka.Tools.NPDM.NPDMProvider;
import konogonka.Tools.PFS0.PFS0Provider;
import konogonka.Tools.RomFs.RomFsDecryptedProvider;
import konogonka.Tools.TIK.TIKProvider;
import konogonka.Tools.XCI.XCIProvider;
import libKonogonka.Tools.ISuperProvider;
import libKonogonka.Tools.NCA.NCAProvider;
import libKonogonka.Tools.NPDM.NPDMProvider;
import libKonogonka.Tools.PFS0.PFS0Provider;
import libKonogonka.Tools.RomFs.RomFsDecryptedProvider;
import libKonogonka.Tools.TIK.TIKProvider;
import libKonogonka.Tools.XCI.XCIProvider;
import java.io.File;
import java.util.HashMap;

View File

@ -3,7 +3,7 @@ package konogonka.Workers;
import javafx.concurrent.Task;
import konogonka.ModelControllers.EMsgType;
import konogonka.ModelControllers.LogPrinter;
import konogonka.Tools.NCA.NCAContent;
import libKonogonka.Tools.NCA.NCAContent;
import java.io.BufferedOutputStream;
import java.io.File;

View File

@ -3,8 +3,8 @@ package konogonka.Workers;
import javafx.concurrent.Task;
import konogonka.ModelControllers.EMsgType;
import konogonka.ModelControllers.LogPrinter;
import konogonka.Tools.RomFs.FileSystemEntry;
import konogonka.Tools.RomFs.IRomFsProvider;
import libKonogonka.Tools.RomFs.FileSystemEntry;
import libKonogonka.Tools.RomFs.IRomFsProvider;
import java.io.BufferedOutputStream;
import java.io.File;

View File

@ -1,5 +1,5 @@
/*
Copyright 2019-2020 Dmitry Isaenko
Copyright 2019-2022 Dmitry Isaenko
This file is part of Konogonka.
@ -22,7 +22,7 @@ import javafx.concurrent.Task;
import konogonka.Controllers.IRowModel;
import konogonka.ModelControllers.EMsgType;
import konogonka.ModelControllers.LogPrinter;
import konogonka.Tools.ISuperProvider;
import libKonogonka.Tools.ISuperProvider;
import java.io.*;
import java.util.List;

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -1,7 +0,0 @@
package konogonka.exceptions;
public class EmptySectionException extends NullPointerException {
public EmptySectionException(String message){
super(message);
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
_version=v${project.version}

View File

@ -4,7 +4,7 @@
<?import javafx.scene.layout.AnchorPane?>
<!--
~ Copyright 2019-2020 Dmitry Isaenko
~ Copyright 2019-2022 Dmitry Isaenko
~
~ This file is part of Konogonka.
~

View File

@ -6,7 +6,7 @@
<?import javafx.scene.layout.VBox?>
<!--
~ Copyright 2019-2020 Dmitry Isaenko
~ Copyright 2019-2022 Dmitry Isaenko
~
~ This file is part of Konogonka.
~