diff --git a/SentinelAuth/build.gradle b/SentinelAuth/build.gradle new file mode 100644 index 0000000..35dbc08 --- /dev/null +++ b/SentinelAuth/build.gradle @@ -0,0 +1,32 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.4.4' + id 'io.spring.dependency-management' version '1.1.7' +} + +group = 'me.trouper.sentinel' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.named('test') { + useJUnitPlatform() +} + +bootJar { + mainClass = 'me.trouper.sentinel.auth.SentinelAuth' // Replace with your actual main class +} \ No newline at end of file diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/AuthController.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/AuthController.java new file mode 100644 index 0000000..3efe021 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/AuthController.java @@ -0,0 +1,44 @@ +package me.trouper.sentinel.auth; + +import me.trouper.sentinel.auth.data.AuthRequest; +import me.trouper.sentinel.auth.data.AuthResponse; +import me.trouper.sentinel.auth.services.JarService; +import me.trouper.sentinel.auth.services.LicenseService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +@RestController +public class AuthController { + private final LicenseService licenseService; + private final JarService jarService; + + public AuthController(LicenseService ls, JarService js) { + this.licenseService = ls; + this.jarService = js; + } + + @PostMapping("/authenticate") + public ResponseEntity> authenticate(@RequestBody AuthRequest request) { + for (String key : licenseService.getLicenseKeys()) { + String serverCode = LicenseService.generateAuthCode(key); + if (serverCode.equals(request.authCode())) { + Map response = new HashMap<>(); + response.put("decryptionKey", jarService.getDecryptionKey()); + + if (!jarService.getJarHash().equals(request.clientHash())) { + response.put("jarData", + Base64.getEncoder().encodeToString(jarService.getEncryptedJar())); + } + + return ResponseEntity.ok(response); + } + } + return ResponseEntity.status(401).build(); + } +} \ No newline at end of file diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/SentinelAuth.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/SentinelAuth.java new file mode 100644 index 0000000..b7666bf --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/SentinelAuth.java @@ -0,0 +1,13 @@ +package me.trouper.sentinel.auth; + +import me.trouper.sentinel.auth.services.JarService; +import me.trouper.sentinel.auth.services.LicenseService; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SentinelAuth { + public static void main(String[] args) { + SpringApplication.run(SentinelAuth.class, args); + } +} diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthRequest.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthRequest.java new file mode 100644 index 0000000..61f6991 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthRequest.java @@ -0,0 +1,5 @@ +package me.trouper.sentinel.auth.data; + +public record AuthRequest(String authCode, String clientHash) { + +} diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthResponse.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthResponse.java new file mode 100644 index 0000000..eaeb360 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/data/AuthResponse.java @@ -0,0 +1,5 @@ +package me.trouper.sentinel.auth.data; + +public record AuthResponse(byte[] jarData, String decryptionKey) { + +} diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/JarService.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/JarService.java new file mode 100644 index 0000000..c1f9ad0 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/JarService.java @@ -0,0 +1,59 @@ +package me.trouper.sentinel.auth.services; + +import jakarta.annotation.PostConstruct; +import me.trouper.sentinel.auth.utils.DigestUtils; +import org.springframework.stereotype.Service; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Base64; + +@Service +public class JarService { + private byte[] unencryptedJar; + private byte[] encryptedJar; // Store encrypted JAR + private String jarHash; + private String decryptionKey; // Will be initialized + private final Path unencryptedJarPath = Path.of("storage/SentinelPlugin-1.0-SNAPSHOT.jar"); + + @PostConstruct + public void init() throws IOException, GeneralSecurityException { + if (Files.exists(unencryptedJarPath)) { + this.unencryptedJar = Files.readAllBytes(unencryptedJarPath); + + // Set encryption key and decrypt key + String encryptionKey = "AES_KEY_12345678"; + this.decryptionKey = encryptionKey; // Initialize decryptionKey + + // Encrypt once during startup + this.encryptedJar = encryptJar(unencryptedJar, encryptionKey); + this.jarHash = DigestUtils.getSHA256(encryptedJar); + } else { + throw new FileNotFoundException("Unencrypted plugin JAR not found at: " + unencryptedJarPath); + } + } + + public byte[] getEncryptedJar() { + return encryptedJar; + } + + private byte[] encryptJar(byte[] input, String encryptionKey) throws GeneralSecurityException { + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), "AES"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + return cipher.doFinal(input); + } + + public String getJarHash() { return jarHash; } + public String getDecryptionKey() { return decryptionKey; } +} \ No newline at end of file diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/LicenseService.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/LicenseService.java new file mode 100644 index 0000000..7137363 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/services/LicenseService.java @@ -0,0 +1,41 @@ +package me.trouper.sentinel.auth.services; + +import org.springframework.stereotype.Service; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; + +@Service +public class LicenseService { + private final List licenseKeys = List.of("SECRET_KEY_12345"); // In real use, load from secure config + + public List getLicenseKeys() { + return licenseKeys; + } + + public static String generateAuthCode(String licenseKey) { + try { + byte[] key = licenseKey.getBytes(StandardCharsets.UTF_8); + long counter = Instant.now().getEpochSecond() / 10; + byte[] counterBytes = ByteBuffer.allocate(8).putLong(counter).array(); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(key, "HmacSHA1")); + byte[] hmac = mac.doFinal(counterBytes); + + int offset = hmac[hmac.length - 1] & 0xF; + int binary = ((hmac[offset] & 0x7F) << 24) + | ((hmac[offset + 1] & 0xFF) << 16) + | ((hmac[offset + 2] & 0xFF) << 8) + | (hmac[offset + 3] & 0xFF); + + return String.format("%06d", binary % 1_000_000); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/SentinelAuth/src/main/java/me/trouper/sentinel/auth/utils/DigestUtils.java b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/utils/DigestUtils.java new file mode 100644 index 0000000..73d4ee4 --- /dev/null +++ b/SentinelAuth/src/main/java/me/trouper/sentinel/auth/utils/DigestUtils.java @@ -0,0 +1,29 @@ +package me.trouper.sentinel.auth.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class DigestUtils { + public static String getSHA256(byte[] bytes) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] digestBytes = md.digest(bytes); + return bytesToHex(digestBytes); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = String.format("%02x", b); + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/SentinelAuth/src/main/resources/application.properties b/SentinelAuth/src/main/resources/application.properties new file mode 100644 index 0000000..3e36480 --- /dev/null +++ b/SentinelAuth/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=SentinelAuth \ No newline at end of file diff --git a/SentinelPlugin/build.gradle b/SentinelPlugin/build.gradle new file mode 100644 index 0000000..b01be20 --- /dev/null +++ b/SentinelPlugin/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'java' +} + +group = 'me.trouper.sentinel' +version = '1.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } +} + +dependencies { + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") +} + +def targetJavaVersion = 21 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release.set(targetJavaVersion) + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('paper-plugin.yml') { + expand props + } +} diff --git a/SentinelPlugin/gradle.properties b/SentinelPlugin/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/SentinelPlugin/gradle/wrapper/gradle-wrapper.properties b/SentinelPlugin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..0d8ab51 --- /dev/null +++ b/SentinelPlugin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip diff --git a/SentinelPlugin/settings.gradle b/SentinelPlugin/settings.gradle new file mode 100644 index 0000000..521630e --- /dev/null +++ b/SentinelPlugin/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'SentinelPlugin' diff --git a/SentinelPlugin/src/main/java/me/trouper/sentinel/plugin/SentinelPlugin.java b/SentinelPlugin/src/main/java/me/trouper/sentinel/plugin/SentinelPlugin.java new file mode 100644 index 0000000..0c1adf5 --- /dev/null +++ b/SentinelPlugin/src/main/java/me/trouper/sentinel/plugin/SentinelPlugin.java @@ -0,0 +1,25 @@ +package me.trouper.sentinel.plugin; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.plugin.java.JavaPlugin; + +public final class SentinelPlugin extends JavaPlugin { + + public static void initialize(JavaPlugin loaderPlugin) { + loaderPlugin.getLogger().info("Hello from dynamically loaded Plugin!"); + Bukkit.getScheduler().runTask(loaderPlugin, () -> { + PluginCommand command = Bukkit.getPluginCommand("demo"); + if (command != null) { + command.setExecutor((sender, cmd, label, args) -> { + sender.sendMessage("Hello from dynamically loaded plugin!"); + return true; + }); + } + }); + } +} diff --git a/build.gradle b/build.gradle index 2cd413b..b01be20 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java' } -group = 'me.trouper' +group = 'me.trouper.sentinel' version = '1.0-SNAPSHOT' repositories { diff --git a/settings.gradle b/settings.gradle index 8ce721c..85f52dd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ rootProject.name = 'SentinelLoader' +include 'SentinelAuth' +include 'SentinelPlugin' diff --git a/src/main/java/me/trouper/sentinel/loader/SentinelLoader.java b/src/main/java/me/trouper/sentinel/loader/SentinelLoader.java new file mode 100644 index 0000000..b2934a2 --- /dev/null +++ b/src/main/java/me/trouper/sentinel/loader/SentinelLoader.java @@ -0,0 +1,221 @@ +package me.trouper.sentinel.loader; + +import me.trouper.sentinel.loader.data.AuthResponse; +import me.trouper.sentinel.loader.utils.AuthUtils; +import me.trouper.sentinel.loader.utils.DigestUtils; +import org.bukkit.plugin.java.JavaPlugin; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.logging.Level; + +public final class SentinelLoader extends JavaPlugin { + + private String licenseKey = "SECRET_KEY_12345"; + private Path encryptedJarPath; + private static SentinelLoader instance; + + @Override + public void onEnable() { + instance = this; + + try { + // Ensure data folder exists + if (!getDataFolder().exists() && !getDataFolder().mkdirs()) { + getLogger().severe("Failed to create plugin data folder!"); + return; + } + + encryptedJarPath = getDataFolder().toPath().resolve("encrypted-plugin.jar"); + + // Get client hash safely + String clientHash = ""; + if (Files.exists(encryptedJarPath)) { + try { + clientHash = DigestUtils.getSHA256(encryptedJarPath); + } catch (IOException e) { + getLogger().log(Level.WARNING, "Corrupted JAR file detected, will re-download", e); + try { + Files.deleteIfExists(encryptedJarPath); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, "Failed to delete corrupted JAR file", ex); + } + } + } + + String authCode = AuthUtils.generateAuthCode(licenseKey); + getLogger().info("Sending auth Auth Code: " + authCode); + AuthResponse response = sendAuthRequest(authCode, clientHash); + + if (response == null) { + getLogger().severe("Authentication failed"); + return; + } + + // Handle JAR download + if (response.jarData() != null) { + try { + Files.createDirectories(encryptedJarPath.getParent()); + Files.write(encryptedJarPath, response.jarData()); + getLogger().info("Successfully updated plugin JAR"); + } catch (IOException e) { + getLogger().log(Level.SEVERE, "Failed to save encrypted JAR", e); + return; + } + } + + // Load the plugin + try { + byte[] decryptedJar = decrypt( + Files.readAllBytes(encryptedJarPath), + response.decryptionKey() + ); + getLogger().info("Trying to load plugin..."); + loadPlugin(decryptedJar); + } catch (IOException | GeneralSecurityException e) { + getLogger().log(Level.SEVERE, "Failed to decrypt or load plugin", e); + try { + Files.deleteIfExists(encryptedJarPath); + } catch (IOException ex) { + getLogger().log(Level.SEVERE, "Failed to cleanup invalid JAR", ex); + } + } + } catch (Exception e) { + getLogger().log(Level.SEVERE, "Critical error during initialization", e); + } + } + + + private AuthResponse sendAuthRequest(String authCode, String clientHash) { + try { + URL url = new URL("http://localhost:8080/authenticate"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setDoOutput(true); + + // Write request body + String requestBody = String.format( + "{\"authCode\":\"%s\",\"clientHash\":\"%s\"}", + authCode, + clientHash + ); + try (OutputStream os = conn.getOutputStream()) { + byte[] input = requestBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + + // Read response + if (conn.getResponseCode() != 200) { + SentinelLoader.getInstance().getLogger().warning("Auth server responded with code: " + conn.getResponseCode()); + return null; + } + + try (BufferedReader br = new BufferedReader( + new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { + StringBuilder response = new StringBuilder(); + String responseLine; + while ((responseLine = br.readLine()) != null) { + response.append(responseLine.trim()); + } + + // Simple JSON parsing (for production use a proper library like Gson) + String json = response.toString(); + boolean hasJar = json.contains("\"jarData\""); + String decryptionKey = extractJsonValue(json, "decryptionKey"); + + if (hasJar) { + String jarDataBase64 = extractJsonValue(json, "jarData"); + byte[] jarData = Base64.getDecoder().decode(jarDataBase64); + return new AuthResponse(jarData, decryptionKey); + } else { + return new AuthResponse(null, decryptionKey); + } + } + } catch (Exception e) { + SentinelLoader.getInstance().getLogger().log(Level.SEVERE, "Failed to authenticate with server", e); + return null; + } + } + + // Helper method to extract values from simple JSON + private String extractJsonValue(String json, String key) { + int start = json.indexOf("\"" + key + "\":") + key.length() + 3; + int end = json.indexOf("\"", start + 1); + if (end == -1) end = json.indexOf(",", start); + if (end == -1) end = json.indexOf("}", start); + return json.substring(start, end).replace("\"", ""); + } + + private byte[] decrypt(byte[] data, String key) throws GeneralSecurityException { + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(), "AES")); + return cipher.doFinal(data); + } + + private void loadPlugin(byte[] decryptedJar) throws Exception { + new InMemoryClassLoader(getClassLoader(), decryptedJar) + .loadClass("me.trouper.sentinel.plugin.SentinelPlugin") + .getMethod("initialize", JavaPlugin.class) + .invoke(null, this); + } + + private static class InMemoryClassLoader extends ClassLoader { + private final Map> classes = new HashMap<>(); + private final byte[] jarBytes; + + public InMemoryClassLoader(ClassLoader parent, byte[] jarBytes) { + super(parent); + this.jarBytes = jarBytes; + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (classes.containsKey(name)) return classes.get(name); + + try (JarInputStream jar = new JarInputStream(new ByteArrayInputStream(jarBytes))) { + JarEntry entry; + while ((entry = jar.getNextJarEntry()) != null) { + if (!entry.getName().endsWith(".class")) continue; + + String className = entry.getName() + .replace(".class", "") + .replace('/', '.'); + + if (!className.equals(name)) continue; + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = jar.read(buffer)) != -1) { + bos.write(buffer, 0, len); + } + + byte[] bytes = bos.toByteArray(); + Class clazz = defineClass(className, bytes, 0, bytes.length); + classes.put(className, clazz); + return clazz; + } + } catch (IOException e) { + throw new ClassNotFoundException("Class not found: " + name, e); + } + throw new ClassNotFoundException(); + } + } + + public static SentinelLoader getInstance() { + return instance; + } +} diff --git a/src/main/java/me/trouper/sentinel/loader/data/AuthRequest.java b/src/main/java/me/trouper/sentinel/loader/data/AuthRequest.java new file mode 100644 index 0000000..223646c --- /dev/null +++ b/src/main/java/me/trouper/sentinel/loader/data/AuthRequest.java @@ -0,0 +1,5 @@ +package me.trouper.sentinel.loader.data; + +public record AuthRequest(String authCode, String clientHash) { + +} diff --git a/src/main/java/me/trouper/sentinel/loader/data/AuthResponse.java b/src/main/java/me/trouper/sentinel/loader/data/AuthResponse.java new file mode 100644 index 0000000..bdae2de --- /dev/null +++ b/src/main/java/me/trouper/sentinel/loader/data/AuthResponse.java @@ -0,0 +1,5 @@ +package me.trouper.sentinel.loader.data; + +public record AuthResponse(byte[] jarData, String decryptionKey) { + +} diff --git a/src/main/java/me/trouper/sentinel/loader/utils/AuthUtils.java b/src/main/java/me/trouper/sentinel/loader/utils/AuthUtils.java new file mode 100644 index 0000000..168d5f4 --- /dev/null +++ b/src/main/java/me/trouper/sentinel/loader/utils/AuthUtils.java @@ -0,0 +1,43 @@ +package me.trouper.sentinel.loader.utils; + +import me.trouper.sentinel.loader.SentinelLoader; +import me.trouper.sentinel.loader.data.AuthResponse; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.Base64; +import java.util.logging.Level; + +public class AuthUtils { + + + public static String generateAuthCode(String licenseKey) { + try { + byte[] key = licenseKey.getBytes(StandardCharsets.UTF_8); + long counter = Instant.now().getEpochSecond() / 10; + byte[] counterBytes = ByteBuffer.allocate(8).putLong(counter).array(); + + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(new SecretKeySpec(key, "HmacSHA1")); + byte[] hmac = mac.doFinal(counterBytes); + + int offset = hmac[hmac.length - 1] & 0xF; + int binary = ((hmac[offset] & 0x7F) << 24) + | ((hmac[offset + 1] & 0xFF) << 16) + | ((hmac[offset + 2] & 0xFF) << 8) + | (hmac[offset + 3] & 0xFF); + + return String.format("%06d", binary % 1_000_000); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/me/trouper/sentinel/loader/utils/DigestUtils.java b/src/main/java/me/trouper/sentinel/loader/utils/DigestUtils.java new file mode 100644 index 0000000..cb6d0a1 --- /dev/null +++ b/src/main/java/me/trouper/sentinel/loader/utils/DigestUtils.java @@ -0,0 +1,25 @@ +package me.trouper.sentinel.loader.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class DigestUtils { + public static String getSHA256(Path filePath) throws IOException, NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte[] fileBytes = Files.readAllBytes(filePath); + byte[] digestBytes = md.digest(fileBytes); + return bytesToHex(digestBytes); + } + + private static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = String.format("%02x", b); + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/main/java/me/trouper/sentinelLoader/SentinelLoader.java b/src/main/java/me/trouper/sentinelLoader/SentinelLoader.java deleted file mode 100644 index 3c36e5a..0000000 --- a/src/main/java/me/trouper/sentinelLoader/SentinelLoader.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.trouper.sentinelLoader; - -import org.bukkit.plugin.java.JavaPlugin; - -public final class SentinelLoader extends JavaPlugin { - - @Override - public void onEnable() { - // Plugin startup logic - } - - @Override - public void onDisable() { - // Plugin shutdown logic - } -} diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index d83af88..4c2114b 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: SentinelLoader version: '1.0-SNAPSHOT' -main: me.trouper.sentinelLoader.SentinelLoader +main: me.trouper.sentinel.loader.SentinelLoader api-version: '1.21' prefix: SentinelLoader load: STARTUP