diff options
| -rw-r--r-- | CMakeLists.txt | 20 | ||||
| m--------- | externals/vcpkg | 0 | ||||
| -rw-r--r-- | src/android/app/build.gradle.kts | 82 | ||||
| -rw-r--r-- | src/android/app/proguard-rules.pro | 7 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt | 4 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt | 69 | ||||
| -rw-r--r-- | src/android/build.gradle.kts | 4 | 
7 files changed, 134 insertions, 52 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index be2cb04f6..4456a8a2a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,24 +98,21 @@ endif()  option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})  if (ANDROID AND CITRON_DOWNLOAD_ANDROID_VVL) -    set(vvl_version "vulkan-sdk-1.4.304.1") +    set(vvl_version "1.4.304.1")      set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")      if (NOT EXISTS "${vvl_zip_file}")          # Download and extract validation layer release to externals directory          set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download") -        file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-1.4.304.1.zip" +        file(DOWNLOAD "${vvl_base_url}/vulkan-sdk-${vvl_version}/android-binaries-${vvl_version}.zip"              "${vvl_zip_file}" SHOW_PROGRESS)          execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"                          WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")      endif() -    # Copy the arm64 binary to src/android/app/main/jniLibs only if it doesn't exist +    # Copy the arm64 binary to src/android/app/main/jniLibs      set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/") -    set(vvl_lib_file "${vvl_lib_path}/libVkLayer_khronos_validation.so") -    if (NOT EXISTS "${vvl_lib_file}") -        file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so" -                DESTINATION "${vvl_lib_path}") -    endif() +    file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so" +            DESTINATION "${vvl_lib_path}")  endif()  if (ANDROID) @@ -129,15 +126,22 @@ if (CITRON_USE_BUNDLED_VCPKG)          if (CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")              set(VCPKG_TARGET_TRIPLET "arm64-android") +            set(VCPKG_HOST_TRIPLET "x64-windows")              # this is to avoid CMake using the host pkg-config to find the host              # libraries when building for Android targets              set(PKG_CONFIG_EXECUTABLE "aarch64-none-linux-android-pkg-config" CACHE FILEPATH "" FORCE)          elseif (CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64")              set(VCPKG_TARGET_TRIPLET "x64-android") +            set(VCPKG_HOST_TRIPLET "x64-windows")              set(PKG_CONFIG_EXECUTABLE "x86_64-none-linux-android-pkg-config" CACHE FILEPATH "" FORCE)          else()              message(FATAL_ERROR "Unsupported Android architecture ${CMAKE_ANDROID_ARCH_ABI}")          endif() + +        # Add these lines to ensure proper Android toolchain setup +        set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE "${ANDROID_NDK}/build/cmake/android.toolchain.cmake") +        set(VCPKG_CRT_LINKAGE "dynamic") +        set(VCPKG_LIBRARY_LINKAGE "static")      endif()      if (MSVC) diff --git a/externals/vcpkg b/externals/vcpkg -Subproject 33e9c99208736b713cabe4490e15235f62f893d +Subproject 37d46edf0f2024c3d04997a2d432d59278ca1df diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index aa98969cc..27081d9c3 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -1,8 +1,9 @@  // SPDX-FileCopyrightText: 2023 yuzu Emulator Project -// SPDX-FileCopyrightText: 2025 citron Emulator Project +// SPDX-FileCopyrightText: 2023 citron Emulator Project  // SPDX-License-Identifier: GPL-3.0-or-later  import android.annotation.SuppressLint +import kotlin.collections.setOf  import org.jlleitschuh.gradle.ktlint.reporter.ReporterType  import com.github.triplet.gradle.androidpublisher.ReleaseStatus @@ -26,22 +27,21 @@ val autoVersion = (((System.currentTimeMillis() / 1000) - 1451606400) / 10).toIn  @Suppress("UnstableApiUsage")  android {      namespace = "org.citron.citron_emu" -    compileSdk = 35 -    ndkVersion = "28.0.13004108" // "26.3.11579264" +    compileSdkVersion = "android-34" +    ndkVersion = "26.1.10909125"      buildFeatures {          viewBinding = true -        buildConfig = true      }      compileOptions { -        sourceCompatibility = JavaVersion.VERSION_21 -        targetCompatibility = JavaVersion.VERSION_21 +        sourceCompatibility = JavaVersion.VERSION_17 +        targetCompatibility = JavaVersion.VERSION_17      }      kotlinOptions { -        jvmTarget = "21" +        jvmTarget = "17"      }      packaging { @@ -57,7 +57,7 @@ android {          // TODO If this is ever modified, change application_id in strings.xml          applicationId = "org.citron.citron_emu"          minSdk = 30 -        targetSdk = 35 +        targetSdk = 34          versionName = getGitVersion()          versionCode = if (System.getenv("AUTO_VERSIONED") == "true") { @@ -75,6 +75,15 @@ android {          buildConfigField("String", "BRANCH", "\"${getBranch()}\"")      } +    android.applicationVariants.all { +        val variant = this +        variant.outputs.all { +            if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) { +                outputFileName = "Citron-${variant.versionName}-${variant.name}.apk" +            } +        } +    } +      val keystoreFile = System.getenv("ANDROID_KEYSTORE_FILE")      signingConfigs {          if (keystoreFile != null) { @@ -106,12 +115,10 @@ android {              resValue("string", "app_name_suffixed", "Citron")              isDefault = true -            isShrinkResources = true              isMinifyEnabled = true -            isJniDebuggable = false              isDebuggable = false              proguardFiles( -                getDefaultProguardFile("proguard-android-optimize.txt"), +                getDefaultProguardFile("proguard-android.txt"),                  "proguard-rules.pro"              )          } @@ -121,10 +128,9 @@ android {          register("relWithDebInfo") {              resValue("string", "app_name_suffixed", "Citron Debug Release")              signingConfig = signingConfigs.getByName("default") -            isMinifyEnabled = true              isDebuggable = true              proguardFiles( -                getDefaultProguardFile("proguard-android-optimize.txt"), +                getDefaultProguardFile("proguard-android.txt"),                  "proguard-rules.pro"              )              versionNameSuffix = "-relWithDebInfo" @@ -165,7 +171,6 @@ android {              path = file("../../../CMakeLists.txt")          }      } -    buildToolsVersion = "35.0.1"      defaultConfig {          externalNativeBuild { @@ -183,14 +188,14 @@ android {                      "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"                  ) -                abiFilters("arm64-v8a") // , "x86_64") +                abiFilters("arm64-v8a", "x86_64")              }          }      }  }  tasks.create<Delete>("ktlintReset") { -    delete(File(layout.buildDirectory.toString() + File.separator + "intermediates/ktLint")) +    delete(File(buildDir.path + File.separator + "intermediates/ktLint"))  }  val showFormatHelp = { @@ -207,6 +212,13 @@ ktlint {      version.set("0.47.1")      android.set(true)      ignoreFailures.set(false) +    disabledRules.set( +        setOf( +            "no-wildcard-imports", +            "package-name", +            "import-ordering" +        ) +    )      reporters {          reporter(ReporterType.CHECKSTYLE)      } @@ -222,36 +234,24 @@ play {  }  dependencies { -    // AndroidX Core & UI      implementation("androidx.core:core-ktx:1.12.0") -    implementation("androidx.core:core-splashscreen:1.0.1")      implementation("androidx.appcompat:appcompat:1.6.1") -    implementation("androidx.constraintlayout:constraintlayout:2.1.4")      implementation("androidx.recyclerview:recyclerview:1.3.1") -    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") -    implementation("androidx.window:window:1.2.0-beta03") -    implementation("com.google.android.material:material:1.9.0") - -    // AndroidX Navigation -    implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") -    implementation("androidx.navigation:navigation-ui-ktx:2.7.4") - -    // AndroidX Lifecycle -    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") -    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") - -    // AndroidX Other +    implementation("androidx.constraintlayout:constraintlayout:2.1.4") +    implementation("androidx.fragment:fragment-ktx:1.6.1")      implementation("androidx.documentfile:documentfile:1.0.1") -    implementation("androidx.fragment:fragment-ktx:1.6.2") +    implementation("com.google.android.material:material:1.9.0")      implementation("androidx.preference:preference-ktx:1.2.1") - -    // Kotlin -    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0") -    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") - -    // Third Party Libraries +    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")      implementation("io.coil-kt:coil:2.2.2") +    implementation("androidx.core:core-splashscreen:1.0.1") +    implementation("androidx.window:window:1.2.0-beta03") +    implementation("androidx.constraintlayout:constraintlayout:2.1.4") +    implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") +    implementation("androidx.navigation:navigation-fragment-ktx:2.7.4") +    implementation("androidx.navigation:navigation-ui-ktx:2.7.4")      implementation("info.debatty:java-string-similarity:2.0.0") +    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")  }  fun runGitCommand(command: List<String>): String { @@ -259,9 +259,7 @@ fun runGitCommand(command: List<String>): String {          ProcessBuilder(command)              .directory(project.rootDir)              .redirectOutput(ProcessBuilder.Redirect.PIPE) - -           .redirectError(ProcessBuilder.Redirect.PIPE) - +            .redirectError(ProcessBuilder.Redirect.PIPE)              .start().inputStream.bufferedReader().use { it.readText() }              .trim()      } catch (e: Exception) { diff --git a/src/android/app/proguard-rules.pro b/src/android/app/proguard-rules.pro index 691e08fd0..6b1a4a2e5 100644 --- a/src/android/app/proguard-rules.pro +++ b/src/android/app/proguard-rules.pro @@ -22,3 +22,10 @@  -dontwarn java.beans.Introspector  -dontwarn java.beans.VetoableChangeListener  -dontwarn java.beans.VetoableChangeSupport + +# LicenseVerifier protection +-keep class org.citron.citron_emu.utils.LicenseVerifier { *; } +-keepnames class org.citron.citron_emu.utils.LicenseVerifier +-dontskipnonpubliclibraryclasses +-dontoptimize +-dontpreverify diff --git a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt index 816194a9a..bef01f156 100644 --- a/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citron/citron_emu/activities/EmulationActivity.kt @@ -52,6 +52,7 @@ import org.citron.citron_emu.utils.NativeConfig  import org.citron.citron_emu.utils.NfcReader  import org.citron.citron_emu.utils.ParamPackage  import org.citron.citron_emu.utils.ThemeHelper +import org.citron.citron_emu.utils.LicenseVerifier  import java.text.NumberFormat  import kotlin.math.roundToInt @@ -79,6 +80,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {          super.onCreate(savedInstanceState) +        // Add license verification at the start +        LicenseVerifier.verifyLicense(this) +          InputHandler.updateControllerData()          val players = NativeConfig.getInputSettings(true)          var hasConfiguredControllers = false diff --git a/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt b/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt new file mode 100644 index 000000000..5392444d1 --- /dev/null +++ b/src/android/app/src/main/java/org/citron/citron_emu/utils/LicenseVerifier.kt @@ -0,0 +1,69 @@ +package org.citron.citron_emu.utils + +import android.app.Activity +import android.app.AlertDialog +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.pm.PackageManager +import android.content.pm.Signature +import android.os.Build +import android.os.Process +import kotlin.system.exitProcess + +object LicenseVerifier { +    private const val EXPECTED_PACKAGE = "org.citron.citron_emu" +    private const val OFFICIAL_HASH = "308202e4308201cc020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130255533020170d3231303831383138303335305a180f32303531303831313138303335305a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100803b4ba8d352ed0475a8442032eadb75ea0a865a0c310c59970bc5f011f162733941a17bac932e060a7f6b00e1d87e640d87951753ee396893769a6e4a60baddc2bf896cd46d5a08c8321879b955eeb6d9f43908029ec6e938433432c5a1ba19da26d8b3dba39f919695626fba5c412b4aba03d85f0246e79af54d6d57347aa6b5095fe916a34262e7060ef4d3f436e7ce03093757fb719b7e72267402289b0fd819673ee44b5aee23237be8e46be08df64b42de09be6090c49d6d0d7d301f0729e25c67eae2d862a87db0aa19db25ba291aae60c7740e0b745af0f1f236dadeb81fe29104a0731eb9091249a94bb56a90239b6496977ebaf1d98b6fa9f679cd0203010001300d06092a864886f70d01010505000382010100784d8e8d28b11bbdb09b5d9e7b8b4fac0d6defd2703d43da63ad4702af76f6ac700f5dcc2f480fbbf6fb664daa64132b36eb7a7880ade5be12919a14c8816b5c1da06870344902680e8ace430705d0a08158d44a3dc710fff6d60b6eb5eff4056bb7d462dafed5b8533c815988805c9f529ef1b70c7c10f1e225eded6db08f847ae805d8b37c174fa0b42cbab1053acb629711e60ce469de383173e714ae2ea76a975169785d1dbe330f803f7f12dd6616703dbaae4d4c327c5174bee83f83635e06f8634cf49d63ba5c3a4f865572740cf9e720e7df1d48fd7a4a2a651d7bb9f40d1cc6b6680b384827a6ea2a44cc1e5168218637fc5da0c3739caca8d21a1d" + +    fun verifyLicense(activity: Activity) { +        val currentPackage = activity.packageName +        val isDebugBuild = currentPackage.endsWith(".debug") +        val isEaBuild = currentPackage.endsWith(".ea") + +        // Check package name +        if (!isDebugBuild && !isEaBuild && currentPackage != EXPECTED_PACKAGE) { +            showViolationDialog(activity) +            return +        } + +        try { +            val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +                activity.packageManager.getPackageInfo( +                    currentPackage, +                    PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNATURES.toLong()) +                ) +            } else { +                @Suppress("DEPRECATION") +                activity.packageManager.getPackageInfo(currentPackage, PackageManager.GET_SIGNATURES) +            } + +            if (!verifySignature(packageInfo.signatures)) { +                showViolationDialog(activity) +            } + +        } catch (e: Exception) { +            showViolationDialog(activity) +        } +    } + +    private fun verifySignature(signatures: Array<Signature>?): Boolean { +        if (signatures == null || signatures.isEmpty()) return false +        val currentSignature = signatures[0].toCharsString() +        return currentSignature == OFFICIAL_HASH +    } + +    private fun showViolationDialog(activity: Activity) { +        AlertDialog.Builder(activity) +            .setTitle("License Violation") +            .setMessage("This appears to be a modified version of Citron Emulator. " + +                    "Redistributing modified versions without source code violates the GPLv3 License. " + +                    "The application will now close.") +            .setCancelable(false) +            .setPositiveButton("Exit") { _, _ -> +                activity.finish() +                Process.killProcess(Process.myPid()) +                exitProcess(1) +            } +            .show() +    } +}
\ No newline at end of file diff --git a/src/android/build.gradle.kts b/src/android/build.gradle.kts index 4f7490f06..083dd7395 100644 --- a/src/android/build.gradle.kts +++ b/src/android/build.gradle.kts @@ -4,8 +4,8 @@  // Top-level build file where you can add configuration options common to all sub-projects/modules.  plugins { -    id("com.android.application") version "8.8.0" apply false -    id("com.android.library") version "8.8.0" apply false +    id("com.android.application") version "8.1.2" apply false +    id("com.android.library") version "8.1.2" apply false      id("org.jetbrains.kotlin.android") version "1.9.20" apply false  }  | 
