Interface update and code improvements, translation
BIN
CodeField.png
Before Width: | Height: | Size: 7.8 KiB |
BIN
DecodedCode.png
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
DropSave.png
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 206 KiB |
BIN
ExampleCode.png
Normal file
After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 205 KiB |
46
README.md
|
@ -1,10 +1,48 @@
|
|||
# JDarkroom
|
||||
This is a simple, completely offline reimplementation of Darkroom website<br>
|
||||
that existed a while ago, but now it is offline. Using JDarkroom you can retrieve<br>
|
||||
locker code in Akuda bar by dropping a save file here or by entering an Internet code.<br>
|
||||
### ENG
|
||||
This is a simple, completely offline reimplementation of the Darkroom website that existed<br>
|
||||
a while ago, but now it is offline. Using JDarkroom you can retrieve locker code in Akuda bar.<br>
|
||||
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.<br>
|
||||
|
||||
Example of an Internet code: ```fCe\w9!iBXJ1ijn&```<br>
|
||||
Both Russian and English game versions are supported.
|
||||
|
||||
***A note***<br>
|
||||
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?<br>
|
||||
As the author of this article said, these can go offline at any time, but with this tool you don't have to<br>
|
||||
worry about that, because this app will just be stored on your pc as a single file.
|
||||
|
||||
|
||||
### РУС
|
||||
Это простая, не требующая интернет-соединения программа, которая повторяет функционал сайта Darkroom,<br>
|
||||
который существовал раньше, однако, теперь он больше не доступен. Используя JDarkroom вы можете получить код от шкафа в баре Акуда.<br>
|
||||
Сделать это можно двумя способами:
|
||||
- Перенести в окно программы файл сохранения (slot[0-4].sav)
|
||||
- Ввести специальный Интернет-код, который вы можете найти внизу вашего экрана при сохранении игры.<br>
|
||||
|
||||
Пример Интернет-кода: ```fCe\w9!iBXJ1ijn&```<br>
|
||||
Поддерживаются Английская и Русская версии игры.
|
||||
|
||||
***Заметка***<br>
|
||||
Это приложение не существовало бы, если бы не эта суперполезная статья: https://9214.github.io/13
|
||||
|
||||
Зачем я создал это приложение, ведь уже существуют сайты, которые очень похожи на оригинальный Darkroom?<br>
|
||||
Как сказал автор этой статьи, эти веб-сайты, так же как и оригинальный, могут быть отключены в любое время,<br>
|
||||
но с этим приложением вам не стоит переживать, ведь оно просто будет находится у вас на компьютере в виде одного файла.
|
||||
|
||||
### 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)
|
8
build.bat
Normal file
|
@ -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 ..\
|
BIN
resources/background.png
Normal file
After Width: | Height: | Size: 217 KiB |
BIN
resources/mdisk.png
Normal file
After Width: | Height: | Size: 853 B |
4
resources/readme.txt
Normal file
|
@ -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.
|
2
run-en.bat
Normal file
|
@ -0,0 +1,2 @@
|
|||
@ECHO OFF
|
||||
java -jar JDarkroom.jar en-US
|
2
run-ru.bat
Normal file
|
@ -0,0 +1,2 @@
|
|||
@ECHO OFF
|
||||
java -jar JDarkroom.jar ru-RU
|
3
src/META-INF/MANIFEST.MF
Normal file
|
@ -0,0 +1,3 @@
|
|||
Manifest-Version: 1.0
|
||||
Main-Class: me.theentropyshard.jdarkroom.Main
|
||||
|
97
src/me/theentropyshard/jdarkroom/Decoder.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.theentropyshard.jdarkroom;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -68,6 +85,7 @@ public final class DecodingData {
|
|||
*/
|
||||
public static final Map<String, Byte> CODE_INDEX_TABLE = Collections.unmodifiableMap(
|
||||
new HashMap<String, 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);
|
||||
|
@ -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
|
||||
}}
|
||||
);
|
||||
}
|
||||
|
|
64
src/me/theentropyshard/jdarkroom/I18N.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, String> TRANSLATION = new HashMap<>();
|
||||
|
||||
static {
|
||||
I18N.TRANSLATION.put("en.dropFileHere", "<html>Drop save file here! (slot[0-4].sav)<br>Or click to enter Internet code!</html>");
|
||||
I18N.TRANSLATION.put("ru.dropFileHere", "<html>Переместите файл сохранения сюда! (slot[0-4].sav)<br>Или нажмите, чтобы ввести Интернет-код!</html>");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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() {
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
59
src/me/theentropyshard/jdarkroom/SaveFileReader.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
73
src/me/theentropyshard/jdarkroom/Utils.java
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, BufferedImage> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<File> droppedFiles = (List<File>) 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<File> droppedFiles = (List<File>)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|