diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9c647f2..4e96796 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -32,11 +32,20 @@ jobs: with: cache-read-only: ${{ github.ref != 'refs/heads/meow' }} - name: build - run: ./gradlew build --warning-mode=all - - name: capture build artifacts + run: ./gradlew build buildDevNatives --warning-mode=all + - name: capture Java artifacts if: ${{ runner.os == 'Linux' && matrix.java == '21' }} # Upload artifacts from one job, ignore the rest uses: actions/upload-artifact@v4 with: name: kit-tunes-artifacts path: build/libs if-no-files-found: error + - name: capture Rust artifacts + uses: actions/upload-artifact@v4 + with: + name: libkittenthoughts-${{ runner.os }}-artifacts + path: | + projects/kitten-thoughts/target/debug/*.so + projects/kitten-thoughts/target/debug/*.dylib + projects/kitten-thoughts/target/debug/*.dll + if-no-files-found: error 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/ diff --git a/build-logic/src/main/java/net/pixaurora/kit_tunes/build_logic/mod_resources_gen/data/ModIcon.java b/build-logic/src/main/java/net/pixaurora/kit_tunes/build_logic/mod_resources_gen/data/ModIcon.java index 6a417e6..dae035f 100644 --- a/build-logic/src/main/java/net/pixaurora/kit_tunes/build_logic/mod_resources_gen/data/ModIcon.java +++ b/build-logic/src/main/java/net/pixaurora/kit_tunes/build_logic/mod_resources_gen/data/ModIcon.java @@ -9,8 +9,9 @@ public enum ModIcon { KIT_TUNES(), KIT_TUNES_API(), - KITTEN_SQUARE(), KITTEN_HEART(), + KITTEN_THOUGHTS(), + KITTEN_SQUARE(), KITTEN_SOUNDS(); private final String modId; diff --git a/build.gradle.kts b/build.gradle.kts index f92355f..9ab6dd8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,6 +14,7 @@ mod { dependencies { include(project(":projects:kit-tunes-api")) include(project(":projects:kitten-heart")) + include(project(":projects:kitten-thoughts")) include(project(":projects:kitten-sounds:r1.17.0")) include(project(":projects:kitten-sounds:r1.20.3")) diff --git a/projects/kitten-heart/build.gradle.kts b/projects/kitten-heart/build.gradle.kts index 08ac7a7..88d96e3 100644 --- a/projects/kitten-heart/build.gradle.kts +++ b/projects/kitten-heart/build.gradle.kts @@ -13,15 +13,16 @@ mod { dependencies { required("quilt_loader").versionAbove(libs.versions.quilt.loader.get()) - required("kit_tunes_api") required("kitten_square") required("kitten_sounds") + required("kitten_thoughts") } } dependencies { implementation(project(":projects:kit-tunes-api")) + implementation(project(":projects:kitten-thoughts")) implementation(libs.annotations) implementation(libs.quilt.loader) 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 baf1b3a..f757d58 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,6 +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.impl.KittenThoughts; +import net.pixaurora.kitten_thoughts.impl.scrobbler.ScrobblerSetup; public class KitTunes { public static final Logger LOGGER = LoggerFactory.getLogger(Constants.MOD_ID); @@ -41,6 +43,11 @@ public static void init() { // doing this can sometimes cause issues. MusicMetadata.init(MusicMetadataLoader.albumFiles(), MusicMetadataLoader.artistFiles(), MusicMetadataLoader.trackFiles()); + + KittenThoughts.init(); + + String helloMessage = ScrobblerSetup.hello("CoolCat"); + KitTunes.LOGGER.info(helloMessage); } public static void tick() { 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/Cargo.lock b/projects/kitten-thoughts/Cargo.lock new file mode 100644 index 0000000..90fe096 --- /dev/null +++ b/projects/kitten-thoughts/Cargo.lock @@ -0,0 +1,294 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "kitten_thoughts" +version = "0.1.0" +dependencies = [ + "jni", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "syn" +version = "2.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/projects/kitten-thoughts/Cargo.toml b/projects/kitten-thoughts/Cargo.toml new file mode 100644 index 0000000..4cf5a12 --- /dev/null +++ b/projects/kitten-thoughts/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "kitten_thoughts" +version = "0.1.0" +edition = "2021" + +[dependencies] +jni = "0.21.1" + +[lib] +crate-type = ["cdylib"] +path = "src/main/rust/lib.rs" diff --git a/projects/kitten-thoughts/build.gradle.kts b/projects/kitten-thoughts/build.gradle.kts new file mode 100644 index 0000000..cd768c5 --- /dev/null +++ b/projects/kitten-thoughts/build.gradle.kts @@ -0,0 +1,40 @@ +import java.io.ByteArrayOutputStream + +plugins { + id("kit_tunes.java.08") + id("kit_tunes.base") + id("kit_tunes.default_resources") +} + +mod { + metadata { + library() + parentModId = "kit_tunes" + } +} + +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/gradle.properties b/projects/kitten-thoughts/gradle.properties new file mode 100644 index 0000000..625f08d --- /dev/null +++ b/projects/kitten-thoughts/gradle.properties @@ -0,0 +1,5 @@ +# Mod properties +base_mod_name = kitten +sub_mod_name = thoughts + +description = A Rust library to add some features, such as a HTTP server for scrobbler setup and music file parsing 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/impl/scrobbler/ScrobblerSetup.java b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/scrobbler/ScrobblerSetup.java new file mode 100644 index 0000000..b7502ad --- /dev/null +++ b/projects/kitten-thoughts/src/main/java/net/pixaurora/kitten_thoughts/impl/scrobbler/ScrobblerSetup.java @@ -0,0 +1,5 @@ +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/hello.rs b/projects/kitten-thoughts/src/main/rust/hello.rs new file mode 100644 index 0000000..6da8742 --- /dev/null +++ b/projects/kitten-thoughts/src/main/rust/hello.rs @@ -0,0 +1,6 @@ +pub fn hello(input: String) -> String { + format!( + "Hello, {}! This message is from Rust of all programming languages, woo hoo!", + input + ) +} diff --git a/projects/kitten-thoughts/src/main/rust/lib.rs b/projects/kitten-thoughts/src/main/rust/lib.rs new file mode 100644 index 0000000..fc7e151 --- /dev/null +++ b/projects/kitten-thoughts/src/main/rust/lib.rs @@ -0,0 +1,40 @@ +use jni::{ + objects::{JClass, JString}, + sys::jstring, + JNIEnv, +}; + +pub mod hello; + +#[no_mangle] +pub extern "system" fn Java_net_pixaurora_kitten_1thoughts_impl_scrobbler_ScrobblerSetup_hello< + 'local, +>( + mut env: JNIEnv<'local>, + _class: JClass<'local>, + input: JString<'local>, +) -> jstring { + let input: String = env + .get_string(&input) + .expect("Couldn't get java string!") + .into(); + + let output = hello::hello(input); + let output = env + .new_string(output) + .expect("Couldn't create java string!"); + + output.into_raw() +} + +#[cfg(test)] +mod tests { + use super::hello::hello; + + #[test] + fn hello_message() { + let output = hello("Coolcat".into()); + + assert_eq!(output, "Hello, Coolcat!"); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index ff09596..3912b54 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,6 +21,7 @@ includeBuild("build-logic") include("projects:kit-tunes-api") include("projects:kitten-heart") +include("projects:kitten-thoughts") include("projects:kitten-sounds:r1.17.0") include("projects:kitten-sounds:r1.20.3") diff --git a/shared-resources/icons/kitten_thoughts.png b/shared-resources/icons/kitten_thoughts.png new file mode 100644 index 0000000..6e46c9f Binary files /dev/null and b/shared-resources/icons/kitten_thoughts.png differ