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); + } }