diff --git a/CodeField.png b/CodeField.png
deleted file mode 100644
index de1dabe..0000000
Binary files a/CodeField.png and /dev/null differ
diff --git a/DecodedCode.png b/DecodedCode.png
new file mode 100644
index 0000000..7ecc7c8
Binary files /dev/null and b/DecodedCode.png differ
diff --git a/DropSave.png b/DropSave.png
index cde2545..c8fc948 100644
Binary files a/DropSave.png and b/DropSave.png differ
diff --git a/ExampleCode.png b/ExampleCode.png
new file mode 100644
index 0000000..17b2306
Binary files /dev/null and b/ExampleCode.png differ
diff --git a/IncorrectCode.png b/IncorrectCode.png
index 33c86ae..df53daf 100644
Binary files a/IncorrectCode.png and b/IncorrectCode.png differ
diff --git a/README.md b/README.md
index 5460c06..488ce52 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,48 @@
# JDarkroom
-This is a simple, completely offline reimplementation of Darkroom website
-that existed a while ago, but now it is offline. Using JDarkroom you can retrieve
-locker code in Akuda bar by dropping a save file here or by entering an Internet code.
+### ENG
+This is a simple, completely offline reimplementation of the Darkroom website that existed
+a while ago, but now it is offline. Using JDarkroom you can retrieve locker code in Akuda bar.
+There are two ways how you can do that:
+- By dropping a save file here (slot[0-4].sav)
+- Enter an Internet code that you can find at the bottom of the screen when you are about to save your game.
+
+Example of an Internet code: ```fCe\w9!iBXJ1ijn&```
Both Russian and English game versions are supported.
+***A note***
+This app would not exist, if it was not this super helpful article: https://9214.github.io/13
+
+Why did I make this if there are online websites that are much more similar to the original Darkroom?
+As the author of this article said, these can go offline at any time, but with this tool you don't have to
+worry about that, because this app will just be stored on your pc as a single file.
+
+
+### РУС
+Это простая, не требующая интернет-соединения программа, которая повторяет функционал сайта Darkroom,
+который существовал раньше, однако, теперь он больше не доступен. Используя JDarkroom вы можете получить код от шкафа в баре Акуда.
+Сделать это можно двумя способами:
+- Перенести в окно программы файл сохранения (slot[0-4].sav)
+- Ввести специальный Интернет-код, который вы можете найти внизу вашего экрана при сохранении игры.
+
+Пример Интернет-кода: ```fCe\w9!iBXJ1ijn&```
+Поддерживаются Английская и Русская версии игры.
+
+***Заметка***
+Это приложение не существовало бы, если бы не эта суперполезная статья: https://9214.github.io/13
+
+Зачем я создал это приложение, ведь уже существуют сайты, которые очень похожи на оригинальный Darkroom?
+Как сказал автор этой статьи, эти веб-сайты, так же как и оригинальный, могут быть отключены в любое время,
+но с этим приложением вам не стоит переживать, ведь оно просто будет находится у вас на компьютере в виде одного файла.
+
### Screenshots
+#### You can drop save file here:
![Drop Save Here](DropSave.png)
-![Code Field](CodeField.png)
+
+#### An example:
+![Example Code](ExampleCode.png)
+
+#### Code that resulted from ```fCe\w9!iBXJ1ijn&```:
+![Decoded Code](DecodedCode.png)
+
+#### If you enter wrong code:
![Incorrect Code Entered](IncorrectCode.png)
\ No newline at end of file
diff --git a/build.bat b/build.bat
new file mode 100644
index 0000000..2b0a702
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,8 @@
+@ECHO OFF
+set pkg=me\theentropyshard\jdarkroom
+mkdir out
+javac -encoding utf8 -classpath src\%pkg% -d out -sourcepath . src\%pkg%\*.java
+cd out
+jar cfe0 JDarkroom.jar me.theentropyshard.jdarkroom.Main %pkg%\*.class -C ..\resources .
+move JDarkroom.jar ..\
+cd ..\
\ No newline at end of file
diff --git a/resources/background.png b/resources/background.png
new file mode 100644
index 0000000..814ae8f
Binary files /dev/null and b/resources/background.png differ
diff --git a/resources/mdisk.png b/resources/mdisk.png
new file mode 100644
index 0000000..4a79fcb
Binary files /dev/null and b/resources/mdisk.png differ
diff --git a/resources/readme.txt b/resources/readme.txt
new file mode 100644
index 0000000..65a4088
--- /dev/null
+++ b/resources/readme.txt
@@ -0,0 +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.
\ No newline at end of file
diff --git a/run-en.bat b/run-en.bat
new file mode 100644
index 0000000..e5f49a1
--- /dev/null
+++ b/run-en.bat
@@ -0,0 +1,2 @@
+@ECHO OFF
+java -jar JDarkroom.jar en-US
\ No newline at end of file
diff --git a/run-ru.bat b/run-ru.bat
new file mode 100644
index 0000000..2cc7e0a
--- /dev/null
+++ b/run-ru.bat
@@ -0,0 +1,2 @@
+@ECHO OFF
+java -jar JDarkroom.jar ru-RU
\ No newline at end of file
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..88d79cb
--- /dev/null
+++ b/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: me.theentropyshard.jdarkroom.Main
+
diff --git a/src/me/theentropyshard/jdarkroom/Decoder.java b/src/me/theentropyshard/jdarkroom/Decoder.java
new file mode 100644
index 0000000..59c2c81
--- /dev/null
+++ b/src/me/theentropyshard/jdarkroom/Decoder.java
@@ -0,0 +1,97 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package me.theentropyshard.jdarkroom;
+
+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)));
+ }
+
+ for(int i = 6; i > 0; i--) {
+ int byte1 = 2 * i + i / 6;
+ int bi1 = i % 6;
+ int byte2 = 15;
+ int bi2 = i - 1;
+
+ Utils.swapBits(byte1, byte2, bi1, bi2, bytes);
+ }
+
+ String bin = Integer.toBinaryString(bytes[bytes.length - 1]);
+ int index = Integer.parseInt(bin.substring(0, 3), 2);
+
+ 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;
+ int p = m % 6;
+ int q = n % 6;
+
+ int byte1 = (m - p) / 6;
+ int bi1 = p;
+ int byte2 = (n - q) / 6;
+ int bi2 = q;
+
+ Utils.swapBits(byte1, byte2, bi1, bi2, bytes);
+ }
+
+ byte thirdRoundKey = DecodingData.KEY[bytes[bytes.length - 1] & 0b111];
+
+ for(int i = 40; i > 0; i--) {
+ int m = i * thirdRoundKey % 90;
+ int n = m % 6;
+ int byteInd = (m - n) / 6;
+ int bitInd = n;
+
+ bytes[byteInd] ^= (1 << bitInd);
+ }
+
+ return Decoder.readInternetCode(bytes);
+ }
+
+ private static String readInternetCode(byte[] bytes) {
+ int intCode = 0;
+ intCode |= (bytes[8] & (0x1E000000 >> 25)) << 25;
+ intCode |= (bytes[9] & (0x01000000 >> 19)) << 19;
+ intCode |= (bytes[9] & (0x00001F00 >> 8)) << 8;
+ intCode |= (bytes[10] & (0x001F0000 >> 14)) << 14;
+ 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);
+
+ String[] letters1 = DecodingData.SAVE_INDEX_TABLE.get(l1 + 1).split("\\s");
+ String[] letters2 = DecodingData.SAVE_INDEX_TABLE.get(l2 + 1).split("\\s");
+
+ String russianCode = letters1[1] + d1 + letters2[1] + d2;
+ String englishCode = letters1[0] + d1 + letters2[0] + d2;
+
+ 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);
+ }
+}
diff --git a/src/me/theentropyshard/jdarkroom/DecodingData.java b/src/me/theentropyshard/jdarkroom/DecodingData.java
index 92a0bb1..cbd65e4 100644
--- a/src/me/theentropyshard/jdarkroom/DecodingData.java
+++ b/src/me/theentropyshard/jdarkroom/DecodingData.java
@@ -1,3 +1,20 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
package me.theentropyshard.jdarkroom;
import java.util.Collections;
@@ -68,6 +85,7 @@ public final class DecodingData {
*/
public static final Map CODE_INDEX_TABLE = Collections.unmodifiableMap(
new HashMap() {{
+ // @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);
@@ -132,6 +150,7 @@ public final class DecodingData {
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);
+ // @formatter:on
}}
);
}
diff --git a/src/me/theentropyshard/jdarkroom/I18N.java b/src/me/theentropyshard/jdarkroom/I18N.java
new file mode 100644
index 0000000..1ac25a0
--- /dev/null
+++ b/src/me/theentropyshard/jdarkroom/I18N.java
@@ -0,0 +1,64 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package me.theentropyshard.jdarkroom;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+public enum I18N {
+ ;
+
+ public static final Locale LOCALE = Locale.getDefault();
+ public static final String LANGUAGE = I18N.LOCALE.getLanguage().toLowerCase(Locale.ROOT);
+
+ private static final Map TRANSLATION = new HashMap<>();
+
+ static {
+ I18N.TRANSLATION.put("en.dropFileHere", "Drop save file here! (slot[0-4].sav)
Or click to enter Internet code!");
+ I18N.TRANSLATION.put("ru.dropFileHere", "Переместите файл сохранения сюда! (slot[0-4].sav)
Или нажмите, чтобы ввести Интернет-код!");
+
+ I18N.TRANSLATION.put("en.enterCodeHere", "Enter Internet code here");
+ I18N.TRANSLATION.put("ru.enterCodeHere", "Введите сюда Интернет-код");
+
+ I18N.TRANSLATION.put("en.submit", "Submit");
+ I18N.TRANSLATION.put("ru.submit", "Отправить");
+
+ I18N.TRANSLATION.put("en.incorrectCodeLength", "Internet code length must be 16 chars!");
+ I18N.TRANSLATION.put("ru.incorrectCodeLength", "Длина Интернет-кода должна быть 16 символов!");
+
+ I18N.TRANSLATION.put("en.badFile", "Bad file given!");
+ I18N.TRANSLATION.put("ru.badFile", "Перемещен плохой файл!");
+
+ I18N.TRANSLATION.put("ru.andText", "и");
+ I18N.TRANSLATION.put("en.andText", "and");
+
+ I18N.TRANSLATION.put("ru.codeLabelRu", "Русский код");
+ I18N.TRANSLATION.put("ru.codeLabelEn", "Английский код");
+
+ I18N.TRANSLATION.put("en.codeLabelRu", "Russian code");
+ I18N.TRANSLATION.put("en.codeLabelEn", "English code");
+ }
+
+ public static String getString(String key) {
+ if(key.indexOf(".") == 2) {
+ return I18N.TRANSLATION.get(key);
+ }
+ return I18N.TRANSLATION.get(I18N.LANGUAGE + "." + key);
+ }
+}
diff --git a/src/me/theentropyshard/jdarkroom/JDarkroom.java b/src/me/theentropyshard/jdarkroom/JDarkroom.java
index 5327cbf..7d54060 100644
--- a/src/me/theentropyshard/jdarkroom/JDarkroom.java
+++ b/src/me/theentropyshard/jdarkroom/JDarkroom.java
@@ -1,8 +1,21 @@
-package me.theentropyshard.jdarkroom;
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
+package me.theentropyshard.jdarkroom;
public class JDarkroom {
public JDarkroom() {
@@ -14,134 +27,6 @@ public class JDarkroom {
new View();
}
- public String findInSave(File file) {
- if(file == null || file.isDirectory()) {
- return null;
- }
-
- StringBuilder russianCode = new StringBuilder();
- StringBuilder englishCode = new StringBuilder();
- try(RandomAccessFile raf = new RandomAccessFile(file, "r")) {
- if(raf.length() <= 0) {
- return null;
- }
-
- raf.seek(DecodingData.CODE_OFFSET);
-
- for(int i = 0; i < 16; i++) {
- byte b = raf.readByte();
- if(i % 4 == 0) {
- String[] letters = DecodingData.SAVE_INDEX_TABLE.get((int) b).split("\\s");
- russianCode.append(letters[1]);
- englishCode.append(letters[0]);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- return null;
- }
-
- return russianCode + " or " + englishCode;
- }
-
- public String decodeInternetCode(String code) {
- if(code == null || code.length() != 16) {
- return null;
- }
-
- byte[] bytes = new byte[16];
- for(int i = 0; i < 16; i++) {
- bytes[i] = DecodingData.CODE_INDEX_TABLE.get(Character.toString(code.charAt(i)));
- }
-
- for(int i = 6; i > 0; i--) {
- int byte1 = 2 * i + i / 6;
- int bi1 = i % 6;
- int byte2 = 15;
- int bi2 = i - 1;
-
- JDarkroom.swapBits(byte1, byte2, bi1, bi2, bytes);
- }
-
- String bin = Integer.toBinaryString(bytes[bytes.length - 1]);
- int index = Integer.parseInt(bin.substring(0, 3), 2);
-
- 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;
- int p = m % 6;
- int q = n % 6;
-
- int byte1 = (m - p) / 6;
- int bi1 = p;
- int byte2 = (n - q) / 6;
- int bi2 = q;
-
- JDarkroom.swapBits(byte1, byte2, bi1, bi2, bytes);
- }
-
- byte thirdRoundKey = DecodingData.KEY[bytes[bytes.length - 1] & 0b111];
-
- for(int i = 40; i > 0; i--) {
- int m = i * thirdRoundKey % 90;
- int n = m % 6;
- int byteInd = (m - n) / 6;
- int bitInd = n;
-
- bytes[byteInd] ^= (1 << bitInd);
- }
-
- int intCode = 0;
- intCode |= (bytes[8] & (0x1E000000 >> 25)) << 25;
- intCode |= (bytes[9] & (0x01000000 >> 19)) << 19;
- intCode |= (bytes[9] & (0x00001F00 >> 8)) << 8;
- intCode |= (bytes[10] & (0x001F0000 >> 14)) << 14;
- 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);
-
- String[] letters1 = DecodingData.SAVE_INDEX_TABLE.get(l1 + 1).split("\\s");
- String[] letters2 = DecodingData.SAVE_INDEX_TABLE.get(l2 + 1).split("\\s");
-
- String russianCode = letters1[1] + d1 + letters2[1] + d2;
- String englishCode = letters1[0] + d1 + letters2[0] + d2;
-
- return russianCode + " or " + englishCode;
- }
-
- /**
- * Swaps two different bits in two numbers
- *
- * @param byteInd1 Index of first byte
- * @param byteInd2 Index of second byte
- * @param bi1 0-based bit from first number, indexed from right
- * @param bi2 0-based bit from second number, indexed from right
- * @param bytes Byte array
- */
- public static void swapBits(int byteInd1, int byteInd2, int bi1, int bi2, byte... bytes) {
- int bit1 = (bytes[byteInd1] >> bi1) & 1;
- int bit2 = (bytes[byteInd2] >> bi2) & 1;
-
- if(bit1 == 0) {
- bytes[byteInd2] &= ~(1 << bi2);
- } else {
- bytes[byteInd2] |= 1 << bi2;
- }
-
- if(bit2 == 0) {
- bytes[byteInd1] &= ~(1 << bi1);
- } else {
- bytes[byteInd1] |= 1 << bi1;
- }
- }
-
private static JDarkroom instance;
public static JDarkroom getInstance() {
diff --git a/src/me/theentropyshard/jdarkroom/Main.java b/src/me/theentropyshard/jdarkroom/Main.java
index 2026e2f..de02383 100644
--- a/src/me/theentropyshard/jdarkroom/Main.java
+++ b/src/me/theentropyshard/jdarkroom/Main.java
@@ -1,11 +1,47 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
package me.theentropyshard.jdarkroom;
import javax.swing.*;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
- JDialog.setDefaultLookAndFeelDecorated(true);
- JFrame.setDefaultLookAndFeelDecorated(true);
+ Pattern localePattern = Pattern.compile("([a-z]{2})-([A-Z]{2})$");
+ if(args.length > 0) {
+ Matcher matcher = localePattern.matcher(args[0]);
+ if(matcher.matches()) {
+ try {
+ String[] langAndCountry = args[0].split("-");
+ Locale.setDefault(new Locale(langAndCountry[0], langAndCountry[1]));
+ } catch (Exception se) {
+ se.printStackTrace();
+ }
+ }
+ }
+
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ignored) {
+
+ }
new JDarkroom();
}
diff --git a/src/me/theentropyshard/jdarkroom/SaveFileReader.java b/src/me/theentropyshard/jdarkroom/SaveFileReader.java
new file mode 100644
index 0000000..c3c45e6
--- /dev/null
+++ b/src/me/theentropyshard/jdarkroom/SaveFileReader.java
@@ -0,0 +1,59 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package me.theentropyshard.jdarkroom;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+public enum SaveFileReader {
+ ;
+
+ public static String findInSave(File file) {
+ if(file == null || file.isDirectory()) {
+ return null;
+ }
+
+ StringBuilder russianCode = new StringBuilder();
+ StringBuilder englishCode = new StringBuilder();
+ try(RandomAccessFile raf = new RandomAccessFile(file, "r")) {
+ if(raf.length() <= 0) {
+ return null;
+ }
+
+ raf.seek(DecodingData.CODE_OFFSET);
+
+ for(int i = 0; i < 16; i++) {
+ byte b = raf.readByte();
+ if(i % 4 == 0) {
+ String[] letters = DecodingData.SAVE_INDEX_TABLE.get((int) b).split("\\s");
+ russianCode.append(letters[1]);
+ englishCode.append(letters[0]);
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+
+ 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);
+ }
+}
diff --git a/src/me/theentropyshard/jdarkroom/Utils.java b/src/me/theentropyshard/jdarkroom/Utils.java
new file mode 100644
index 0000000..36071eb
--- /dev/null
+++ b/src/me/theentropyshard/jdarkroom/Utils.java
@@ -0,0 +1,73 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
+package me.theentropyshard.jdarkroom;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public enum Utils {
+ ;
+
+ private static final Map IMAGE_CACHE = new HashMap<>();
+
+ public static BufferedImage loadImage(String path) {
+ if(Utils.IMAGE_CACHE.containsKey(path)) {
+ return Utils.IMAGE_CACHE.get(path);
+ }
+
+ try {
+ BufferedImage bufferedImage = ImageIO.read(Objects.requireNonNull(Utils.class.getResourceAsStream(path)));
+ Utils.IMAGE_CACHE.put(path, bufferedImage);
+ return bufferedImage;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * Swaps two different bits in two numbers
+ *
+ * @param byteInd1 Index of first byte
+ * @param byteInd2 Index of second byte
+ * @param bi1 0-based bit from first number, indexed from right
+ * @param bi2 0-based bit from second number, indexed from right
+ * @param bytes Byte array
+ */
+ public static void swapBits(int byteInd1, int byteInd2, int bi1, int bi2, byte... bytes) {
+ int bit1 = (bytes[byteInd1] >> bi1) & 1;
+ int bit2 = (bytes[byteInd2] >> bi2) & 1;
+
+ if(bit1 == 0) {
+ bytes[byteInd2] &= ~(1 << bi2);
+ } else {
+ bytes[byteInd2] |= 1 << bi2;
+ }
+
+ if(bit2 == 0) {
+ bytes[byteInd1] &= ~(1 << bi1);
+ } else {
+ bytes[byteInd1] |= 1 << bi1;
+ }
+ }
+}
diff --git a/src/me/theentropyshard/jdarkroom/View.java b/src/me/theentropyshard/jdarkroom/View.java
index f232178..4e294e6 100644
--- a/src/me/theentropyshard/jdarkroom/View.java
+++ b/src/me/theentropyshard/jdarkroom/View.java
@@ -1,3 +1,20 @@
+/* JDarkroom. Reimplementation of Darkroom website.
+ * Copyright (C) 2023 TheEntropyShard
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see .
+ */
+
package me.theentropyshard.jdarkroom;
import javax.swing.*;
@@ -8,107 +25,215 @@ import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
import java.io.File;
import java.util.List;
public final class View extends JPanel {
- private static final String YOUR_CODE_IS = "Your code is: ";
- private static final float FONT_SIZE = 20.0F;
- private final JTextField codeField;
+ private static final Color TRANSPARENT_COLOR = new Color(255, 255, 255, 96);
+ private static final Color TRANSPARENT_COLOR_DARKER = View.TRANSPARENT_COLOR.darker().darker().darker();
+ private static final BufferedImage BACKGROUND = Utils.loadImage("/background.png");
+ private static final int WIDTH = View.BACKGROUND.getWidth();
+ private static final int HEIGHT = View.BACKGROUND.getHeight();
+ private static final Font FONT = new Font("Arial", Font.BOLD, 14);
+ private final JLabel resultLabel;
+
+ @SuppressWarnings("unchecked")
public View() {
- this.setPreferredSize(new Dimension(800, 600));
+ this.setPreferredSize(new Dimension(View.WIDTH, View.HEIGHT));
- CardLayout cardLayout = new CardLayout();
+ CardLayout cardLayout = new CardLayout(30, 50);
this.setLayout(cardLayout);
- JLabel codeLabel = new JLabel(View.YOUR_CODE_IS) {{
+ this.resultLabel = new JLabel() {{
this.setHorizontalAlignment(JLabel.CENTER);
- this.setFont(this.getFont().deriveFont(View.FONT_SIZE));
- this.setFocusable(true);
- this.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- super.mouseClicked(e);
- cardLayout.show(View.this, "DropLabel");
- }
- });
+ this.setFont(View.FONT);
}};
- this.add(codeLabel, "CodeLabel");
- JLabel dropLabel = new JLabel("Drop save file here, or click to enter Internet Code!", JLabel.CENTER) {{
- this.setDropTarget(new DropTarget() {
- @Override
- public synchronized void drop(DropTargetDropEvent e) {
- try {
- e.acceptDrop(DnDConstants.ACTION_COPY);
- List droppedFiles = (List) e.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
- String code = JDarkroom.getInstance().findInSave(droppedFiles.get(0));
- if(code == null) {
- codeLabel.setText("Incorrect Internet Code entered!");
- codeLabel.setForeground(Color.RED);
- codeField.setText("");
- } else {
- codeLabel.setForeground(Color.DARK_GRAY);
- codeLabel.setText(View.YOUR_CODE_IS + code);
- }
- cardLayout.show(View.this, "CodeLabel");
- e.dropComplete(true);
- } catch (Exception ex) {
- ex.printStackTrace();
+ JPanel dropFilePanel = new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.CENTER;
+ this.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ super.mousePressed(e);
+ cardLayout.show(View.this, "enterCodePanel");
+ }
+ });
+ this.add(new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
+ this.add(new JLabel(I18N.getString("dropFileHere")) {{
+ this.setFont(View.FONT);
+ this.setHorizontalAlignment(JLabel.CENTER);
+ }});
}
- }
- });
- this.addMouseListener(new MouseAdapter() {
- @Override
- public void mouseClicked(MouseEvent e) {
- super.mouseClicked(e);
- cardLayout.show(View.this, "InputPanel");
- }
- });
- this.setFocusable(true);
- this.setFont(this.getFont().deriveFont(View.FONT_SIZE));
- }};
- this.add(dropLabel, "DropLabel");
- JPanel panel = new JPanel(new GridBagLayout());
- GridBagConstraints c = new GridBagConstraints();
- c.fill = GridBagConstraints.BOTH;
- c.anchor = GridBagConstraints.CENTER;
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR_DARKER);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ }, c);
+ this.setDropTarget(new DropTarget() {
+ @Override
+ public synchronized void drop(DropTargetDropEvent dtde) {
+ dtde.acceptDrop(DnDConstants.ACTION_COPY);
+ try {
+ List droppedFiles = (List)
+ dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
+ String code = SaveFileReader.findInSave(droppedFiles.get(0));
+ if(code == null) {
+ resultLabel.setText(I18N.getString("badFile"));
+ } else {
+ resultLabel.setText(code);
+ }
+ cardLayout.show(View.this, "resultPanel");
+ } catch (Exception e) {
+ e.printStackTrace();
+ dtde.dropComplete(false);
+ }
+ dtde.dropComplete(true);
+ }
+ });
+ }
- this.codeField = new JTextField() {{
- this.setPreferredSize(new Dimension(250, 50));
- this.setFont(this.getFont().deriveFont(View.FONT_SIZE));
- }};
- panel.add(this.codeField, c);
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ };
- JButton button = new JButton("Enter") {{
- this.setPreferredSize(new Dimension(100, 50));
- this.setFont(this.getFont().deriveFont(View.FONT_SIZE));
- this.addActionListener(e -> {
- String code = JDarkroom.getInstance().decodeInternetCode(codeField.getText());
- if(code == null) {
- codeLabel.setText("Incorrect Internet Code entered!");
- codeLabel.setForeground(Color.RED);
- codeField.setText("");
- } else {
- codeLabel.setForeground(Color.DARK_GRAY);
- codeLabel.setText(View.YOUR_CODE_IS + code);
- }
- cardLayout.show(View.this, "CodeLabel");
- });
- }};
+ JPanel enterCodePanel = new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setLayout(new BorderLayout());
+ this.add(new JPanel() {{
+ this.setOpaque(false);
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.CENTER;
+ JTextField textField = new JTextField() {{
+ this.setFont(View.FONT);
+ this.setPreferredSize(new Dimension(220, 30));
+ }};
+ this.add(new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
+ this.setLayout(new GridLayout(2, 1));
+ this.add(new JPanel() {{
+ this.setLayout(new BorderLayout());
+ this.setOpaque(false);
+ this.add(new JLabel(I18N.getString("enterCodeHere")) {{
+ this.setHorizontalAlignment(JLabel.CENTER);
+ this.setFont(View.FONT);
+ }});
+ }});
+ this.add(new JPanel() {{
+ this.setOpaque(false);
+ this.add(textField);
+ this.add(new JButton(I18N.getString("submit")) {{
+ this.setFont(View.FONT);
+ this.setPreferredSize(new Dimension(120, 30));
+ this.addActionListener(e -> {
+ String text = textField.getText();
+ textField.setText("");
+ if(text.length() != 16) {
+ resultLabel.setText(I18N.getString("incorrectCodeLength"));
+ } else {
+ resultLabel.setText(Decoder.decodeInternetCode(text));
+ }
+ cardLayout.show(View.this, "resultPanel");
+ });
+ }});
+ }});
+ }
- panel.add(button, c);
- this.add(panel, "InputPanel");
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR_DARKER);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ }, c);
+ }});
+ }
- cardLayout.show(this, "DropLabel");
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ };
+
+ JPanel resultPanel = new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setLayout(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.CENTER;
+ this.add(new JPanel() {
+ {
+ this.setOpaque(false);
+ this.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
+ this.add(resultLabel);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR_DARKER);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ }, c);
+ this.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ super.mousePressed(e);
+ cardLayout.show(View.this, "dropFilePanel");
+ }
+ });
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.setColor(View.TRANSPARENT_COLOR);
+ g.fillRect(0, 0, this.getWidth(), this.getHeight());
+ }
+ };
+
+ this.add(dropFilePanel, "dropFilePanel");
+ this.add(enterCodePanel, "enterCodePanel");
+ this.add(resultPanel, "resultPanel");
+
+ cardLayout.show(this, "dropFilePanel");
JFrame frame = new JFrame("JDarkroom");
- frame.add(this);
+ frame.add(this, BorderLayout.CENTER);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ frame.setIconImage(Utils.loadImage("/mdisk.png"));
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ g.drawImage(View.BACKGROUND, 0, 0, null);
+ }
}