libKonogonka/src/main/java/libKonogonka/Tools/XCI/XCIGamecardInfo.java

120 lines
4.9 KiB
Java

/*
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 libKonogonka.Tools.XCI;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;
import static libKonogonka.LoperConverter.getLEint;
import static libKonogonka.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; }
}