Fully fixed decoding, added Test.java and Shell build script

This commit is contained in:
Yuri Torlopov 2023-02-01 19:17:36 +03:00
parent c857d52f96
commit 8994bcf209
8 changed files with 305 additions and 88 deletions

9
build.sh Normal file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
rm -rf out
mkdir out
javac -encoding utf8 -classpath src/me/theentropyshard/jdarkroom -d out -sourcepath . src/me/theentropyshard/jdarkroom/*.java
cd out
jar cfe0 JDarkroom.jar me.theentropyshard.jdarkroom.Main me/theentropyshard/jdarkroom/*.class -C ../resources .
mv ./JDarkroom.jar ../
cd ../

View file

@ -1,4 +1,4 @@
background.png taken from the Internet, resized and darkened
mdisk.ico taken from https://github.com/9214/daruma, I sent an email asking for
this icon, at the moment of writing this, author still not answered, but the license is
WTFPL, so I think it is OK to take this icon.
this icon. Author permitted me to use it.

101
resources/test_data.txt Normal file
View file

@ -0,0 +1,101 @@
8a1\wyRVK1L8Z2kZ
o?d\w3R1e1KQVSiP
\tH5Wc8m+Vtk8QWR
8AvLKYqyb5tQJi7h
uCc1FQWyGWdR?tcZ
6!VPg?UQ?RZyhh/a
MquDCP+Tbz+sJHvv
Eg\Fc?AWH4z9\Z77
\vSJr\hCU7z8R1h!
/1\Hs!UQhcSkx4LN
u6ajbPxQY3SpgW9N
eov8WRNHZ1ckhQWe
uQBGJs4HF4typCNQ
mLT+6ZXy4di?Rkk9
o\EFd\718zx9tmES
MSG5P&uXAdcsXRnA
uQBGJs4HV4NyDTNQ
E+\qjfLyLb\qxAvB
mRV4UmUWV?PWZaUJ
6d&ZM6b&/3T/rQMA
/S9UcnkyMWs1Hw8i
gbN9E3pEsz/51aEF
uCc1FQWyWW9B8sdZ
mLT+6ZXyYdi?Rsk9
oKMEd\71MzR9PvES
MquDKP+Tbz+s+HvP
2SBkhhaEs8oZBRHk
8a1\wyRVK1L8Z?kY
c8HYkktToXdusKSL
6d&ZM6b&13T/pcMA
MquDKP+TLzosJGrv
8AvLKYqyb5tQLq7h
EXB\BhaWF6+CAHN/
Wm\gH3VwXcA4epd8
mRV4UmUSE6vW5XUJ
kEdUmH/i8QuVx18b
E+\qjfL2Pb\qwKvA
2SJkhhaE84+ZhQnk
Um\gH3VwHcg414dc
WZu&E8HwdWxkawqs
uCc1FQWymWdR?&dZ
?6Stp\&TQfHqStzE
u6ajbPxU+zypAf9N
8a1\wyRFa1r849k5
/1\Hs!UUhcSkwwLt
mRV4UmUWU?PWYW/J
2S9UUnkyMWs1G/8C
mLT+6ZXy4di?Rs&9
aQ/Pp3WxEf+Kj9wb
MSG4f&uXAdcsVZmA
MtH5ec8mYVNkWBWR
6d&ZM6bh&zz/KdsA
\vSJr\hWA7T858hf
o?d\w3Rxf1KQXeiP
6!VPo?UQuR5yJ/w6
\tH5ec8m+VtkXESx
oKMFN\71czxtNuES
8AvLaYqyL5NAJr6h
MquDKP+Tbz+sLDvP
2SBkhhaEs8oZAVHk
/1\Hs!UA1cykRzLM
8ot1?L!V+ZSTkMAy
6dtZM6bh1zT/KYMA
MquDKP+TLzos+Crv
EXBPBhaWV6oSAGN/
WZu&E8HwtWR/Yxrs
Wm\gH3VwXcA4ftd8
mRV4UmUSE6vW4TUJ
\vSJr\hGQ7z8Y9h!
E+\qjfL2Lb\qxKvB
2SJkhhaE84+ZgUnk
Um\gH3VwHcg4esZc
uCc/FQWy2W9B8kdZ
uCc/FQWymWdB?&dZ
?6Stp\&TQfHqTpzE
o?d\w3R1PxqQ3fCP
eov8WRNDJx8kAdWe
/1\Hs!UU&cSkxwLt
mRV4UmUWU?PWbS/J
?6Stp\&TAfnq74zk
aQ/\53WxUfoah8wb
aQ/\p3WxEf+aj9wb
MSG5f&uXAdc8XZmA
ibN9M3pE8zU5dLE&
EomojKLQr7vCdbp/
\vSJr\hSE7T84UhW
o?d\w3Rxf1KQWSiG
eon8WRNDZxckAQ2X
gbN9E3pEsz/5cuQF
qKMEd\71c3x9PmES
?AvKaYqyL9NQJj6h
sQBGJs4HV4NyBzdQ
EgGFc?ASG4z9MZ7y
/1\Hs!UExcykQbLF
8ot1?L!VMZST&kA6
EouojKLQ77PCdWJ9
MquDCP+TLzosLm7v
GXB\BhaWV?oCA\N/
UZukU8HwtSRka5rs
/S9UcnkycWM1&FoC
fCe\w9!iBXJ1ijn&

View file

@ -23,7 +23,7 @@ public enum Decoder {
public static String decodeInternetCode(String code) {
byte[] bytes = new byte[16];
for(int i = 0; i < 16; i++) {
bytes[i] = DecodingData.CODE_INDEX_TABLE.get(Character.toString(code.charAt(i)));
bytes[i] = DecodingData.FROM_INTERNET_CODE.get(code.charAt(i));
}
for(int i = 6; i > 0; i--) {
@ -35,12 +35,10 @@ public enum Decoder {
Utils.swapBits(byte1, byte2, bi1, bi2, bytes);
}
String bin = Integer.toBinaryString(bytes[bytes.length - 1]);
int index = Integer.parseInt(bin.substring(0, 3), 2);
int index = (bytes[15] >> 3) & 0b111;
byte firstRoundKey = DecodingData.KEY[index];
byte secondRoundKey = DecodingData.KEY[DecodingData.KEY.length - 1 - index];
for(int i = 30; i > 0; i--) {
int m = (i * firstRoundKey + 45) % 90;
int n = (i * secondRoundKey + 45) % 90;
@ -55,8 +53,7 @@ public enum Decoder {
Utils.swapBits(byte1, byte2, bi1, bi2, bytes);
}
byte thirdRoundKey = DecodingData.KEY[bytes[bytes.length - 1] & 0b111];
byte thirdRoundKey = DecodingData.KEY[bytes[15] & 0b111];
for(int i = 40; i > 0; i--) {
int m = i * thirdRoundKey % 90;
int n = m % 6;
@ -78,20 +75,23 @@ public enum Decoder {
intCode |= (bytes[10] & (0x0000000C >> 2)) << 2;
intCode |= (bytes[11] & (0x00000003 << 4)) >> 4;
int l1 = Integer.parseInt(Integer.toHexString((intCode >> 24) & 0xFF), 10);
int d1 = Integer.parseInt(Integer.toHexString((intCode >> 16) & 0xFF), 16);
int l2 = Integer.parseInt(Integer.toHexString((intCode >> 8) & 0xFF), 16);
int d2 = Integer.parseInt(Integer.toHexString((intCode >> 0) & 0xFF), 16);
int l1 = (intCode >> 24) & 0xFF;
int d1 = (intCode >> 16) & 0xFF;
int l2 = (intCode >> 8) & 0xFF;
int d2 = (intCode >> 0) & 0xFF;
String[] letters1 = DecodingData.SAVE_INDEX_TABLE.get(l1 + 1).split("\\s");
String[] letters2 = DecodingData.SAVE_INDEX_TABLE.get(l2 + 1).split("\\s");
String[] letters1 = DecodingData.BYTES_TO_CODE.get(Integer.valueOf(l1).byteValue());
String[] letters2 = DecodingData.BYTES_TO_CODE.get(Integer.valueOf(l2).byteValue());
// From https://9214.github.io/13
// "Recall that locker code is stored in letter-digit-letter-digit format – letter
// is a letter index from 0-based A-Z alphabet, and digit is actual digit."
String russianCode = letters1[1] + d1 + letters2[1] + d2;
String englishCode = letters1[0] + d1 + letters2[0] + d2;
// I could put this in PRSF (private static final), but it might be not initialized yet
String russianLabel = I18N.getString("codeLabelRu");
String englishLabel = I18N.getString("codeLabelEn");
String andText = I18N.getString("andText");
return String.format("%s: %s %s %s: %s", russianLabel, russianCode, andText, englishLabel, englishCode);
return String.format("%s: %s %s %s: %s", russianLabel, russianCode, I18N.getString("andText"), englishLabel, englishCode);
}
}

View file

@ -21,9 +21,11 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public final class DecodingData {
public enum DecodingData {
;
/**
* Offset, where Akuda locker code located
* Offset, where Akuda locker code located in save
*/
public static final long CODE_OFFSET = 0x00002D58L;
@ -80,76 +82,152 @@ public final class DecodingData {
);
/**
* Table for decoding bytes from decoded Internet Code, english and russian letters
* Ok, this is bigger
* Table for decoding Internet Code to bytes, english and russian letters
* Example code: fCe\w9!iBXJ1ijn&
*/
public static final Map<String, Byte> CODE_INDEX_TABLE = Collections.unmodifiableMap(
new HashMap<String, Byte>() {{
public static final Map<Character, Byte> FROM_INTERNET_CODE = Collections.unmodifiableMap(
new HashMap<Character, Byte>() {{
// @formatter:off
this.put("A", (byte) 0); this.put("А", (byte) 0);
this.put("B", (byte) 1); this.put("Б", (byte) 1);
this.put("C", (byte) 2); this.put("Ц", (byte) 2);
this.put("D", (byte) 3); this.put("Д", (byte) 3);
this.put("E", (byte) 4); this.put("Е", (byte) 4);
this.put("F", (byte) 5); this.put("Ф", (byte) 5);
this.put("G", (byte) 6); this.put("Г", (byte) 6);
this.put("H", (byte) 7); this.put("Ю", (byte) 7);
this.put("+", (byte) 8); // this.put("+", (byte) 8);
this.put("J", (byte) 9); this.put("Й", (byte) 9);
this.put("K", (byte) 10); this.put("К", (byte) 10);
this.put("L", (byte) 11); this.put("Л", (byte) 10);
this.put("M", (byte) 12); this.put("М", (byte) 10);
this.put("N", (byte) 13); this.put("Н", (byte) 10);
this.put("\\", (byte) 14); // this.put("\\", (byte) 10);
this.put("P", (byte) 15); this.put("П", (byte) 10);
this.put("Q", (byte) 16); this.put("Я", (byte) 10);
this.put("R", (byte) 17); this.put("Р", (byte) 10);
this.put("S", (byte) 18); this.put("С", (byte) 10);
this.put("T", (byte) 19); this.put("Т", (byte) 10);
this.put("U", (byte) 20); this.put("У", (byte) 10);
this.put("V", (byte) 21); this.put("В", (byte) 10);
this.put("W", (byte) 22); this.put("Ш", (byte) 10);
this.put("X", (byte) 23); this.put("Х", (byte) 10);
this.put("Y", (byte) 24); this.put("Ч", (byte) 10);
this.put("Z", (byte) 25); this.put("Ж", (byte) 10);
this.put("a", (byte) 26); this.put("а", (byte) 10);
this.put("b", (byte) 27); this.put("б", (byte) 10);
this.put("c", (byte) 28); this.put("ц", (byte) 10);
this.put("d", (byte) 29); this.put("д", (byte) 10);
this.put("e", (byte) 30); this.put("е", (byte) 10);
this.put("f", (byte) 31); this.put("ф", (byte) 10);
this.put("g", (byte) 32); this.put("г", (byte) 10);
this.put("h", (byte) 33); this.put("ю", (byte) 10);
this.put("i", (byte) 34); this.put("и", (byte) 10);
this.put("j", (byte) 35); this.put("й", (byte) 10);
this.put("k", (byte) 36); this.put("к", (byte) 10);
this.put("&", (byte) 37); // this.put("&", (byte) 10);
this.put("m", (byte) 38); this.put("м", (byte) 10);
this.put("n", (byte) 39); this.put("н", (byte) 10);
this.put("o", (byte) 40); this.put("о", (byte) 10);
this.put("p", (byte) 41); this.put("п", (byte) 10);
this.put("q", (byte) 42); this.put("я", (byte) 10);
this.put("r", (byte) 43); this.put("р", (byte) 10);
this.put("s", (byte) 44); this.put("с", (byte) 10);
this.put("t", (byte) 45); this.put("т", (byte) 10);
this.put("u", (byte) 46); this.put("у", (byte) 10);
this.put("v", (byte) 47); this.put("в", (byte) 10);
this.put("w", (byte) 48); this.put("ш", (byte) 10);
this.put("x", (byte) 49); this.put("х", (byte) 10);
this.put("y", (byte) 50); this.put("ч", (byte) 10);
this.put("z", (byte) 51); this.put("ж", (byte) 10);
this.put("/", (byte) 52); // this.put("/", (byte) 10);
this.put("1", (byte) 53); // this.put("K", (byte) 10);
this.put("2", (byte) 54); // this.put("K", (byte) 10);
this.put("3", (byte) 55); // this.put("K", (byte) 10);
this.put("4", (byte) 56); // this.put("K", (byte) 10);
this.put("5", (byte) 57); // this.put("K", (byte) 10);
this.put("6", (byte) 58); // this.put("K", (byte) 10);
this.put("7", (byte) 59); // this.put("K", (byte) 10);
this.put("8", (byte) 60); // this.put("K", (byte) 10);
this.put("9", (byte) 61); // this.put("K", (byte) 10);
this.put("?", (byte) 62); // this.put("K", (byte) 10);
this.put("!", (byte) 63); // this.put("K", (byte) 10);
this.put('A', (byte) 0); this.put('А', (byte) 0);
this.put('B', (byte) 1); this.put('Б', (byte) 1);
this.put('C', (byte) 2); this.put('Ц', (byte) 2);
this.put('D', (byte) 3); this.put('Д', (byte) 3);
this.put('E', (byte) 4); this.put('Е', (byte) 4);
this.put('F', (byte) 5); this.put('Ф', (byte) 5);
this.put('G', (byte) 6); this.put('Г', (byte) 6);
this.put('H', (byte) 7); this.put('Ю', (byte) 7);
this.put('+', (byte) 8);
this.put('J', (byte) 9); this.put('Й', (byte) 9);
this.put('K', (byte) 10); this.put('К', (byte) 10);
this.put('L', (byte) 11); this.put('Л', (byte) 11);
this.put('M', (byte) 12); this.put('М', (byte) 12);
this.put('N', (byte) 13); this.put('Н', (byte) 13);
this.put('\\', (byte) 14);
this.put('P', (byte) 15); this.put('П', (byte) 15);
this.put('Q', (byte) 16); this.put('Я', (byte) 16);
this.put('R', (byte) 17); this.put('Р', (byte) 17);
this.put('S', (byte) 18); this.put('С', (byte) 18);
this.put('T', (byte) 19); this.put('Т', (byte) 19);
this.put('U', (byte) 20); this.put('У', (byte) 20);
this.put('V', (byte) 21); this.put('В', (byte) 21);
this.put('W', (byte) 22); this.put('Ш', (byte) 22);
this.put('X', (byte) 23); this.put('Х', (byte) 23);
this.put('Y', (byte) 24); this.put('Ч', (byte) 24);
this.put('Z', (byte) 25); this.put('Ж', (byte) 25);
this.put('a', (byte) 26); this.put('а', (byte) 26);
this.put('b', (byte) 27); this.put('б', (byte) 27);
this.put('c', (byte) 28); this.put('ц', (byte) 28);
this.put('d', (byte) 29); this.put('д', (byte) 29);
this.put('e', (byte) 30); this.put('е', (byte) 30);
this.put('f', (byte) 31); this.put('ф', (byte) 31);
this.put('g', (byte) 32); this.put('г', (byte) 32);
this.put('h', (byte) 33); this.put('ю', (byte) 33);
this.put('i', (byte) 34); this.put('и', (byte) 34);
this.put('j', (byte) 35); this.put('й', (byte) 35);
this.put('k', (byte) 36); this.put('к', (byte) 36);
this.put('&', (byte) 37);
this.put('m', (byte) 38); this.put('м', (byte) 38);
this.put('n', (byte) 39); this.put('н', (byte) 39);
this.put('o', (byte) 40); this.put('о', (byte) 40);
this.put('p', (byte) 41); this.put('п', (byte) 41);
this.put('q', (byte) 42); this.put('я', (byte) 42);
this.put('r', (byte) 43); this.put('р', (byte) 43);
this.put('s', (byte) 44); this.put('с', (byte) 44);
this.put('t', (byte) 45); this.put('т', (byte) 45);
this.put('u', (byte) 46); this.put('у', (byte) 46);
this.put('v', (byte) 47); this.put('в', (byte) 47);
this.put('w', (byte) 48); this.put('ш', (byte) 48);
this.put('x', (byte) 49); this.put('х', (byte) 49);
this.put('y', (byte) 50); this.put('ч', (byte) 50);
this.put('z', (byte) 51); this.put('ж', (byte) 51);
this.put('/', (byte) 52);
this.put('1', (byte) 53);
this.put('2', (byte) 54);
this.put('3', (byte) 55);
this.put('4', (byte) 56);
this.put('5', (byte) 57);
this.put('6', (byte) 58);
this.put('7', (byte) 59);
this.put('8', (byte) 60);
this.put('9', (byte) 61);
this.put('?', (byte) 62);
this.put('!', (byte) 63);
// @formatter:on
}}
);
/**
* Table for transforming bytes into game code
* Format of code: letter-digit-letter-digit <br>
* Example: N5K9
*/
public static final Map<Byte, String[]> BYTES_TO_CODE = Collections.unmodifiableMap(
new HashMap<Byte, String[]>() {{
// @formatter:off
this.put((byte) 0, new String[]{"A", "А"});
this.put((byte) 1, new String[]{"B", "Б"});
this.put((byte) 2, new String[]{"C", "Ц"});
this.put((byte) 3, new String[]{"D", "Д"});
this.put((byte) 4, new String[]{"E", "Е"});
this.put((byte) 5, new String[]{"F", "Ф"});
this.put((byte) 6, new String[]{"G", "Г"});
this.put((byte) 7, new String[]{"H", "Ю"});
this.put((byte) 8, new String[]{"+", "+"});
this.put((byte) 9, new String[]{"J", "Й"});
this.put((byte) 10, new String[]{"K", "К"});
this.put((byte) 11, new String[]{"L", "Л"});
this.put((byte) 12, new String[]{"M", "М"});
this.put((byte) 13, new String[]{"N", "Н"});
this.put((byte) 14, new String[]{"\\", "\\"});
this.put((byte) 15, new String[]{"P", "П"});
this.put((byte) 16, new String[]{"Q", "Я"});
this.put((byte) 17, new String[]{"R", "Р"});
this.put((byte) 18, new String[]{"S", "С"});
this.put((byte) 19, new String[]{"T", "Т"});
this.put((byte) 20, new String[]{"U", "У"});
this.put((byte) 21, new String[]{"V", "В"});
this.put((byte) 22, new String[]{"W", "Ш"});
this.put((byte) 23, new String[]{"X", "Х"});
this.put((byte) 24, new String[]{"Y", "Ч"});
this.put((byte) 25, new String[]{"Z", "Ж"});
this.put((byte) 26, new String[]{"a", "а"});
this.put((byte) 27, new String[]{"b", "б"});
this.put((byte) 28, new String[]{"c", "ц"});
this.put((byte) 29, new String[]{"d", "д"});
this.put((byte) 30, new String[]{"e", "е"});
this.put((byte) 31, new String[]{"f", "ф"});
this.put((byte) 32, new String[]{"g", "г"});
this.put((byte) 33, new String[]{"h", "ю"});
this.put((byte) 34, new String[]{"i", "и"});
this.put((byte) 35, new String[]{"j", "й"});
this.put((byte) 36, new String[]{"k", "к"});
this.put((byte) 37, new String[]{"&", "&"});
this.put((byte) 38, new String[]{"m", "м"});
this.put((byte) 39, new String[]{"n", "н"});
this.put((byte) 40, new String[]{"o", "о"});
this.put((byte) 41, new String[]{"p", "п"});
this.put((byte) 42, new String[]{"q", "я"});
this.put((byte) 43, new String[]{"r", "р"});
this.put((byte) 44, new String[]{"s", "с"});
this.put((byte) 45, new String[]{"t", "т"});
this.put((byte) 46, new String[]{"u", "у"});
this.put((byte) 47, new String[]{"v", "в"});
this.put((byte) 48, new String[]{"w", "ш"});
this.put((byte) 49, new String[]{"x", "х"});
this.put((byte) 50, new String[]{"y", "ч"});
this.put((byte) 51, new String[]{"z", "ж"});
this.put((byte) 52, new String[]{"/", "/"});
this.put((byte) 53, new String[]{"1", "1"});
this.put((byte) 54, new String[]{"2", "2"});
this.put((byte) 55, new String[]{"3", "3"});
this.put((byte) 56, new String[]{"4", "4"});
this.put((byte) 57, new String[]{"5", "5"});
this.put((byte) 58, new String[]{"6", "6"});
this.put((byte) 59, new String[]{"7", "7"});
this.put((byte) 60, new String[]{"8", "8"});
this.put((byte) 61, new String[]{"9", "9"});
this.put((byte) 62, new String[]{"?", "?"});
this.put((byte) 63, new String[]{"!", "!"});
// @formatter:on
}}
);

View file

@ -56,9 +56,9 @@ public enum I18N {
}
public static String getString(String key) {
if(key.indexOf(".") == 2) {
/*if(key.indexOf(".") == 2) {
return I18N.TRANSLATION.get(key);
}
}*/
return I18N.TRANSLATION.get(I18N.LANGUAGE + "." + key);
}
}

View file

@ -70,4 +70,10 @@ public enum Utils {
bytes[byteInd1] |= 1 << bi1;
}
}
/*public static final class StrIntBiMap extends HashMap<String, Integer> {
public Integer getValueByKey(String key) {
}
}*/
}

23
test/Test.java Normal file
View file

@ -0,0 +1,23 @@
import me.theentropyshard.jdarkroom.Decoder;
import java.util.Objects;
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
try {
Scanner scanner = new Scanner(Objects.requireNonNull(Test.class.getResourceAsStream("/test_data.txt")));
for(int i = 1; scanner.hasNextLine(); i++) {
String code = scanner.nextLine();
if(code.length() != 16) {
System.err.println("Found illegal test code in test_data.txt: " + code + ", line=" + i + ", length=" + code.length());
continue;
}
String decoded = Decoder.decodeInternetCode(code);
System.out.println("Successfully decoded: " + code + " into " + decoded);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}