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 background.png taken from the Internet, resized and darkened
mdisk.ico taken from https://github.com/9214/daruma, I sent an email asking for 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 this icon. Author permitted me to use it.
WTFPL, so I think it is OK to take this icon.

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) { public static String decodeInternetCode(String code) {
byte[] bytes = new byte[16]; byte[] bytes = new byte[16];
for(int i = 0; i < 16; i++) { 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--) { for(int i = 6; i > 0; i--) {
@ -35,12 +35,10 @@ public enum Decoder {
Utils.swapBits(byte1, byte2, bi1, bi2, bytes); Utils.swapBits(byte1, byte2, bi1, bi2, bytes);
} }
String bin = Integer.toBinaryString(bytes[bytes.length - 1]); int index = (bytes[15] >> 3) & 0b111;
int index = Integer.parseInt(bin.substring(0, 3), 2);
byte firstRoundKey = DecodingData.KEY[index]; byte firstRoundKey = DecodingData.KEY[index];
byte secondRoundKey = DecodingData.KEY[DecodingData.KEY.length - 1 - index]; byte secondRoundKey = DecodingData.KEY[DecodingData.KEY.length - 1 - index];
for(int i = 30; i > 0; i--) { for(int i = 30; i > 0; i--) {
int m = (i * firstRoundKey + 45) % 90; int m = (i * firstRoundKey + 45) % 90;
int n = (i * secondRoundKey + 45) % 90; int n = (i * secondRoundKey + 45) % 90;
@ -55,8 +53,7 @@ public enum Decoder {
Utils.swapBits(byte1, byte2, bi1, bi2, bytes); 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--) { for(int i = 40; i > 0; i--) {
int m = i * thirdRoundKey % 90; int m = i * thirdRoundKey % 90;
int n = m % 6; int n = m % 6;
@ -78,20 +75,23 @@ public enum Decoder {
intCode |= (bytes[10] & (0x0000000C >> 2)) << 2; intCode |= (bytes[10] & (0x0000000C >> 2)) << 2;
intCode |= (bytes[11] & (0x00000003 << 4)) >> 4; intCode |= (bytes[11] & (0x00000003 << 4)) >> 4;
int l1 = Integer.parseInt(Integer.toHexString((intCode >> 24) & 0xFF), 10); int l1 = (intCode >> 24) & 0xFF;
int d1 = Integer.parseInt(Integer.toHexString((intCode >> 16) & 0xFF), 16); int d1 = (intCode >> 16) & 0xFF;
int l2 = Integer.parseInt(Integer.toHexString((intCode >> 8) & 0xFF), 16); int l2 = (intCode >> 8) & 0xFF;
int d2 = Integer.parseInt(Integer.toHexString((intCode >> 0) & 0xFF), 16); int d2 = (intCode >> 0) & 0xFF;
String[] letters1 = DecodingData.SAVE_INDEX_TABLE.get(l1 + 1).split("\\s"); String[] letters1 = DecodingData.BYTES_TO_CODE.get(Integer.valueOf(l1).byteValue());
String[] letters2 = DecodingData.SAVE_INDEX_TABLE.get(l2 + 1).split("\\s"); 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 russianCode = letters1[1] + d1 + letters2[1] + d2;
String englishCode = letters1[0] + d1 + letters2[0] + 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 russianLabel = I18N.getString("codeLabelRu");
String englishLabel = I18N.getString("codeLabelEn"); String englishLabel = I18N.getString("codeLabelEn");
String andText = I18N.getString("andText"); return String.format("%s: %s %s %s: %s", russianLabel, russianCode, I18N.getString("andText"), englishLabel, englishCode);
return String.format("%s: %s %s %s: %s", russianLabel, russianCode, andText, englishLabel, englishCode);
} }
} }

View file

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

View file

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

View file

@ -70,4 +70,10 @@ public enum Utils {
bytes[byteInd1] |= 1 << bi1; 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();
}
}
}