From ce9fb3609161369c1285ff5caf5a650b24b65cbb Mon Sep 17 00:00:00 2001 From: JCM Date: Fri, 4 Feb 2022 12:13:07 +0100 Subject: [PATCH] Make initialisation more convenient This partly fixes #36. --- README.md | 26 +-- src/main/java/de/jcm/discordgamesdk/Core.java | 204 +++++++++++++++++- 2 files changed, 214 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5605fc7..2fb1a5a 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,20 @@ head over to the [ActivityExample.java](examples/ActivityExample.java)! ## Features of the SDK -| Feature | State | Example | -| ------- | ----- | ------- | -| [Achievements](https://discord.com/developers/docs/game-sdk/achievements) | :x: not implemented | | -| [Activities](https://discord.com/developers/docs/game-sdk/activities) | :heavy_check_mark: implemented | [ActivityExample.java](examples/ActivityExample.java) | -| [Applications](https://discord.com/developers/docs/game-sdk/applications) | :x: not implemented | | -| [Voice](https://discord.com/developers/docs/game-sdk/discord-voice) | :heavy_check_mark: implemented | [VoiceExample.java](examples/VoiceExample.java) | -| [Images](https://discord.com/developers/docs/game-sdk/images) | :heavy_check_mark: implemented | none yet :cry: (see [``imageTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L417) for now) | -| [Lobbies](https://discord.com/developers/docs/game-sdk/lobbies) | :heavy_check_mark: implemented | [LobbyExample.java](examples/LobbyExample.java) | -| [Networking](https://discord.com/developers/docs/game-sdk/networking) | :heavy_check_mark: implemented | [NetworkExample.java](examples/NetworkExample.java) | -| [Overlay](https://discord.com/developers/docs/game-sdk/overlay) | :heavy_check_mark: implemented | none yet :cry: (see [``overlayTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L289) for now) | +| Feature | State | Example | +|-----------------------------------------------------------------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------| +| [Achievements](https://discord.com/developers/docs/game-sdk/achievements) | :x: not implemented | | +| [Activities](https://discord.com/developers/docs/game-sdk/activities) | :heavy_check_mark: implemented | [ActivityExample.java](examples/ActivityExample.java) | +| [Applications](https://discord.com/developers/docs/game-sdk/applications) | :x: not implemented | | +| [Voice](https://discord.com/developers/docs/game-sdk/discord-voice) | :heavy_check_mark: implemented | [VoiceExample.java](examples/VoiceExample.java) | +| [Images](https://discord.com/developers/docs/game-sdk/images) | :heavy_check_mark: implemented | none yet :cry: (see [``imageTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L417) for now) | +| [Lobbies](https://discord.com/developers/docs/game-sdk/lobbies) | :heavy_check_mark: implemented | [LobbyExample.java](examples/LobbyExample.java) | +| [Networking](https://discord.com/developers/docs/game-sdk/networking) | :heavy_check_mark: implemented | [NetworkExample.java](examples/NetworkExample.java) | +| [Overlay](https://discord.com/developers/docs/game-sdk/overlay) | :heavy_check_mark: implemented | none yet :cry: (see [``overlayTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L289) for now) | | [Relationships](https://discord.com/developers/docs/game-sdk/relationships) | :heavy_check_mark: implemented | [RelationshipExample.java](examples/RelationshipExample.java), [FriendNotificationExample.java](examples/FriendNotificationExample.java) | -| [Storage](https://discord.com/developers/docs/game-sdk/storage) | :x: not implemented | | -| [Store](https://discord.com/developers/docs/game-sdk/store) | :x: not implemented | | -| [Users](https://discord.com/developers/docs/game-sdk/users) | :heavy_check_mark: implemented | none yet :cry: (see [``userTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L216) for now) | +| [Storage](https://discord.com/developers/docs/game-sdk/storage) | :x: not implemented | | +| [Store](https://discord.com/developers/docs/game-sdk/store) | :x: not implemented | | +| [Users](https://discord.com/developers/docs/game-sdk/users) | :heavy_check_mark: implemented | none yet :cry: (see [``userTest()``](src/test/java/de/jcm/discordgamesdk/DiscordTest.java#L216) for now) | I will try to work on features that are not implemented yet soon, but the remaining ones are quite difficult to test, diff --git a/src/main/java/de/jcm/discordgamesdk/Core.java b/src/main/java/de/jcm/discordgamesdk/Core.java index b7b034a..161e725 100644 --- a/src/main/java/de/jcm/discordgamesdk/Core.java +++ b/src/main/java/de/jcm/discordgamesdk/Core.java @@ -14,6 +14,8 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; /** * The main component for accessing Discord's game SDK. @@ -37,9 +39,6 @@ public class Core implements AutoCloseable * You may call this method more than once which unloads the old shared object and loads the new one. * * @param discordLibrary Location of Discord's native library. - *

On Windows the filename (last component of the path) must be - * "discord_game_sdk.dll" or an {@link UnsatisfiedLinkError} will occur.

- *

On Linux the filename does not matter.

* * @throws UnsatisfiedLinkError if Discord's native library can not be loaded */ @@ -52,6 +51,25 @@ public static void init(File discordLibrary) init(discordLibrary, tempDir); } + /** + * Extracts and initializes the native library. + * This method also loads Discord's native library. + *

+ * The JNI library is extracted from the classpath (e.g. the currently running JAR) + * using {@link Class#getResourceAsStream(String)}. + * Its path inside the JAR must be of the pattern {@code /native/{os}/{arch}/{object name}} + * where {@code os} is either "windows" or "linux", {@code arch} is the system architecture as in + * the system property {@code os.arch} and {@code object name} is the name of the native object + * (e.g. "discord_game_sdk_jni.dll" on Windows or "libdiscord_game_sdk_jni.so" on Linux. + *

+ * You may call this method more than once which unloads the old shared object and loads the new one. + * + * @param discordLibrary Location of Discord's native library. + * @param tempDir Temporary directory, to which the discordLibrary will be copied to avoid problems + * on Windows. + * + * @throws UnsatisfiedLinkError if Discord's native library can not be loaded + */ public static void init(File discordLibrary, File tempDir) { String name = "discord_game_sdk_jni"; @@ -112,6 +130,25 @@ Some systems (e.g. Mac OS X) might report the architecture as "x86_64" instead o initDiscordNative(discordLibrary.getAbsolutePath()); } + /** + * Extracts and initializes the native library. + * This method also loads Discord's native library from any given URL. + *

+ * The JNI library is extracted from the classpath (e.g. the currently running JAR) + * using {@link Class#getResourceAsStream(String)}. + * Its path inside the JAR must be of the pattern {@code /native/{os}/{arch}/{object name}} + * where {@code os} is either "windows" or "linux", {@code arch} is the system architecture as in + * the system property {@code os.arch} and {@code object name} is the name of the native object + * (e.g. "discord_game_sdk_jni.dll" on Windows or "libdiscord_game_sdk_jni.so" on Linux. + *

+ * You may call this method more than once which unloads the old shared object and loads the new one. + *

+ * The URL will be read and copied into a temporary file. + * + * @param url URL from which to load Discord's native library. + * + * @throws UnsatisfiedLinkError if Discord's native library can not be loaded + */ public static void init(URL url) { String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT); @@ -164,6 +201,167 @@ else if(osName.contains("linux")) } } + /** + * Extracts and initializes the native library. + * This method also loads Discord's native library from the classpath, which must be located + * at {@code /lib/{arch}/{name}} where {@code arch} is the system architecture from {@code os.arch}, + * where {@code amd64} is replaced with {@code x86_64} for unification, and {@code name} is either + * {@code discord_game_sdk.dll} for Windows, {@code discord_game_sdk.so} for Linux or {@code discord_game_sdk.dylib} + * for macOS. + *

+ * The JNI library is extracted from the classpath (e.g. the currently running JAR) + * using {@link Class#getResourceAsStream(String)}. + * Its path inside the JAR must be of the pattern {@code /native/{os}/{arch}/{object name}} + * where {@code os} is either "windows" or "linux", {@code arch} is the system architecture as in + * the system property {@code os.arch} and {@code object name} is the name of the native object + * (e.g. "discord_game_sdk_jni.dll" on Windows or "libdiscord_game_sdk_jni.so" on Linux. + *

+ * You may call this method more than once which unloads the old shared object and loads the new one. + *

+ * The resource will be read and copied into a temporary file. + * + * @throws UnsatisfiedLinkError if Discord's native library can not be loaded + */ + public static void initFromClasspath() + { + // Find out which name Discord's library has (.dll for Windows, .so for Linux) + String name = "discord_game_sdk"; + String suffix; + + String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT); + String arch = System.getProperty("os.arch").toLowerCase(Locale.ROOT); + + if(osName.contains("windows")) + { + suffix = ".dll"; + } + else if(osName.contains("linux")) + { + suffix = ".so"; + } + else if(osName.contains("mac os")) + { + suffix = ".dylib"; + } + else + { + throw new RuntimeException("cannot determine OS type: "+osName); + } + + /* + Some systems report "amd64" (e.g. Windows and Linux), some "x86_64" (e.g. Mac OS). + At this point we need the "x86_64" version, as this one is used in the ZIP. + */ + if(arch.equals("amd64")) + arch = "x86_64"; + + // Path of Discord's library inside the ZIP + String res = "/lib/"+arch+"/"+name+suffix; + + Core.init(Objects.requireNonNull(Core.class.getResource(res))); + } + + private static File downloadDiscordLibrary() throws IOException + { + // Find out which name Discord's library has (.dll for Windows, .so for Linux) + String name = "discord_game_sdk"; + String suffix; + + String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT); + String arch = System.getProperty("os.arch").toLowerCase(Locale.ROOT); + + if(osName.contains("windows")) + { + suffix = ".dll"; + } + else if(osName.contains("linux")) + { + suffix = ".so"; + } + else if(osName.contains("mac os")) + { + suffix = ".dylib"; + } + else + { + throw new RuntimeException("cannot determine OS type: "+osName); + } + + /* + Some systems report "amd64" (e.g. Windows and Linux), some "x86_64" (e.g. Mac OS). + At this point we need the "x86_64" version, as this one is used in the ZIP. + */ + if(arch.equals("amd64")) + arch = "x86_64"; + + // Path of Discord's library inside the ZIP + String zipPath = "lib/"+arch+"/"+name+suffix; + + // Open the URL as a ZipInputStream + URL downloadUrl = new URL("https://dl-game-sdk.discordapp.net/2.5.6/discord_game_sdk.zip"); + ZipInputStream zin = new ZipInputStream(downloadUrl.openStream()); + + // Search for the right file inside the ZIP + ZipEntry entry; + while((entry = zin.getNextEntry())!=null) + { + if(entry.getName().equals(zipPath)) + { + // Create a new temporary directory + // We need to do this, because we may not change the filename on Windows + File tempDir = new File(System.getProperty("java.io.tmpdir"), "java-"+name+System.nanoTime()); + if(!tempDir.mkdir()) + throw new IOException("Cannot create temporary directory"); + tempDir.deleteOnExit(); + + // Create a temporary file inside our directory (with a "normal" name) + File temp = new File(tempDir, name+suffix); + temp.deleteOnExit(); + + // Copy the file in the ZIP to our temporary file + Files.copy(zin, temp.toPath()); + + // We are done, so close the input stream + zin.close(); + + // Return our temporary file + return temp; + } + // next entry + zin.closeEntry(); + } + zin.close(); + // We couldn't find the library inside the ZIP + return null; + } + + /** + * Extracts and initializes the native library. + * This method also downloads, extracts and loads Discord's native library from + * + * https://dl-game-sdk.discordapp.net/2.5.6/discord_game_sdk.zip. + *

+ * The JNI library is extracted from the classpath (e.g. the currently running JAR) + * using {@link Class#getResourceAsStream(String)}. + * Its path inside the JAR must be of the pattern {@code /native/{os}/{arch}/{object name}} + * where {@code os} is either "windows" or "linux", {@code arch} is the system architecture as in + * the system property {@code os.arch} and {@code object name} is the name of the native object + * (e.g. "discord_game_sdk_jni.dll" on Windows or "libdiscord_game_sdk_jni.so" on Linux. + *

+ * You may call this method more than once which unloads the old shared object and loads the new one. + *

+ * The resource will be read and copied into a temporary file. + * + * @throws UnsatisfiedLinkError if Discord's native library can not be loaded + */ + public static void initDownload() throws IOException + { + File f = downloadDiscordLibrary(); + if(f == null) + throw new FileNotFoundException("cannot find native library in downloaded zip file"); + init(f); + } + /** * Loads Discord's SDK library. *