mirror of
https://github.com/Melledy/Nebula.git
synced 2025-12-17 14:54:46 +01:00
Initial Commit
This commit is contained in:
284
src/main/java/emu/nebula/util/AeadHelper.java
Normal file
284
src/main/java/emu/nebula/util/AeadHelper.java
Normal file
@@ -0,0 +1,284 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.bouncycastle.asn1.nist.NISTNamedCurves;
|
||||
import org.bouncycastle.asn1.sec.SECNamedCurves;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
|
||||
import org.bouncycastle.crypto.params.*;
|
||||
|
||||
// Official Name: AeadTool
|
||||
public class AeadHelper {
|
||||
private static final ThreadLocal<SecureRandom> random = new ThreadLocal<>() {
|
||||
@Override
|
||||
protected SecureRandom initialValue() {
|
||||
return new SecureRandom();
|
||||
}
|
||||
};
|
||||
|
||||
public static final byte[] serverGarbleKey = "xNdVF^XTa6T3HCUATMQ@sKMLzAw&%L!3".getBytes(StandardCharsets.US_ASCII); // Global
|
||||
public static final byte[] serverMetaKey = "ma5Dn2FhC*Xhxy%c".getBytes(StandardCharsets.US_ASCII); // Global
|
||||
|
||||
public static byte[] generateBytes(int size) {
|
||||
byte[] iv = new byte[size];
|
||||
random.get().nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
// AES CBC
|
||||
|
||||
public static byte[] encryptCBC(byte[] messageData) throws Exception {
|
||||
byte[] iv = generateBytes(16);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.ENCRYPT_MODE,
|
||||
new SecretKeySpec(serverMetaKey, "AES"),
|
||||
new IvParameterSpec(iv)
|
||||
);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(messageData);
|
||||
byte[] data = new byte[encrypted.length + iv.length];
|
||||
|
||||
System.arraycopy(iv, 0, data, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, data, iv.length, encrypted.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] decryptCBC(byte[] messageData) throws Exception {
|
||||
byte[] iv = new byte[16];
|
||||
byte[] data = new byte[messageData.length - iv.length];
|
||||
|
||||
System.arraycopy(messageData, 0, iv, 0, iv.length);
|
||||
System.arraycopy(messageData, iv.length, data, 0, data.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(serverMetaKey, "AES"),
|
||||
new IvParameterSpec(iv)
|
||||
);
|
||||
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
// AES GCM
|
||||
|
||||
public static byte[] encryptGCM(byte[] messageData, byte[] key) throws Exception {
|
||||
byte[] iv = generateBytes(12);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.ENCRYPT_MODE,
|
||||
new SecretKeySpec(key, "AES"),
|
||||
new GCMParameterSpec(128, iv)
|
||||
);
|
||||
cipher.updateAAD(iv);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(messageData);
|
||||
byte[] data = new byte[encrypted.length + iv.length];
|
||||
|
||||
System.arraycopy(iv, 0, data, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, data, iv.length, encrypted.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] decryptGCM(byte[] messageData, byte[] key) throws Exception {
|
||||
byte[] iv = new byte[12];
|
||||
byte[] data = new byte[messageData.length - iv.length];
|
||||
System.arraycopy(messageData, 0, iv, 0, iv.length);
|
||||
System.arraycopy(messageData, iv.length, data, 0, data.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(key, "AES"),
|
||||
new GCMParameterSpec(128, iv)
|
||||
);
|
||||
cipher.updateAAD(iv);
|
||||
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
// Chacha20
|
||||
|
||||
public static byte[] encryptChaCha(byte[] messageData, byte[] key) throws Exception {
|
||||
byte[] iv = generateBytes(12);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.ENCRYPT_MODE,
|
||||
new SecretKeySpec(key, "ChaCha20"),
|
||||
new IvParameterSpec(iv)
|
||||
);
|
||||
cipher.updateAAD(iv);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(messageData);
|
||||
byte[] data = new byte[encrypted.length + iv.length];
|
||||
|
||||
System.arraycopy(iv, 0, data, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, data, iv.length, encrypted.length);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] decryptChaCha(byte[] messageData, byte[] key) throws Exception {
|
||||
byte[] iv = new byte[12];
|
||||
byte[] data = new byte[messageData.length - iv.length];
|
||||
System.arraycopy(messageData, 0, iv, 0, iv.length);
|
||||
System.arraycopy(messageData, iv.length, data, 0, data.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305/None/NoPadding");
|
||||
|
||||
cipher.init(
|
||||
Cipher.DECRYPT_MODE,
|
||||
new SecretKeySpec(key, "ChaCha20"),
|
||||
new IvParameterSpec(iv)
|
||||
);
|
||||
cipher.updateAAD(iv);
|
||||
|
||||
return cipher.doFinal(data);
|
||||
}
|
||||
|
||||
// XOR
|
||||
|
||||
public static byte[] encryptBasic(byte[] messageData, byte[] key3) {
|
||||
byte[] data = new byte[messageData.length];
|
||||
System.arraycopy(messageData, 0, data, 0, data.length);
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] ^= key3[i % key3.length];
|
||||
|
||||
int b = data[i];
|
||||
|
||||
byte v7 = (byte) (b << 1);
|
||||
byte v1 = (byte) ((b >> 7) & 0b00000001);
|
||||
|
||||
data[i] = (byte) (v1 | v7);
|
||||
|
||||
data[i] ^= data.length;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static byte[] decryptBasic(byte[] messageData, byte[] key3) {
|
||||
byte[] data = new byte[messageData.length];
|
||||
System.arraycopy(messageData, 0, data, 0, data.length);
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] ^= data.length;
|
||||
|
||||
int b = data[i];
|
||||
|
||||
byte v1 = (byte) (b << 7);
|
||||
byte v7 = (byte) ((b >> 1) & 0b01111111);
|
||||
|
||||
data[i] = (byte) (v7 | v1);
|
||||
|
||||
data[i] ^= key3[i % key3.length];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// ECDH
|
||||
|
||||
public static AsymmetricCipherKeyPair generateECDHKEyPair() {
|
||||
var generator = new ECKeyPairGenerator();
|
||||
|
||||
var p = NISTNamedCurves.getByName("P-256");
|
||||
var domainParams = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH());
|
||||
var genParams = new ECKeyGenerationParameters(domainParams, random.get());
|
||||
|
||||
generator.init(genParams);
|
||||
return generator.generateKeyPair();
|
||||
}
|
||||
|
||||
public static byte[] generateKey(byte[] clientPublic, byte[] serverPublic, byte[] serverPrivate) {
|
||||
// Setup
|
||||
byte[] ikm = new byte[32];
|
||||
byte[] salt = serverPublic;
|
||||
byte[] info = new byte[serverPublic.length];
|
||||
|
||||
// Create info
|
||||
for (int i = 0; i < info.length; i++) {
|
||||
int c = clientPublic[i] & 0xff;
|
||||
int s = serverPublic[i] & 0xff;
|
||||
if (c > s) {
|
||||
s = (s << 1);
|
||||
} else {
|
||||
s = ((s >> 1) & 0b01111111);
|
||||
}
|
||||
info[i] = (byte) (s ^ c);
|
||||
}
|
||||
|
||||
var sharedKey = calcECDHSharedKey(clientPublic, serverPrivate);
|
||||
int count = Math.min(sharedKey.length, 32);
|
||||
System.arraycopy(sharedKey, 0, ikm, 32 - count, count);
|
||||
|
||||
// Generator
|
||||
var generator = new HKDFBytesGenerator(new SHA256Digest());
|
||||
var genParams = new HKDFParameters(ikm, salt, info);
|
||||
|
||||
byte[] output = new byte[32];
|
||||
|
||||
generator.init(genParams);
|
||||
generator.generateBytes(output, 0, output.length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public static byte[] calcECDHSharedKey(byte[] clientPublic, byte[] serverPrivate) {
|
||||
var p = SECNamedCurves.getByName("secp256r1");
|
||||
var domainParams = new ECDomainParameters(p.getCurve(), p.getG(), p.getN(), p.getH(), p.getSeed());
|
||||
|
||||
var clientPoint = p.getCurve().decodePoint(clientPublic);
|
||||
var clientParams = new ECPublicKeyParameters(clientPoint, domainParams);
|
||||
|
||||
var serverInteger = new BigInteger(serverPrivate);
|
||||
var serverParams = new ECPrivateKeyParameters(serverInteger, domainParams);
|
||||
|
||||
var agreement = new ECDHBasicAgreement();
|
||||
agreement.init(serverParams);
|
||||
|
||||
var result = agreement.calculateAgreement(clientParams);
|
||||
|
||||
return getUnsignedByteArray(result);
|
||||
}
|
||||
|
||||
public static byte[] getUnsignedByteArray(BigInteger b) {
|
||||
byte[] signedByteArray = b.toByteArray();
|
||||
byte[] unsignedByteArray;
|
||||
|
||||
if (signedByteArray[0] == 0 && signedByteArray.length > 1) {
|
||||
// Remove the leading zero byte
|
||||
unsignedByteArray = new byte[signedByteArray.length - 1];
|
||||
System.arraycopy(signedByteArray, 1, unsignedByteArray, 0, unsignedByteArray.length);
|
||||
} else {
|
||||
// No leading zero byte to remove, or it's a negative number
|
||||
// which is not directly representable as an unsigned byte array without special handling
|
||||
unsignedByteArray = signedByteArray;
|
||||
}
|
||||
|
||||
return unsignedByteArray;
|
||||
}
|
||||
}
|
||||
39
src/main/java/emu/nebula/util/FileUtils.java
Normal file
39
src/main/java/emu/nebula/util/FileUtils.java
Normal file
@@ -0,0 +1,39 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
|
||||
public class FileUtils {
|
||||
public static void write(String dest, byte[] bytes) {
|
||||
Path path = Paths.get(dest);
|
||||
|
||||
try {
|
||||
Files.write(path, bytes);
|
||||
} catch (IOException e) {
|
||||
Nebula.getLogger().warn("Failed to write file: " + dest);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] read(String dest) {
|
||||
return read(Paths.get(dest));
|
||||
}
|
||||
|
||||
public static byte[] read(Path path) {
|
||||
try {
|
||||
return Files.readAllBytes(path);
|
||||
} catch (IOException e) {
|
||||
Nebula.getLogger().warn("Failed to read file: " + path);
|
||||
}
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
public static byte[] read(File file) {
|
||||
return read(file.getPath());
|
||||
}
|
||||
}
|
||||
94
src/main/java/emu/nebula/util/Handbook.java
Normal file
94
src/main/java/emu/nebula/util/Handbook.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import emu.nebula.GameConstants;
|
||||
import emu.nebula.Nebula;
|
||||
import emu.nebula.data.GameData;
|
||||
import emu.nebula.data.ResourceType;
|
||||
import emu.nebula.data.resources.CharacterDef;
|
||||
import emu.nebula.data.resources.ItemDef;
|
||||
|
||||
public class Handbook {
|
||||
|
||||
public static void generate() {
|
||||
// Temp vars
|
||||
Map<String, String> languageKey = null;
|
||||
List<Integer> list = null;
|
||||
|
||||
// Save to file
|
||||
String file = "./Nebula Handbook.txt";
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8), true)) {
|
||||
// Format date for header
|
||||
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
|
||||
var time = Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault()).format(dtf);
|
||||
|
||||
// Header
|
||||
writer.println("# Nebula " + GameConstants.VERSION + " Handbook");
|
||||
writer.println("# Created " + time);
|
||||
|
||||
// Dump characters
|
||||
writer.println(System.lineSeparator());
|
||||
writer.println("# Characters");
|
||||
list = GameData.getCharacterDataTable().keySet().intStream().sorted().boxed().toList();
|
||||
languageKey = loadLanguageKey(CharacterDef.class);
|
||||
for (int id : list) {
|
||||
CharacterDef data = GameData.getCharacterDataTable().get(id);
|
||||
writer.print(data.getId());
|
||||
writer.print(" : ");
|
||||
writer.println(languageKey.getOrDefault(data.getName(), data.getName()));
|
||||
}
|
||||
|
||||
// Dump characters
|
||||
writer.println(System.lineSeparator());
|
||||
writer.println("# Items");
|
||||
list = GameData.getItemDataTable().keySet().intStream().sorted().boxed().toList();
|
||||
languageKey = loadLanguageKey(ItemDef.class);
|
||||
for (int id : list) {
|
||||
ItemDef data = GameData.getItemDataTable().get(id);
|
||||
writer.print(data.getId());
|
||||
writer.print(" : ");
|
||||
writer.print(languageKey.getOrDefault(data.getTitle(), data.getTitle()));
|
||||
|
||||
writer.print(" [");
|
||||
writer.print(data.getItemType());
|
||||
writer.println("]");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, String> loadLanguageKey(Class<?> resourceClass) {
|
||||
// Get type
|
||||
ResourceType type = resourceClass.getAnnotation(ResourceType.class);
|
||||
if (type == null) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
// Load
|
||||
Map<String, String> map = null;
|
||||
|
||||
try {
|
||||
map = JsonUtils.loadToMap(Nebula.getConfig().getResourceDir() + "/language/en_US/" + type.name(), String.class, String.class);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (map == null) {
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
112
src/main/java/emu/nebula/util/JsonUtils.java
Normal file
112
src/main/java/emu/nebula/util/JsonUtils.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import emu.nebula.Nebula;
|
||||
|
||||
public class JsonUtils {
|
||||
private static final Gson gson = new GsonBuilder().setDateFormat("dd-MM-yyyy hh:mm:ss").setPrettyPrinting().create();
|
||||
private static final Gson gsonCompact = new GsonBuilder().create();
|
||||
|
||||
public static Gson getGsonFactory() {
|
||||
return gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an object to a JSON string
|
||||
*/
|
||||
public static String encode(Object object) {
|
||||
return gson.toJson(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an object to a JSON string
|
||||
* @param object
|
||||
* @param compact
|
||||
* @return
|
||||
*/
|
||||
public static String encode(Object object, boolean compact) {
|
||||
return compact ? gsonCompact.toJson(object) : gson.toJson(object);
|
||||
}
|
||||
|
||||
public static JsonElement encodeToElement(Object object) {
|
||||
return gson.toJsonTree(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely JSON decodes a given string.
|
||||
* @param jsonData The JSON-encoded data.
|
||||
* @return JSON decoded data, or null if an exception occurred.
|
||||
*/
|
||||
public static <T> T decode(String jsonData, Class<T> classType) {
|
||||
if (jsonData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return gson.fromJson(jsonData, classType);
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> decodeList(String jsonData, Class<T> classType) {
|
||||
if (jsonData == null) return null;
|
||||
try {
|
||||
return gson.fromJson(jsonData, TypeToken.getParameterized(List.class, classType).getType());
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Set<T> decodeSet(String jsonData, Class<T> classType) {
|
||||
if (jsonData == null) return null;
|
||||
try {
|
||||
return gson.fromJson(jsonData, TypeToken.getParameterized(Set.class, classType).getType());
|
||||
} catch (Exception ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(InputStreamReader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, classType);
|
||||
}
|
||||
|
||||
public static <T> T loadToClass(File file, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) {
|
||||
return loadToClass(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(InputStreamReader fileReader, Class<T> classType) throws IOException {
|
||||
return gson.fromJson(fileReader, TypeToken.getParameterized(List.class, classType).getType());
|
||||
}
|
||||
|
||||
public static <T> List<T> loadToList(String filename, Class<T> classType) throws IOException {
|
||||
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToList(fileReader, classType);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(InputStreamReader fileReader, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
return gson.fromJson(fileReader, TypeToken.getParameterized(Map.class, keyType, valueType).getType());
|
||||
}
|
||||
|
||||
public static <T1, T2> Map<T1, T2> loadToMap(String filename, Class<T1> keyType, Class<T2> valueType) throws IOException {
|
||||
try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(filename)), StandardCharsets.UTF_8)) {
|
||||
return loadToMap(fileReader, keyType, valueType);
|
||||
} catch (FileNotFoundException ignored) {
|
||||
Nebula.getLogger().error("File not found: {}.", filename);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/main/java/emu/nebula/util/Snowflake.java
Normal file
24
src/main/java/emu/nebula/util/Snowflake.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
public class Snowflake {
|
||||
private static final long EPOCH = 1735689600000L; // Wednesday, January 1, 2025 12:00:00 AM (GMT)
|
||||
private static int cachedTimestamp;
|
||||
private static byte sequence;
|
||||
|
||||
public synchronized static int newUid() {
|
||||
int timestamp = (int) ((System.currentTimeMillis() - EPOCH) / 1000);
|
||||
|
||||
if (cachedTimestamp != timestamp) {
|
||||
sequence = 0;
|
||||
cachedTimestamp = timestamp;
|
||||
} else {
|
||||
sequence++;
|
||||
}
|
||||
|
||||
return (cachedTimestamp << 4) + sequence;
|
||||
}
|
||||
|
||||
public synchronized static int toTimestamp(int snowflake) {
|
||||
return (snowflake >> 4) + (int) (EPOCH / 1000);
|
||||
}
|
||||
}
|
||||
265
src/main/java/emu/nebula/util/Utils.java
Normal file
265
src/main/java/emu/nebula/util/Utils.java
Normal file
@@ -0,0 +1,265 @@
|
||||
package emu.nebula.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntList;
|
||||
|
||||
public class Utils {
|
||||
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static final Object EMPTY_OBJECT = new Object();
|
||||
public static final int[] EMPTY_INT_ARRAY = new int[0];
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
|
||||
public static String bytesToHex(byte[] bytes) {
|
||||
return bytesToHex(bytes, 0);
|
||||
}
|
||||
|
||||
public static String bytesToHex(byte[] bytes, int offset) {
|
||||
if (bytes == null || (bytes.length - offset) <= 0) return "";
|
||||
char[] hexChars = new char[(bytes.length - offset) * 2];
|
||||
for (int j = offset; j < bytes.length; j++) {
|
||||
int v = bytes[j] & 0xFF;
|
||||
int h = j - offset;
|
||||
hexChars[h * 2] = HEX_ARRAY[v >>> 4];
|
||||
hexChars[h * 2 + 1] = HEX_ARRAY[v & 0x0F];
|
||||
}
|
||||
return new String(hexChars);
|
||||
}
|
||||
|
||||
public static byte[] hexToBytes(String s) {
|
||||
int len = s.length();
|
||||
byte[] data = new byte[len / 2];
|
||||
for (int i = 0; i < len; i += 2) {
|
||||
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
|
||||
+ Character.digit(s.charAt(i+1), 16));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public static String capitalize(String s) {
|
||||
StringBuilder sb = new StringBuilder(s);
|
||||
sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String lowerCaseFirstChar(String s) {
|
||||
StringBuilder sb = new StringBuilder(s);
|
||||
sb.setCharAt(0, Character.toLowerCase(sb.charAt(0)));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string with the path to a file.
|
||||
* @param path The path to the file.
|
||||
* @return A path using the operating system's file separator.
|
||||
*/
|
||||
public static String toFilePath(String path) {
|
||||
return path.replace("/", File.separator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a file exists on the file system.
|
||||
* @param path The path to the file.
|
||||
* @return True if the file exists, false otherwise.
|
||||
*/
|
||||
public static boolean fileExists(String path) {
|
||||
return new File(path).exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a folder on the file system.
|
||||
* @param path The path to the folder.
|
||||
* @return True if the folder was created, false otherwise.
|
||||
*/
|
||||
public static boolean createFolder(String path) {
|
||||
return new File(path).mkdirs();
|
||||
}
|
||||
|
||||
public static long getCurrentSeconds() {
|
||||
return Math.floorDiv(System.currentTimeMillis(), 1000);
|
||||
}
|
||||
|
||||
public static int getMinPromotionForLevel(int level) {
|
||||
return Math.max(Math.min((int) ((level - 11) / 10D), 6), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string argument as a signed decimal integer. Returns a 0 if the string argument is not an integer.
|
||||
*/
|
||||
public static int parseSafeInt(String s) {
|
||||
if (s == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
|
||||
try {
|
||||
i = Integer.parseInt(s);
|
||||
} catch (Exception e) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the string argument as a signed decimal long. Returns a 0 if the string argument is not a long.
|
||||
*/
|
||||
public static long parseSafeLong(String s) {
|
||||
if (s == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
long i = 0;
|
||||
|
||||
try {
|
||||
i = Long.parseLong(s);
|
||||
} catch (Exception e) {
|
||||
i = 0;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add 2 integers without overflowing
|
||||
*/
|
||||
public static int safeAdd(int a, int b) {
|
||||
return safeAdd(a, b, Integer.MAX_VALUE, 0);
|
||||
}
|
||||
|
||||
public static int safeAdd(int a, int b, long max, long min) {
|
||||
long sum = (long) a + (long) b;
|
||||
|
||||
if (sum > max) {
|
||||
return (int) max;
|
||||
} else if (sum < min) {
|
||||
return (int) min;
|
||||
}
|
||||
|
||||
return (int) sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract 2 integers without overflowing
|
||||
*/
|
||||
public static int safeSubtract(int a, int b) {
|
||||
return safeSubtract(a, b, Integer.MAX_VALUE, Integer.MIN_VALUE);
|
||||
}
|
||||
|
||||
public static int safeSubtract(int a, int b, long max, long min) {
|
||||
long sum = (long) a - (long) b;
|
||||
|
||||
if (sum > max) {
|
||||
return (int) max;
|
||||
} else if (sum < min) {
|
||||
return (int) min;
|
||||
}
|
||||
|
||||
return (int) sum;
|
||||
}
|
||||
|
||||
public static double generateRandomDouble() {
|
||||
return ThreadLocalRandom.current().nextDouble();
|
||||
}
|
||||
|
||||
public static int randomRange(int min, int max) {
|
||||
return ThreadLocalRandom.current().nextInt(min, max + 1);
|
||||
}
|
||||
|
||||
public static int randomElement(int[] array) {
|
||||
return array[ThreadLocalRandom.current().nextInt(0, array.length)];
|
||||
}
|
||||
|
||||
public static <T> T randomElement(List<T> list) {
|
||||
return list.get(ThreadLocalRandom.current().nextInt(0, list.size()));
|
||||
}
|
||||
|
||||
public static int randomElement(IntList list) {
|
||||
return list.getInt(ThreadLocalRandom.current().nextInt(0, list.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an integer array contains a value
|
||||
* @param array
|
||||
* @param value The value to check for
|
||||
*/
|
||||
public static boolean arrayContains(int[] array, int value) {
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (array[i] == value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean arrayContains(Integer[] array, int value) {
|
||||
for (Integer element : array) {
|
||||
if (element != null && element.equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean arrayContains(List<Integer> list, int value) {
|
||||
for (Integer element : list) {
|
||||
if (element.equals(value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int[] convertListToIntArray(List<Integer> list) {
|
||||
// Create an int array with the same size as the list
|
||||
int[] intArray = new int[list.size()];
|
||||
|
||||
// Iterate over the list and populate the int array
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
intArray[i] = list.get(i);
|
||||
}
|
||||
|
||||
return intArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 encodes a given byte array.
|
||||
* @param toEncode An array of bytes.
|
||||
* @return A base64 encoded string.
|
||||
*/
|
||||
public static String base64Encode(byte[] toEncode) {
|
||||
return Base64.getEncoder().encodeToString(toEncode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 decodes a given string.
|
||||
* @param toDecode A base64 encoded string.
|
||||
* @return An array of bytes.
|
||||
*/
|
||||
public static byte[] base64Decode(String toDecode) {
|
||||
return Base64.getDecoder().decode(toDecode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a port is open on a given host.
|
||||
*
|
||||
* @param host The host to check.
|
||||
* @param port The port to check.
|
||||
* @return True if the port is open, false otherwise.
|
||||
*/
|
||||
public static boolean isPortOpen(String host, int port) {
|
||||
try (var serverSocket = new ServerSocket()) {
|
||||
serverSocket.setReuseAddress(false);
|
||||
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(host), port), 1);
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user