From 02be6b8e73b5c682f272c044a4af2cd6d77beb9c Mon Sep 17 00:00:00 2001 From: LostLuma Date: Fri, 9 Aug 2024 10:40:30 +0200 Subject: [PATCH 1/2] Fix gitignore --- .gitignore | 34 +++++++++------------------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 4e99b01..c247ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,39 +3,23 @@ icon.png quilt.mod.json +# Cargo +target/ + # Gradle .gradle/ build/ -out/ -classes/ - -# Quilt Loom -remappedSrc/ -run/ - -# Eclipse -*.launch # IntelliJ Idea .idea/ -*.iml -*.ipr -*.iws -# Fleet -.fleet/ +# macOS +*.DS_Store +/.apt_generated/ + +# Quilt Loom +run/ # Visual Studio Code .settings/ .vscode/ -bin/ -.classpath -.project - -# Eclipse JDT LS -.factorypath -workspace/ - -# macOS -*.DS_Store -/.apt_generated/ From 809b17f09875473cadd4a196b5d2fcb029c8a03b Mon Sep 17 00:00:00 2001 From: LostLuma Date: Fri, 9 Aug 2024 11:37:50 +0200 Subject: [PATCH 2/2] Improve native dev workflow, make natives work in prod --- projects/kitten-heart/build.gradle.kts | 1 - .../pixaurora/kitten_heart/impl/KitTunes.java | 4 +- .../kitten_heart/impl/network/Encryption.java | 18 +-- projects/kitten-thoughts/build.gradle.kts | 24 ++++ ...kitten_thoughts_scrobbler_ScrobblerSetup.h | 21 --- .../kitten_thoughts/KittenThoughts.java | 11 -- .../kitten_thoughts/impl/Constants.java | 14 ++ .../kitten_thoughts/impl/KittenThoughts.java | 9 ++ .../impl/error/LibraryLoadError.java | 11 ++ .../{ => impl}/scrobbler/ScrobblerSetup.java | 2 +- .../kitten_thoughts/impl/util/CryptoUtil.java | 40 ++++++ .../kitten_thoughts/impl/util/HttpUtil.java | 64 +++++++++ .../kitten_thoughts/impl/util/NativeUtil.java | 130 ++++++++++++++++++ .../kitten_thoughts.natives.properties | 5 + projects/kitten-thoughts/src/main/rust/lib.rs | 2 +- 15 files changed, 304 insertions(+), 52 deletions(-) delete mode 100644 projects/kitten-thoughts/src/main/headers/net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup.h delete mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/KittenThoughts.java create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/Constants.java create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/KittenThoughts.java create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/error/LibraryLoadError.java rename projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/{ => impl}/scrobbler/ScrobblerSetup.java (61%) create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/CryptoUtil.java create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/HttpUtil.java create mode 100644 projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/NativeUtil.java create mode 100644 projects/kitten-thoughts/src/main/resources/kitten_thoughts.natives.properties diff --git a/projects/kitten-heart/build.gradle.kts b/projects/kitten-heart/build.gradle.kts index a355f08..88d96e3 100644 --- a/projects/kitten-heart/build.gradle.kts +++ b/projects/kitten-heart/build.gradle.kts @@ -13,7 +13,6 @@ mod { dependencies { required("quilt_loader").versionAbove(libs.versions.quilt.loader.get()) - required("kit_tunes_api") required("kitten_square") required("kitten_sounds") diff --git a/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/KitTunes.java b/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/KitTunes.java index 84c9c1e..67c417e 100644 --- a/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/KitTunes.java +++ b/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/KitTunes.java @@ -18,8 +18,8 @@ import net.pixaurora.kitten_heart.impl.resource.ResourcePathImpl; import net.pixaurora.kitten_heart.impl.service.MinecraftUICompat; import net.pixaurora.kitten_heart.impl.service.ServiceLoading; -import net.pixaurora.kitten_thoughts.KittenThoughts; -import net.pixaurora.kitten_thoughts.scrobbler.ScrobblerSetup; +import net.pixaurora.kitten_thoughts.impl.KittenThoughts; +import net.pixaurora.kitten_thoughts.impl.scrobbler.ScrobblerSetup; public class KitTunes { public static final Logger LOGGER = LoggerFactory.getLogger(Constants.MOD_ID); diff --git a/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/network/Encryption.java b/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/network/Encryption.java index 4ab35d3..2084ae2 100644 --- a/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/network/Encryption.java +++ b/projects/kitten-heart/src/main/java/net/pixaurora/kitten_heart/impl/network/Encryption.java @@ -1,12 +1,14 @@ package net.pixaurora.kitten_heart.impl.network; +import net.pixaurora.kitten_thoughts.impl.util.CryptoUtil; + import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Encryption { public static String signMd5(String input) { - return bytesToHex(getMd5Digest(input)); + return CryptoUtil.toHex(getMd5Digest(input)); } public static byte[] getMd5Digest(String input) { @@ -21,18 +23,4 @@ public static byte[] getMd5Digest(String input) { return digester.digest(); } - - public static String bytesToHex(byte[] bytes) { - StringBuilder hexString = new StringBuilder(bytes.length * 2); - - for (byte rawByte : bytes) { - int processedByte = Byte.toUnsignedInt(rawByte); - - // Example byte: 11 01 10 00 - hexString.append(Integer.toHexString(processedByte >> 0b100)); // Grabs the part that is 11 01 - hexString.append(Integer.toHexString(processedByte & 0b1111)); // Grabs the part that is 10 00 - } - - return hexString.toString(); - } } diff --git a/projects/kitten-thoughts/build.gradle.kts b/projects/kitten-thoughts/build.gradle.kts index d5911c4..cd768c5 100644 --- a/projects/kitten-thoughts/build.gradle.kts +++ b/projects/kitten-thoughts/build.gradle.kts @@ -1,3 +1,5 @@ +import java.io.ByteArrayOutputStream + plugins { id("kit_tunes.java.08") id("kit_tunes.base") @@ -13,4 +15,26 @@ mod { dependencies { implementation(libs.annotations) + implementation(libs.quilt.loader) +} + +tasks.register("buildDevNatives") { + inputs.file(file("Cargo.toml")) + inputs.file(file("Cargo.lock")) + + inputs.dir(file("src/main/rust")) + outputs.dir(file("target/debug")) + + doLast { + val stream = ByteArrayOutputStream() + + exec { + errorOutput = stream + standardOutput = stream + + commandLine("cargo", "build") + } + + logger.info(stream.toString()) + } } diff --git a/projects/kitten-thoughts/src/main/headers/net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup.h b/projects/kitten-thoughts/src/main/headers/net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup.h deleted file mode 100644 index d53d19d..0000000 --- a/projects/kitten-thoughts/src/main/headers/net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup.h +++ /dev/null @@ -1,21 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include -/* Header for class net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup */ - -#ifndef _Included_net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup -#define _Included_net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup -#ifdef __cplusplus -extern "C" { -#endif -/* - * Class: net_pixaurora_kitten_thoughts_scrobbler_ScrobblerSetup - * Method: hello - * Signature: (Ljava/lang/String;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_net_pixaurora_kitten_1thoughts_scrobbler_ScrobblerSetup_hello - (JNIEnv *, jclass, jstring); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/KittenThoughts.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/KittenThoughts.java deleted file mode 100644 index 347ff7c..0000000 --- a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/KittenThoughts.java +++ /dev/null @@ -1,11 +0,0 @@ -package net.pixaurora.kitten_thoughts; - -import java.nio.file.Path; -import java.nio.file.Paths; - -public class KittenThoughts { - public static void init() { - Path libraryPath = Paths.get("libkitten_thoughts.so").toAbsolutePath(); - System.load(libraryPath.toString()); - } -} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/Constants.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/Constants.java new file mode 100644 index 0000000..5639252 --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/Constants.java @@ -0,0 +1,14 @@ +package net.pixaurora.kitten_thoughts.impl; + +import org.quiltmc.loader.api.QuiltLoader; + +import java.nio.file.Path; + +public class Constants { + public static final String MOD_ID = "kitten_thoughts"; + + public static final String NATIVES_VERSION = "0.1.0"; + public static final String NATIVES_DIRECTORY_PROPERTY = "kitten_thoughts.natives_path"; + + public static final Path NATIVES_CACHE_DIR = QuiltLoader.getCacheDir().resolve(MOD_ID); +} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/KittenThoughts.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/KittenThoughts.java new file mode 100644 index 0000000..f04d668 --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/KittenThoughts.java @@ -0,0 +1,9 @@ +package net.pixaurora.kitten_thoughts.impl; + +import net.pixaurora.kitten_thoughts.impl.util.NativeUtil; + +public class KittenThoughts { + public static void init() { + NativeUtil.load(); + } +} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/error/LibraryLoadError.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/error/LibraryLoadError.java new file mode 100644 index 0000000..fead951 --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/error/LibraryLoadError.java @@ -0,0 +1,11 @@ +package net.pixaurora.kitten_thoughts.impl.error; + +public class LibraryLoadError extends Error { + public LibraryLoadError(String message) { + super(message); + } + + public LibraryLoadError(Throwable exception) { + super(exception); + } +} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/scrobbler/ScrobblerSetup.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/scrobbler/ScrobblerSetup.java similarity index 61% rename from projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/scrobbler/ScrobblerSetup.java rename to projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/scrobbler/ScrobblerSetup.java index 0f5cf67..b7502ad 100644 --- a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/scrobbler/ScrobblerSetup.java +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/scrobbler/ScrobblerSetup.java @@ -1,4 +1,4 @@ -package net.pixaurora.kitten_thoughts.scrobbler; +package net.pixaurora.kitten_thoughts.impl.scrobbler; public class ScrobblerSetup { public static native String hello(String input); diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/CryptoUtil.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/CryptoUtil.java new file mode 100644 index 0000000..337f1af --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/CryptoUtil.java @@ -0,0 +1,40 @@ +package net.pixaurora.kitten_thoughts.impl.util; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class CryptoUtil { + public static String sha512(Path path) throws IOException { + return sha512(Files.readAllBytes(path)); + } + + public static String sha512(byte[] data) { + MessageDigest digest; + + try { + digest = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("No SHA512 algorithm found.", e); + } + + digest.update(data); + return toHex(digest.digest()); + } + + public static String toHex(byte[] data) { + StringBuilder builder = new StringBuilder(data.length * 2); + + for (byte value : data) { + int number = Byte.toUnsignedInt(value); + + builder.append(Integer.toHexString(number >> 0b100)); // Former four bits + builder.append(Integer.toHexString(number & 0b1111)); // Latter four bits + } + + return builder.toString(); + } +} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/HttpUtil.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/HttpUtil.java new file mode 100644 index 0000000..4224ce2 --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/HttpUtil.java @@ -0,0 +1,64 @@ +package net.pixaurora.kitten_thoughts.impl.util; + +import net.pixaurora.kitten_thoughts.impl.Constants; +import org.quiltmc.loader.api.ModMetadata; +import org.quiltmc.loader.api.QuiltLoader; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.temporal.ChronoUnit; + +public class HttpUtil { + private static final Duration TIMEOUT = Duration.of(2, ChronoUnit.MINUTES); + + public static void download(URL url, Path into) throws IOException { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + + connection.setReadTimeout((int) TIMEOUT.toMillis()); + connection.setRequestProperty("User-Agent", buildUserAgent()); + + connection.connect(); + int status = connection.getResponseCode(); + + if (status != 200) { + throw new IOException("Library download error: " + status); + } + + int size; + String length = connection.getHeaderField("Content-Length"); + + try { + size = Integer.parseInt(length); + } catch (NumberFormatException e) { + throw new IOException("Received invalid Content-Length header!"); + } + + try (InputStream stream = connection.getInputStream()) { + int input; + int index = 0; + + byte[] data = new byte[size]; + + while ((input = stream.read()) != -1) { + data[index] = (byte) input; + index ++; + } + + Files.write(into, data); + } + } + + private static String buildUserAgent() { + ModMetadata metadata = QuiltLoader.getModContainer(Constants.MOD_ID).get().metadata(); + + String version = metadata.version().raw(); + String homepage = metadata.getContactInfo("homepage"); + + return String.format("Kit Tunes/%s (+%s)", version, homepage); + } +} diff --git a/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/NativeUtil.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/NativeUtil.java new file mode 100644 index 0000000..3f564bd --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/util/NativeUtil.java @@ -0,0 +1,130 @@ +package net.pixaurora.kitten_thoughts.impl.util; + +import net.pixaurora.kitten_thoughts.impl.Constants; +import net.pixaurora.kitten_thoughts.impl.error.LibraryLoadError; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; + +public class NativeUtil { + private static boolean isLoaded = false; + + private static final String METADATA = "/kitten_thoughts.natives.properties"; + private static final String BASE_URL = "https://files.lostluma.net/kitten-thoughts-jni/" + Constants.NATIVES_VERSION + "/"; + + public static void load() throws LibraryLoadError { + if (isLoaded) { + return; + } + + try { + load0(); + isLoaded = true; + } catch (IOException e) { + throw new LibraryLoadError(e); + } + } + + private static void load0() throws IOException, LibraryLoadError { + String directory = System.getProperty(Constants.NATIVES_DIRECTORY_PROPERTY); + + if (directory != null) { + loadDevLibrary(directory); + } else { + loadProdLibrary(); + } + } + + private static void loadDevLibrary(String directory) throws LibraryLoadError { + String name = getDevLibraryName(); + Path path = Paths.get(directory).resolve(name); + + try { + System.load(path.toAbsolutePath().toString()); + } catch (UnsatisfiedLinkError e) { + throw new LibraryLoadError(e); + } + } + + private static String getDevLibraryName() { + String name = System.getProperty("os.name").toLowerCase(); + + if (name.contains("win")) { + return "kitten_thoughts.dll"; + } else if (name.contains("mac")) { + return "libkitten_thoughts.dylib"; + } else { + return "libkitten_thoughts.so"; + } + } + + private static void loadProdLibrary() throws IOException, LibraryLoadError { + Properties properties = new Properties(); + + try (InputStream stream = NativeUtil.class.getResourceAsStream(METADATA)) { + if (stream == null) { + throw new LibraryLoadError("Failed to read native library info."); + } + + properties.load(stream); + } + + String base = getArch() + "." + getName(); + + if (properties.getProperty(base + ".name") == null) { + throw new LibraryLoadError("No library found for " + getArch() + " " + getName()); + } + + String name = properties.getProperty(base + ".name"); + String hash = properties.getProperty(base + ".hash"); + + Path path = Constants.NATIVES_CACHE_DIR.resolve(name); + + if (!isLibraryValid(path, hash)) { + Files.createDirectories(path.getParent()); + HttpUtil.download(new URL(BASE_URL + name), path); + + if (!isLibraryValid(path, hash)) { + throw new LibraryLoadError("Native library could not be validated."); + } + } + + try { + System.load(path.toAbsolutePath().toString()); + } catch (UnsatisfiedLinkError e) { + throw new LibraryLoadError(e); + } + } + + private static String getArch() { + String arch = System.getProperty("os.arch"); + + // MacOS + if (arch.equals("x86_64")) { + arch = "amd64"; + } + + return arch; + } + + private static String getName() { + String name = System.getProperty("os.name").toLowerCase(); + + if (name.contains("win")) { + return "windows"; + } else if (name.contains("mac")) { + return "macos"; + } else { + return "linux"; + } + } + + private static boolean isLibraryValid(Path path, String hash) throws IOException { + return Files.exists(path) && hash.equals(CryptoUtil.sha512(path)); + } +} diff --git a/projects/kitten-thoughts/src/main/resources/kitten_thoughts.natives.properties b/projects/kitten-thoughts/src/main/resources/kitten_thoughts.natives.properties new file mode 100644 index 0000000..824b6ce --- /dev/null +++ b/projects/kitten-thoughts/src/main/resources/kitten_thoughts.natives.properties @@ -0,0 +1,5 @@ +amd64.windows.name=libkitten-thoughts-jni-0.1.0+amd64.windows.dll +amd64.windows.hash=2ac1f066031fdd7e994e9d02f2671f07936ac5e6e9d4a9ebfb31a68e204ab97c5420419ba22197e59663c342ce35c53463e5cadd2f8f13593eccb8ca06a68b45 + +amd64.linux.name=libkitten-thoughts-jni-0.1.0+amd64.linux.so +amd64.linux.hash=235d4076810cdcac89d959a4278ce99a95993d91aa68a5a5f2aef91afe88dd23316f53ba64c62657d69631d99fe743479370d17622cfd3fbfec1da1b6f673961 diff --git a/projects/kitten-thoughts/src/main/rust/lib.rs b/projects/kitten-thoughts/src/main/rust/lib.rs index cc31b22..fc7e151 100644 --- a/projects/kitten-thoughts/src/main/rust/lib.rs +++ b/projects/kitten-thoughts/src/main/rust/lib.rs @@ -7,7 +7,7 @@ use jni::{ pub mod hello; #[no_mangle] -pub extern "system" fn Java_net_pixaurora_kitten_1thoughts_scrobbler_ScrobblerSetup_hello< +pub extern "system" fn Java_net_pixaurora_kitten_1thoughts_impl_scrobbler_ScrobblerSetup_hello< 'local, >( mut env: JNIEnv<'local>,