diff --git a/app/build.gradle b/app/build.gradle index b5efd0abc..ffe0e4d10 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,6 +26,21 @@ discordApplicationId = new BigInteger(discordApplicationId).toString() def discordLargeImage = providers.gradleProperty('discord.large_image') .getOrElse(discordProperties.getProperty('large_image', '')).trim() +def curseForgeApiKey = localProperties.getProperty('curseforge.api_key', '').trim() + +// Fallback for local debug builds only: the same obfuscated key LeviLauncher +// for Windows embeds (base64, then XOR 0xAF). Release and beta builds, and +// every CI build, keep using the key from local.properties (CI fills it from +// a secret). +def curseForgeDebugApiKey = curseForgeApiKey +if (curseForgeDebugApiKey.isEmpty() && !System.getenv('CI')) { + def embeddedKeyFile = file('curseforge-debug-apikey.bin') + if (embeddedKeyFile.exists()) { + def deobfuscated = embeddedKeyFile.bytes.collect { (byte) (it ^ 0xAF) } as byte[] + curseForgeDebugApiKey = new String(Base64.decoder.decode(new String(deobfuscated, 'US-ASCII')), 'UTF-8').trim() + } +} + def getGitTag() { def tag = "unknown" try { @@ -60,7 +75,7 @@ android { versionCode getVersionCodeByTimestamp() versionName getGitTag() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - buildConfigField "String", "CURSEFORGE_API_KEY", "\"${localProperties.getProperty('curseforge.api_key', '')}\"" + buildConfigField "String", "CURSEFORGE_API_KEY", groovy.json.JsonOutput.toJson(curseForgeApiKey) buildConfigField 'long', 'DISCORD_APPLICATION_ID', "${discordApplicationId}L" buildConfigField 'String', 'DISCORD_LARGE_IMAGE', groovy.json.JsonOutput.toJson(discordLargeImage) ndk { @@ -115,6 +130,7 @@ android { debugSymbolLevel 'FULL' } buildConfigField "boolean", "IS_BETA", "false" + buildConfigField "String", "CURSEFORGE_API_KEY", groovy.json.JsonOutput.toJson(curseForgeDebugApiKey) } beta { initWith release diff --git a/app/curseforge-debug-apikey.bin b/app/curseforge-debug-apikey.bin new file mode 100644 index 000000000..84959af79 --- /dev/null +++ b/app/curseforge-debug-apikey.bin @@ -0,0 +1 @@ +åëåÇåëêØåèßãÍéÌšùÅõøùøýÄýÄÜüèÌØÎéõõúê–ßÌèÈ�áûý÷ÊëåÄýùÄœöžÌÖýÅéßËÃê›öøöšûúØÚËúý× \ No newline at end of file diff --git a/app/src/main/java/org/levimc/launcher/core/curseforge/CurseForgeClient.java b/app/src/main/java/org/levimc/launcher/core/curseforge/CurseForgeClient.java index 4e3a8dfa3..f87124ed0 100644 --- a/app/src/main/java/org/levimc/launcher/core/curseforge/CurseForgeClient.java +++ b/app/src/main/java/org/levimc/launcher/core/curseforge/CurseForgeClient.java @@ -50,6 +50,17 @@ public static synchronized CurseForgeClient getInstance() { return instance; } + private static IOException httpError(Response response) { + int code = response.code(); + if (code == 401 || code == 403) { + String reason = API_KEY == null || API_KEY.isEmpty() + ? "no CurseForge API key in this build (set curseforge.api_key in local.properties)" + : "CurseForge rejected the API key"; + return new IOException(reason + " (HTTP " + code + ")"); + } + return new IOException("Unexpected code " + response); + } + public interface CurseForgeCallback { void onSuccess(T result); void onError(Throwable t); @@ -88,7 +99,7 @@ public void onFailure(Call call, IOException e) { @Override public void onResponse(Call call, Response response) { if (!response.isSuccessful()) { - callback.onError(new IOException("Unexpected code " + response)); + callback.onError(httpError(response)); return; } @@ -120,7 +131,7 @@ public void onFailure(Call call, IOException e) { @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { - callback.onError(new IOException("Unexpected code " + response)); + callback.onError(httpError(response)); return; } @@ -155,7 +166,7 @@ public void onFailure(Call call, IOException e) { @Override public void onResponse(Call call, Response response) { if (!response.isSuccessful()) { - callback.onError(new IOException("Unexpected code " + response)); + callback.onError(httpError(response)); return; }