├── .idea
├── .name
├── .gitignore
├── compiler.xml
├── kotlinc.xml
├── vcs.xml
├── misc.xml
├── inspectionProfiles
│ └── Project_Default.xml
└── gradle.xml
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── assets
│ │ │ ├── xposed_init
│ │ │ └── felica_template.json
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── array.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── style.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── strings.xml
│ │ │ ├── navigation
│ │ │ │ └── nav_graph.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── menu
│ │ │ │ ├── card_menu.xml
│ │ │ │ └── toolbar_menu.xml
│ │ │ ├── xml
│ │ │ │ ├── host_nfcf_service.xml
│ │ │ │ ├── apdu_service.xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── mipmap-anydpi-v33
│ │ │ │ └── ic_launcher.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ ├── layout
│ │ │ │ ├── add_card.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── activity_setting.xml
│ │ │ │ └── card.xml
│ │ │ ├── values-zh
│ │ │ │ └── strings.xml
│ │ │ ├── drawable
│ │ │ │ ├── aic.xml
│ │ │ │ ├── aic_fill.xml
│ │ │ │ ├── aic_bg.xml
│ │ │ │ ├── aic_bg_border.xml
│ │ │ │ ├── aic_bg_border_radius.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ │ └── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ ├── java
│ │ │ └── moe
│ │ │ │ └── tqlwsl
│ │ │ │ └── aicemu
│ │ │ │ ├── GlobalVar.kt
│ │ │ │ ├── ApduService.kt
│ │ │ │ ├── ReadCard.kt
│ │ │ │ ├── FelicaCard.kt
│ │ │ │ ├── EmuCard.kt
│ │ │ │ ├── SettingActivity.kt
│ │ │ │ ├── xp.java
│ │ │ │ └── MainActivity.kt
│ │ ├── jni
│ │ │ ├── main.h
│ │ │ ├── dobby
│ │ │ │ └── dobby.h
│ │ │ └── main.cpp
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── moe
│ │ │ └── tqlwsl
│ │ │ └── aicemu
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── moe
│ │ └── tqlwsl
│ │ └── aicemu
│ │ └── ExampleInstrumentedTest.kt
├── libs
│ └── arm64-v8a
│ │ ├── libdobby.a
│ │ └── libdobby.so
├── CMakeLists.txt
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── gradle.properties
├── gradlew.bat
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | AICEmu
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/src/main/assets/xposed_init:
--------------------------------------------------------------------------------
1 | moe.tqlwsl.aicemu.xp
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libdobby.a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/libs/arm64-v8a/libdobby.a
--------------------------------------------------------------------------------
/app/libs/arm64-v8a/libdobby.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/libs/arm64-v8a/libdobby.so
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HowToDoThis/AICEmu/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/array.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - com.android.nfc
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/GlobalVar.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import android.app.Application
4 |
5 | class GlobalVar : Application() {
6 | var IDm: String = "02fe000000000000"
7 | var isHCEFUnlocked: Boolean = false
8 | }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 13 19:37:32 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | .vscode
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/card_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/host_nfcf_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF000000
4 | #FFFFFFFF
5 | #e83948
6 | #98030d
7 | #e86871
8 |
9 | #505050
10 | #B0B0B0
11 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | maven { url "https://api.xposed.info/" }
14 | }
15 | }
16 | rootProject.name = "AICEmu"
17 | include ':app'
18 |
--------------------------------------------------------------------------------
/app/src/test/java/moe/tqlwsl/aicemu/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/jni/main.h:
--------------------------------------------------------------------------------
1 | #ifndef MAIN_H
2 | #define MAIN_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | #include
17 |
18 | void *(*old_func)(u_int8_t, u_int8_t *, int) = nullptr;
19 | #endif //MAIN_H
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/ApduService.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import android.nfc.cardemulation.HostApduService
4 | import android.os.Bundle
5 |
6 |
7 | class ApduService : HostApduService() {
8 | override fun onDeactivated(reason: Int) {
9 | // placeholder
10 | }
11 |
12 | override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle): ByteArray {
13 | // placeholder
14 | return ByteArray(0)
15 | }
16 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/apdu_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.10.2)
2 |
3 |
4 | include_directories(
5 | src/main/jni/dobby
6 | )
7 | enable_language(C ASM)
8 |
9 |
10 | add_library(
11 | pmm
12 | SHARED
13 | src/main/jni/main.cpp
14 | )
15 |
16 | include_directories(dobby)
17 | add_library(local_dobby STATIC IMPORTED)
18 | set_target_properties(local_dobby PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI}/libdobby.a)
19 |
20 |
21 | find_library(
22 | log-lib
23 | log
24 | )
25 |
26 | find_library(android-lib android)
27 | target_link_libraries(
28 | pmm
29 | local_dobby
30 | ${log-lib}
31 | ${android-lib}
32 | )
33 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/moe/tqlwsl/aicemu/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("moe.tqlwsl.aicemu", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | >
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/assets/felica_template.json:
--------------------------------------------------------------------------------
1 | {
2 | "00": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
3 | "01": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
4 | "02": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
5 | "03": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
6 | "04": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
7 | "05": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
8 | "06": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
9 | "07": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
10 | "08": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
11 | "09": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
12 | "0A": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
13 | "0B": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
14 | "0C": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
15 | "0D": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
16 | "0E": "FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF",
17 | "80": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
18 | "81": "00 00 00 00 00 00 00 00",
19 | "82": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
20 | "83": "00 00 00 00 00 00 00 00 00 F1 00 00 00 01 43 00",
21 | "84": "00 00",
22 | "85": "88 B4",
23 | "86": "00 01",
24 | "87": "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00",
25 | "88": "FE 7F 00 00 07 01 1E 00 FF 41 FF 41 01",
26 | "90": "00 00 00",
27 | "91": "",
28 | "92": "00 00"
29 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/app/src/main/res/layout/add_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
24 |
25 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 设置
3 | AIC Card
4 | 请与 AIC 卡贴贴!
5 | 改名
6 | 删除
7 | 展示卡号
8 | 隐藏卡号
9 | 出错了!
10 | 打开兼容模式
11 | 关闭兼容模式
12 | 没开 NFC...
13 | 设备不支持 NFC...
14 | 设置
15 | 设备不支持 HCE-F...
16 | 设备支持 HCE-F!
17 | Unlocker 运行中!
18 | Unlocker 炸了...
19 | Unlocker 出现了错误!
20 | Pmmtool 运行中!
21 | Pmmtool 炸了...
22 | Hook 失败了...
23 | 正在模拟 %1$s...(正常)
24 | 正在模拟 %1$s...(兼容)
25 | 兼容模式
26 | 正常模式
27 | 添加测试卡
28 | 模拟AIC卡
29 | 用 Pmmtool 修改 PMm
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/aic.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/aic_fill.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/aic_bg.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/aic_bg_border.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
17 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/aic_bg_border_radius.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
15 |
20 |
21 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'moe.tqlwsl.aicemu'
8 | compileSdk 34
9 |
10 | defaultConfig {
11 | applicationId "moe.tqlwsl.aicemu"
12 | minSdk 29
13 | targetSdk 33
14 | versionCode 7
15 | versionName '1.0.0'
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_11
28 | targetCompatibility JavaVersion.VERSION_11
29 | }
30 | externalNativeBuild {
31 | defaultConfig.externalNativeBuild.cmake {
32 | abiFilters 'arm64-v8a'
33 | }
34 | cmake {
35 | path file('CMakeLists.txt')
36 | version '3.22.1'
37 | }
38 | }
39 | kotlinOptions {
40 | jvmTarget = '11'
41 | }
42 | buildFeatures {
43 | viewBinding true
44 | }
45 | packagingOptions {
46 | jniLibs {
47 | useLegacyPackaging true
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 | implementation 'androidx.core:core-ktx:1.12.0'
54 | implementation 'androidx.appcompat:appcompat:1.6.1'
55 | implementation 'com.google.android.material:material:1.10.0'
56 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
57 | implementation 'androidx.navigation:navigation-fragment-ktx:2.7.4'
58 | implementation 'androidx.navigation:navigation-ui-ktx:2.7.4'
59 | implementation 'androidx.preference:preference-ktx:1.2.1'
60 | testImplementation 'junit:junit:4.13.2'
61 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
62 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
63 | implementation 'com.google.code.gson:gson:2.8.9'
64 | compileOnly 'de.robv.android.xposed:api:82'
65 | implementation 'org.lsposed.hiddenapibypass:hiddenapibypass:4.3'
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/ReadCard.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu;
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.nfc.NfcAdapter
6 | import android.nfc.Tag
7 | import android.nfc.tech.NfcF
8 | import android.os.Bundle
9 | import android.util.Log
10 | import android.widget.ImageButton
11 | import android.widget.Toast
12 |
13 |
14 | class ReadCard : Activity(), NfcAdapter.ReaderCallback{
15 |
16 | private var nfcAdapter: NfcAdapter? = null
17 | private val TAG = "AICEmu-ReadCard"
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 | setContentView(R.layout.add_card)
22 | val closeButton = this.findViewById(R.id.pop_up_close_button)
23 | closeButton.setOnClickListener { finish() }
24 | nfcAdapter = NfcAdapter.getDefaultAdapter(this)
25 | }
26 |
27 | override fun onResume() {
28 | super.onResume()
29 | nfcAdapter?.let {
30 | val options = Bundle()
31 | options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 5000)
32 | it.enableReaderMode(this, this, NfcAdapter.FLAG_READER_NFC_F, options)
33 | }
34 | }
35 |
36 | override fun onPause() {
37 | super.onPause()
38 | nfcAdapter?.disableReaderMode(this)
39 | }
40 |
41 | override fun onTagDiscovered(tag: Tag) {
42 | val nfcF = NfcF.get(tag)
43 | nfcF?.let {
44 | val idm = it.tag.id
45 | val sysCode = it.systemCode.toHexString(false)
46 | val idmString = idm.toHexString(false)
47 |
48 | Log.d(TAG, "[TAG FOUND] IDm: $idmString, SysCode: $sysCode")
49 | runOnUiThread {
50 | Toast.makeText(this, "IDm: $idmString, SysCode: $sysCode", Toast.LENGTH_LONG).show()
51 | }
52 | val intent = Intent("moe.tqlwsl.aicemu.READ_CARD")
53 | intent.putExtra("Card_IDm", idmString)
54 | sendBroadcast(intent)
55 | finish()
56 | }
57 | }
58 | fun ByteArray.toHexString(hasSpace: Boolean = true) = this.joinToString("") {
59 | (it.toInt() and 0xFF).toString(16).padStart(2, '0').uppercase() + if (hasSpace) " " else ""
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
22 |
23 |
24 |
25 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AICEmu
3 | Rename
4 | Delete
5 | Show IDm
6 | Hide IDm
7 | Compatible Mode on
8 | Compatible Mode off
9 | Settings
10 | Error!
11 | AIC Card
12 | 012EXXXXXXXXXXXX
13 | Please Touch Your AIC Card!
14 | NFC Not Supported...
15 | NFC Currently Off...
16 | add card
17 | edit card
18 | close
19 | EmuCard
20 | Setting
21 | HCE-F Not Supported...
22 | HCE-F Supported!
23 | Unlocker Working!
24 | Unlocker Not Working...
25 | Unlocker Error!
26 | Pmmtool Working!
27 | Pmmtool Not Working...
28 | Hook Failed...
29 | Emulating %1$s...(common)
30 | Emulating %1$s...(compatible)
31 | Common
32 | Compatible
33 | Add Test Card
34 | Emulate AIC Card
35 | Change PMm with Pmmtool
36 | Modified By Xein\nOriginally Made By wlt233 With ❤\ntqlwsl.moe | 2709684396
37 | 1.0.0\nBased on 1.0-Beta4.1 (2023.09.01)
38 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
37 |
38 |
41 |
44 |
47 |
50 |
53 |
54 |
58 |
59 |
60 |
61 |
64 |
65 |
69 |
70 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
31 |
41 |
42 |
51 |
52 |
63 |
64 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/FelicaCard.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 |
4 | import android.content.Context
5 | import android.util.Log
6 | import com.google.gson.Gson
7 | import com.google.gson.JsonSyntaxException
8 | import com.google.gson.reflect.TypeToken
9 | import java.io.BufferedReader
10 | import java.io.IOException
11 | import java.io.InputStreamReader
12 |
13 | class FelicaCard(context: Context, IDm: String) {
14 | private var cardData = mutableMapOf()
15 | private val gson = Gson()
16 | private val TAG = "AICEmu"
17 | private val fileName = "felica_template.json"
18 |
19 | init {
20 | Log.d(TAG, "Load Felica Data from assets/$fileName")
21 | val fileContent = readAssetFile(context, fileName)
22 | val mutableBlock = object : TypeToken>() {}.type
23 | try {
24 | val jsonData = gson.fromJson>(fileContent, mutableBlock)
25 | if (jsonData != null) {
26 | cardData = jsonData
27 | val block82 = ByteArray(16)
28 | val IDmBytes = IDm.decodeHex()
29 | System.arraycopy(IDmBytes, 0, block82, 0, 8)
30 | cardData["82"] = block82.toHexString()
31 | }
32 | Log.d(TAG,"[Felica Card Data]")
33 | Log.d(TAG, "=".repeat(53))
34 | for (en in cardData.entries) {
35 | Log.d(TAG,"[${en.key}]: ${en.value}")
36 | }
37 | Log.d(TAG, "=".repeat(53))
38 | } catch (e: IOException) {
39 | Log.e(TAG, "assets/$fileName Read Error")
40 | } catch (e: JsonSyntaxException) {
41 | Log.e(TAG, "assets/$fileName Syntax Error")
42 | }
43 | }
44 |
45 | // utils
46 | fun String.decodeHex(): ByteArray =
47 | this.replace(" ", "").chunked(2).map { it.toInt(16).toByte() }.toByteArray()
48 | fun ByteArray.toHexString(hasSpace: Boolean = true) = this.joinToString("") {
49 | (it.toInt() and 0xFF).toString(16).padStart(2, '0').uppercase() + if (hasSpace) " " else ""
50 | }
51 |
52 | fun readBlock(id: Byte): ByteArray? {
53 | var idStr = (id.toInt() and 0xFF).toString(16).padStart(2, '0')
54 | Log.d("HCEFService", "read block [$idStr]")
55 | var blockStr = cardData[idStr]
56 | if (blockStr == null) {
57 | idStr = idStr.uppercase()
58 | blockStr = cardData[idStr]
59 | }
60 | if (blockStr != null) {
61 | val blockData = blockStr.decodeHex()
62 | val resp = ByteArray(16)
63 | System.arraycopy(blockData, 0, resp, 0, blockData.size)
64 | return resp
65 | }
66 | Log.e(TAG, "Invalid Block $idStr")
67 | return null
68 | }
69 |
70 | fun writeBlock(id: Byte, data: ByteArray) { }
71 |
72 | private fun readAssetFile(context: Context, fileName: String): String {
73 | val stringBuilder = StringBuilder()
74 | val assetManager = context.assets
75 | val inputStream = assetManager.open(fileName)
76 | val bufferedReader = BufferedReader(InputStreamReader(inputStream))
77 | bufferedReader.forEachLine { line ->
78 | stringBuilder.append(line)
79 | stringBuilder.append("\n")
80 | }
81 | bufferedReader.close()
82 | inputStream.close()
83 | return stringBuilder.toString()
84 | }
85 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/card.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
26 |
27 |
37 |
38 |
48 |
49 |
59 |
60 |
61 |
71 |
72 |
84 |
85 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/EmuCard.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import android.nfc.cardemulation.HostNfcFService
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.widget.Toast
7 |
8 | class EmuCard : HostNfcFService() {
9 | private lateinit var card: FelicaCard
10 | private var TAG: String = "AICEmu-EmuCard"
11 |
12 | // byte utils
13 | fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
14 | fun ByteArray.toHexString(hasSpace: Boolean = true) = this.joinToString("") {
15 | (it.toInt() and 0xFF).toString(16).padStart(2, '0').uppercase() + if (hasSpace) " " else ""
16 | }
17 |
18 | // resp utils
19 | fun packResponse(respType: Byte, nfcid2: ByteArray, payload: ByteArray): ByteArray {
20 | var resp = ByteArray(1) + respType + nfcid2 + payload
21 | resp[0] = resp.size.toByte()
22 | return resp
23 | }
24 |
25 | override fun processNfcFPacket(commandPacket: ByteArray, extras: Bundle?): ByteArray? {
26 | val commandHexStr = commandPacket.toHexString()
27 | Log.d(TAG, "[RECV] processNfcFPacket received $commandHexStr")
28 | Toast.makeText(this, "[RECV] received $commandHexStr", Toast.LENGTH_LONG).show()
29 |
30 | if (commandPacket.size < 1 + 1 + 8 || (commandPacket.size.toByte() != commandPacket[0])) {
31 | Log.e(TAG, "processNfcFPacket: packet size error")
32 | return null
33 | }
34 |
35 | val nfcid2 = ByteArray(8)
36 | System.arraycopy(commandPacket, 2, nfcid2, 0, 8)
37 | // val myNfcid2 =
38 | // byteArrayOfInts(0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
39 | // if (!Arrays.equals(myNfcid2, nfcid2)) {
40 | // Log.e(TAG, "processNfcFPacket: nfcid2 error")
41 | // return null
42 | // }
43 |
44 | if (commandPacket[1] == 0x06.toByte()) { // READ BLK
45 | val blockNum = commandPacket[13].toInt()
46 | val payload = ByteArray(2 + 1 + 16 * blockNum)
47 | payload[2] = blockNum.toByte()
48 | for (i in 0 until blockNum) {
49 | val id = commandPacket[13 + 2 + 2 * i]
50 | card.readBlock(id)?.let { System.arraycopy(it, 0, payload, 3 + 16 * i, 16) }
51 | }
52 | val resp = packResponse(0x07.toByte(), nfcid2, payload)
53 | val respHexStr = resp.toHexString()
54 | Log.d(TAG, "[READ] received [$commandHexStr] | send [$respHexStr]")
55 | //Toast.makeText(this, "Scanned", Toast.LENGTH_LONG).show()
56 | Toast.makeText(this, "[READ] received [$commandHexStr]\nsend [$respHexStr]", Toast.LENGTH_LONG).show()
57 | return resp
58 | }
59 | else if (commandPacket[1] == 0x08.toByte()) { // WRITE BLK // not implemented
60 | val payload = ByteArray(2)
61 | val resp = packResponse(0x09.toByte(), nfcid2, payload)
62 | val respHexStr = resp.toHexString()
63 | Log.d(TAG, "[WRITE] received [$commandHexStr] | send [$respHexStr]")
64 | Toast.makeText(this, "[WRITE] received [$commandHexStr]\nsend [$respHexStr]", Toast.LENGTH_LONG).show()
65 | return resp
66 | }
67 |
68 |
69 | return byteArrayOfInts(0x04, 0x11, 0x45, 0x14)
70 | // sendResponsePacket(byteArrayOfInts(0x04, 0x11, 0x45, 0x14))
71 | // return null
72 | }
73 |
74 | override fun onCreate() {
75 | Log.d(TAG, "onCreate NFCF")
76 | super.onCreate()
77 | val globalVar = this.applicationContext as GlobalVar
78 | card = FelicaCard(this, globalVar.IDm)
79 | Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show()
80 | }
81 |
82 | override fun onDestroy() {
83 | Log.d(TAG, "onDestroy NFCF")
84 | super.onDestroy()
85 | Toast.makeText(this, "onDestroy", Toast.LENGTH_LONG).show()
86 | }
87 | override fun onDeactivated(reason: Int) {
88 | Log.d(TAG, "onDeactivated NFCF")
89 | Toast.makeText(this, "onDeactivated", Toast.LENGTH_LONG).show()
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/SettingActivity.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.SharedPreferences
5 | import android.content.pm.PackageManager
6 | import android.graphics.Color
7 | import android.os.Bundle
8 | import android.util.Log
9 | import android.widget.TextView
10 | import androidx.appcompat.app.AppCompatActivity
11 | import androidx.appcompat.widget.SwitchCompat
12 | import java.lang.reflect.Method
13 |
14 | class SettingActivity : AppCompatActivity() {
15 | private lateinit var prefs: SharedPreferences
16 | private var isHCEFSupported: Boolean = false
17 | private var isHCEFUnlocked: Boolean = false
18 | private var pmmtoolStatus: String? = ""
19 | val TAG: String = "AICEmu"
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_setting)
24 |
25 | isHCEFSupported = packageManager.hasSystemFeature(
26 | PackageManager.FEATURE_NFC_HOST_CARD_EMULATION_NFCF)
27 | Log.d(TAG, "[Setting] isHCEFSupported: $isHCEFSupported")
28 |
29 | val textHCEF = findViewById(R.id.hcef_support_text)
30 | textHCEF.setText((when (isHCEFSupported) {
31 | true -> R.string.HCEF_support_true
32 | else -> R.string.HCEF_support_false
33 | }))
34 | textHCEF.setTextColor((if (isHCEFSupported) Color.GREEN else Color.RED))
35 |
36 | val textUnlocker = findViewById(R.id.unlocker_work_text)
37 | if (isHCEFSupported) {
38 | try {
39 | val globalVar = this.applicationContext as GlobalVar
40 | isHCEFUnlocked = globalVar.isHCEFUnlocked
41 | Log.d(TAG, "[Setting] isHCEFUnlocked: $isHCEFUnlocked")
42 | textUnlocker.setText((when (isHCEFUnlocked) {
43 | true -> R.string.Unlocker_work_true
44 | else -> R.string.Unlocker_work_false
45 | }))
46 | textUnlocker.setTextColor((if (isHCEFUnlocked) Color.GREEN else Color.RED))
47 | } catch (e: Exception) {
48 | e.printStackTrace()
49 | textUnlocker.setText(R.string.Unlocker_work_error)
50 | textUnlocker.setTextColor(Color.RED)
51 | }
52 |
53 | val textPmmtool = findViewById(R.id.pmmtool_work_text)
54 | pmmtoolStatus = getProperty("tmp.AICEmu.pmmtool")
55 | Log.d(TAG, "[Setting] loadPmmtool: $pmmtoolStatus")
56 | textPmmtool.setText((when (pmmtoolStatus) {
57 | "" -> R.string.Pmmtool_work_hook_failed
58 | "0" -> R.string.Pmmtool_work_false
59 | else -> R.string.Pmmtool_work_true
60 | }))
61 | textPmmtool.setTextColor((if (pmmtoolStatus == "1") Color.GREEN else Color.RED))
62 |
63 | val pmmtoolSwitch = findViewById(R.id.pmmtool_switch)
64 | pmmtoolSwitch.isChecked = java.lang.Boolean.parseBoolean(getProperty("tmp.AICEmu.loadPmmtool"))
65 | pmmtoolSwitch.setOnCheckedChangeListener { _, isChecked ->
66 | setProperty("tmp.AICEmu.loadPmmtool", isChecked.toString())
67 | setProperty("tmp.AICEmu.pmmtool", "")
68 | Runtime.getRuntime().exec(arrayOf("su", "-c", "kill -9 $(su -c pidof com.android.nfc)"))
69 | }
70 | }
71 | }
72 |
73 | companion object {
74 | @SuppressLint("PrivateApi")
75 | fun getProperty(key: String?): String? {
76 | return try {
77 | val c = Class.forName("android.os.SystemProperties")
78 | val set: Method = c.getMethod("get", String::class.java)
79 | set.invoke(c, key)?.toString()
80 | } catch (e: java.lang.Exception) {
81 | Log.d("AICEmu", "getProperty exception")
82 | e.printStackTrace()
83 | ""
84 | }
85 | }
86 |
87 | @SuppressLint("PrivateApi")
88 | fun setProperty(key: String?, value: String?) {
89 | try {
90 | val c = Class.forName("android.os.SystemProperties")
91 | val set: Method = c.getMethod("set", String::class.java, String::class.java)
92 | set.invoke(c, key, value)?.toString()
93 | } catch (e: java.lang.Exception) {
94 | Log.d("AICEmu", "setProperty exception")
95 | e.printStackTrace()
96 | }
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/app/src/main/jni/dobby/dobby.h:
--------------------------------------------------------------------------------
1 | #ifndef dobby_h
2 | #define dobby_h
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #include
9 | #include
10 |
11 | typedef uintptr_t addr_t;
12 | typedef uint32_t addr32_t;
13 | typedef uint64_t addr64_t;
14 |
15 | typedef void *dobby_dummy_func_t;
16 | typedef void *asm_func_t;
17 |
18 | #if defined(__arm__)
19 | typedef struct {
20 | uint32_t dummy_0;
21 | uint32_t dummy_1;
22 |
23 | uint32_t dummy_2;
24 | uint32_t sp;
25 |
26 | union {
27 | uint32_t r[13];
28 | struct {
29 | uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12;
30 | } regs;
31 | } general;
32 |
33 | uint32_t lr;
34 | } DobbyRegisterContext;
35 | #elif defined(__arm64__) || defined(__aarch64__)
36 | #define ARM64_TMP_REG_NDX_0 17
37 |
38 | typedef union _FPReg {
39 | __int128_t q;
40 | struct {
41 | double d1;
42 | double d2;
43 | } d;
44 | struct {
45 | float f1;
46 | float f2;
47 | float f3;
48 | float f4;
49 | } f;
50 | } FPReg;
51 |
52 | // register context
53 | typedef struct {
54 | uint64_t dmmpy_0; // dummy placeholder
55 | uint64_t sp;
56 |
57 | uint64_t dmmpy_1; // dummy placeholder
58 | union {
59 | uint64_t x[29];
60 | struct {
61 | uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22,
62 | x23, x24, x25, x26, x27, x28;
63 | } regs;
64 | } general;
65 |
66 | uint64_t fp;
67 | uint64_t lr;
68 |
69 | union {
70 | FPReg q[32];
71 | struct {
72 | FPReg q0, q1, q2, q3, q4, q5, q6, q7;
73 | // [!!! READ ME !!!]
74 | // for Arm64, can't access q8 - q31, unless you enable full floating-point register pack
75 | FPReg q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29,
76 | q30, q31;
77 | } regs;
78 | } floating;
79 | } DobbyRegisterContext;
80 | #elif defined(_M_IX86) || defined(__i386__)
81 | typedef struct _RegisterContext {
82 | uint32_t dummy_0;
83 | uint32_t esp;
84 |
85 | uint32_t dummy_1;
86 | uint32_t flags;
87 |
88 | union {
89 | struct {
90 | uint32_t eax, ebx, ecx, edx, ebp, esp, edi, esi;
91 | } regs;
92 | } general;
93 |
94 | } DobbyRegisterContext;
95 | #elif defined(_M_X64) || defined(__x86_64__)
96 | typedef struct {
97 | uint64_t dummy_0;
98 | uint64_t rsp;
99 |
100 | union {
101 | struct {
102 | uint64_t rax, rbx, rcx, rdx, rbp, rsp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15;
103 | } regs;
104 | } general;
105 |
106 | uint64_t dummy_1;
107 | uint64_t flags;
108 | } DobbyRegisterContext;
109 | #endif
110 |
111 | #define install_hook_name(name, fn_ret_t, fn_args_t...) \
112 | static fn_ret_t fake_##name(fn_args_t); \
113 | static fn_ret_t (*orig_##name)(fn_args_t); \
114 | /* __attribute__((constructor)) */ static void install_hook_##name(void *sym_addr) { \
115 | DobbyHook(sym_addr, (dobby_dummy_func_t)fake_##name, (dobby_dummy_func_t *)&orig_##name); \
116 | return; \
117 | } \
118 | fn_ret_t fake_##name(fn_args_t)
119 |
120 | // memory code patch
121 | int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size);
122 |
123 | // function inline hook
124 | int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func);
125 |
126 | // dynamic binary instruction instrument
127 | // for Arm64, can't access q8 - q31, unless enable full floating-point register pack
128 | typedef void (*dobby_instrument_callback_t)(void *address, DobbyRegisterContext *ctx);
129 | int DobbyInstrument(void *address, dobby_instrument_callback_t pre_handler);
130 |
131 | // destroy and restore code patch
132 | int DobbyDestroy(void *address);
133 |
134 | const char *DobbyGetVersion();
135 |
136 | // symbol resolver
137 | void *DobbySymbolResolver(const char *image_name, const char *symbol_name);
138 |
139 | // import table replace
140 | int DobbyImportTableReplace(char *image_name, char *symbol_name, dobby_dummy_func_t fake_func,
141 | dobby_dummy_func_t *orig_func);
142 |
143 | // for arm, Arm64, try use b xxx instead of ldr absolute indirect branch
144 | // for x86, x64, always use absolute indirect jump
145 | void dobby_enable_near_branch_trampoline();
146 | void dobby_disable_near_branch_trampoline();
147 |
148 | #ifdef __cplusplus
149 | }
150 | #endif
151 |
152 | #endif
153 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/xp.java:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.app.ActivityManager;
6 | import android.app.Application;
7 | import android.content.Context;
8 | import android.content.SharedPreferences;
9 | import android.content.pm.PackageInfo;
10 | import android.content.pm.PackageManager;
11 | import android.os.Bundle;
12 | import android.util.Log;
13 |
14 | import androidx.annotation.NonNull;
15 |
16 | import java.util.List;
17 |
18 | import de.robv.android.xposed.IXposedHookLoadPackage;
19 | import de.robv.android.xposed.XC_MethodHook;
20 | import de.robv.android.xposed.XC_MethodReplacement;
21 | import de.robv.android.xposed.XSharedPreferences;
22 | import de.robv.android.xposed.XposedBridge;
23 | import de.robv.android.xposed.XposedHelpers;
24 | import de.robv.android.xposed.callbacks.XC_LoadPackage;
25 |
26 | public class xp implements IXposedHookLoadPackage {
27 | private final String TAG = "AICEmu-Xposed";
28 | ClassLoader mclassloader = null;
29 | Context mcontext = null;
30 |
31 | @Override
32 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
33 |
34 | XposedBridge.log("[AICEmu-PmmHook] In " + lpparam.packageName);
35 |
36 | if (!lpparam.packageName.equals("com.android.nfc"))
37 | return;
38 |
39 | XposedHelpers.findAndHookMethod("android.nfc.cardemulation.NfcFCardEmulation", lpparam.classLoader,
40 | "isValidNfcid2", String.class, new XC_MethodReplacement() {
41 | @Override
42 | protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
43 | return true;
44 | }
45 | });
46 |
47 | XposedHelpers.findAndHookMethod("com.android.nfc.NfcApplication",
48 | lpparam.classLoader, "onCreate", new XC_MethodHook() {
49 | @Override
50 | protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
51 | XposedBridge.log("[AICEmu-PmmHook] Inside com.android.nfc.NfcApplication#onCreate");
52 | super.beforeHookedMethod(param);
53 | Application application = (Application) param.thisObject;
54 | mcontext = application.getApplicationContext();
55 | XposedBridge.log("[AICEmu-PmmHook] Got context");
56 | }
57 | });
58 |
59 | XposedHelpers.findAndHookMethod("android.nfc.cardemulation.NfcFCardEmulation",
60 | lpparam.classLoader, "isValidSystemCode", String.class, new XC_MethodHook() {
61 | @Override
62 | protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
63 | XposedBridge.log("[AICEmu-PmmHook] Inside android.nfc.cardemulation.NfcFCardEmulation#isValidSystemCode");
64 |
65 | mclassloader = mcontext.getClassLoader();
66 | XposedBridge.log("[AICEmu-PmmHook] Got classloader");
67 | String path = getSoPath();
68 | XposedBridge.log("[AICEmu-PmmHook] So path = " + path);
69 | try {
70 | Boolean needLoadPmmtool = Boolean.parseBoolean(GetProp("tmp.AICEmu.loadPmmtool"));
71 | XposedBridge.log("[AICEmu-PmmHook] loadPmmtool: " + needLoadPmmtool.toString());
72 | if (needLoadPmmtool && !path.equals("")) {
73 | XposedBridge.log("[AICEmu-PmmHook] Start injecting libpmm.so");
74 | XposedHelpers.callMethod(Runtime.getRuntime(), "nativeLoad", path, mclassloader);
75 | XposedBridge.log("[AICEmu-PmmHook] Injected libpmm.so");
76 | }
77 | } catch (Exception e) {
78 | XposedBridge.log(e);
79 | e.printStackTrace();
80 | }
81 |
82 | // Unlocker
83 | param.setResult(true);
84 | }
85 | });
86 |
87 | XposedBridge.log("[AICEmu-PmmHook] Hook succeeded!!!");
88 | }
89 |
90 | @NonNull
91 | private String getSoPath() {
92 | try {
93 | PackageManager pm = mcontext.getPackageManager();
94 | List pkgList = pm.getInstalledPackages(0);
95 | if (pkgList.size() > 0) {
96 | for (PackageInfo pi: pkgList) {
97 | if (pi.applicationInfo.publicSourceDir.contains("moe.tqlwsl.aicemu")) {
98 | return pi.applicationInfo.publicSourceDir.replace("base.apk", "lib/arm64/libpmm.so");
99 | }
100 | }
101 | }
102 | } catch (Exception e) {
103 | XposedBridge.log(e);
104 | e.printStackTrace();
105 | }
106 | return "";
107 | }
108 |
109 | @NonNull
110 | private static String GetProp(String key) {
111 | try {
112 | Class c = Class.forName("android.os.SystemProperties");
113 | return c.getMethod("get", String.class).invoke(c, key).toString();
114 | } catch (Exception e) {
115 | XposedBridge.log("[AICEmu-PmmHook] " + e);
116 | return "";
117 | }
118 | }
119 |
120 | private static void SetProp(String key, String value) {
121 | try {
122 | Class c = Class.forName("android.os.SystemProperties");
123 | c.getMethod("set", String.class, String.class).invoke(c, key, value);
124 | } catch (Exception e) {
125 | XposedBridge.log("[AICEmu-PmmHook] " + e);
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/jni/main.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include "main.h"
3 | #include
4 | #include
5 |
6 | #define _uintval(p) reinterpret_cast(p)
7 | #define _ptr(p) reinterpret_cast(p)
8 | #define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
9 | #define _align_down(x, n) ((x) & -(n))
10 | #define _page_size 4096
11 | #define _page_align(n) _align_up(static_cast(n), _page_size)
12 | #define _ptr_align(x) _ptr(_align_down(reinterpret_cast(x), _page_size))
13 | #define _make_rwx(p, n) ::mprotect(_ptr_align(p), \
14 | _page_align(_uintval(p) + n) != _page_align(_uintval(p)) ? _page_align(n) + _page_size : _page_align(n), \
15 | PROT_READ | PROT_WRITE | PROT_EXEC)
16 |
17 | char buff[30];
18 | char pmm_str[30];
19 | char target_pmm[8] = {0x00, 0xf1, 0x00, 0x00, 0x00, 0x01, 0x43, 0x00};
20 |
21 | void *new_func(u_int8_t tlv_list_len, u_int8_t *p_tlv_list, int app_init) {
22 |
23 | // lovely android 10 arm64 :3
24 | // read more: https://source.android.com/docs/core/tests/debug/native-crash?hl=zh-cn#xom
25 | _make_rwx(p_tlv_list, _page_size);
26 |
27 | bool found = false;
28 |
29 | // if (tlv_list_len == 0x1d) { // hardcoded arg pattern
30 | // 40 0a [syscode] [IDm] 53 02 01 00 55 01 01 51 08 [PMm]
31 | // if (tlv_list_len == 0x1b) { // another type hardcoded arg pattern
32 | // 40 12 [syscode] [IDm] [PMm] 53 02 01 00 55 01 01
33 |
34 | // handmade hexdump
35 | //for (int i = 0x0; i < 0x10; ++i)
36 | // sprintf(buff + i * 3, "%02x ", *(char *)(p_tlv_list + i));
37 | //__android_log_print(6, "AICEmu-pmmtool", "[%x]: %s", p_tlv_list, buff);
38 | //for (int i = 0x0; i < 0x10; ++i)
39 | // sprintf(buff + i * 3, "%02x ", *(char *)(p_tlv_list + 0x10 + i));
40 | //__android_log_print(6, "AICEmu-pmmtool", "[%x]: %s", p_tlv_list + 0x10, buff);
41 |
42 | for (int i = 0x0; i < 0x20; ++i) {
43 | // i know kinda stupid, but easier to read :(
44 | auto type = *(p_tlv_list + i);
45 | auto len = *(p_tlv_list + i + 1);
46 | auto p_value = p_tlv_list + i + 2;
47 |
48 | // 51 = NFC_PMID_LF_T3T_PMM
49 | // 08 = NCI_PARAM_LEN_LF_T3T_PMM
50 | // look for 51 08 (set pmm command) for type 0x1d
51 | if (type == 0x51 && len == 0x08) {
52 | __android_log_print(6, "AICEmu-pmmtool", "hook _Z23nfa_dm_check_set_confighPhb arg0->%x arg1->%x", tlv_list_len, p_tlv_list);
53 |
54 | __android_log_print(6, "AICEmu-pmmtool", "Set Pmm Found... hooking", pmm_str);
55 |
56 | found = true;
57 |
58 | for (int j = 0; j < 8; ++j)
59 | sprintf(pmm_str + j * 3, "%02x ", *(char *)(p_value + j));
60 | __android_log_print(6, "AICEmu-pmmtool", "[1] old PMm: %s", pmm_str);
61 |
62 | // set
63 | for (int j = 0; j < 8; ++j)
64 | *(char *)(p_value + j) = target_pmm[j];
65 |
66 | for (int j = 0; j < 8; ++j)
67 | sprintf(pmm_str + j * 3, "%02x ", *(char *)(p_value + j));
68 | __android_log_print(6, "AICEmu-pmmtool", "[1] new PMm: %s", pmm_str);
69 | }
70 |
71 | // look for FF FF FF FF FF FF FF FF (pmm itself)
72 | if (*(char *)(p_tlv_list + i) == 0xff && *(char *)(p_tlv_list + i + 1) == 0xff
73 | && *(char *)(p_tlv_list + i + 2) == 0xff && *(char *)(p_tlv_list + i + 3) == 0xff
74 | && *(char *)(p_tlv_list + i + 4) == 0xff && *(char *)(p_tlv_list + i + 5) == 0xff
75 | && *(char *)(p_tlv_list + i + 6) == 0xff && *(char *)(p_tlv_list + i + 7) == 0xff) {
76 |
77 | found = true;
78 |
79 | for (int j = 0; j < 8; ++j)
80 | sprintf(pmm_str + j * 3, "%02x ", *(char *)(p_tlv_list + i + j));
81 | __android_log_print(6, "AICEmu-pmmtool", "[2] old PMm: %s", pmm_str);
82 |
83 | // set
84 | for (int j = 0; j < 8; ++j)
85 | *(char *)(p_tlv_list + i + j) = target_pmm[j];
86 |
87 | for (int j = 0; j < 8; ++j)
88 | sprintf(pmm_str + j * 3, "%02x ", *(char *)(p_tlv_list + i + j));
89 | __android_log_print(6, "AICEmu-pmmtool", "[2] new PMm: %s", pmm_str);
90 | }
91 | }
92 |
93 | void *result = old_func(tlv_list_len, p_tlv_list, app_init);
94 |
95 | if (found) {
96 | __android_log_print(6, "AICEmu-pmmtool", "hook result -> %x", result);
97 |
98 | // double check?
99 | for (int i = 0x0; i < 0x20; ++i) {
100 | // i know kinda stupid, but easier to read :(
101 | auto type = *(p_tlv_list + i);
102 | auto len = *(p_tlv_list + i + 1);
103 | auto p_value = p_tlv_list + i + 2;
104 |
105 | // 51 = NFC_PMID_LF_T3T_PMM
106 | // 08 = NCI_PARAM_LEN_LF_T3T_PMM
107 | // look for 51 08 (set pmm command) for type 0x1d
108 | if (type == 0x51 && len == 0x08) {
109 | for (int j = 0; j < 8; ++j)
110 | sprintf(pmm_str + j * 3, "%02x ", *(char *)(p_value + j));
111 | __android_log_print(6, "AICEmu-pmmtool", "[1] returned Pmm: %s", pmm_str);
112 | }
113 | }
114 |
115 | if (result != 0)
116 | __system_property_set("tmp.AICEmu.pmmtool", "0");
117 | else
118 | __system_property_set("tmp.AICEmu.pmmtool", "1");
119 | }
120 |
121 | return result;
122 | }
123 |
124 | jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
125 | __android_log_print(6, "AICEmu-pmmtool", "Inside JNI_OnLoad");
126 | __system_property_set("tmp.AICEmu.pmmtool", "0");
127 |
128 | JNIEnv *env = nullptr;
129 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) {
130 | //void *func_addr = DobbySymbolResolver("libnfc-nci.so", "_Z23nfa_dm_check_set_confighPhb");
131 | void *func_addr = DobbySymbolResolver(NULL, "_Z23nfa_dm_check_set_confighPhb");
132 | __android_log_print(6, "AICEmu-pmmtool", "_Z23nfa_dm_check_set_confighPhb addr->%x", func_addr);
133 | DobbyHook(func_addr, (void *) new_func, (void **) &old_func);
134 | __android_log_print(6, "AICEmu-pmmtool", "Dobby hooked");
135 | return JNI_VERSION_1_6;
136 | }
137 | return 0;
138 | }
139 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/moe/tqlwsl/aicemu/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package moe.tqlwsl.aicemu
2 |
3 | import android.app.AlertDialog
4 | import android.app.PendingIntent
5 | import android.content.*
6 | import android.nfc.NfcAdapter
7 | import android.nfc.cardemulation.CardEmulation
8 | import android.nfc.cardemulation.NfcFCardEmulation
9 | import android.os.Bundle
10 | import android.util.Log
11 | import android.view.*
12 | import android.widget.*
13 | import androidx.appcompat.app.AppCompatActivity
14 | import androidx.appcompat.widget.Toolbar
15 | import androidx.cardview.widget.CardView
16 | import androidx.core.view.WindowCompat
17 | import com.google.android.material.floatingactionbutton.FloatingActionButton
18 | import com.google.gson.Gson
19 | import com.google.gson.JsonSyntaxException
20 | import com.google.gson.reflect.TypeToken
21 | import moe.tqlwsl.aicemu.databinding.ActivityMainBinding
22 | import java.io.File
23 | import java.io.IOException
24 |
25 | internal data class Card(val name: String, val idm: String)
26 |
27 | class MainActivity : AppCompatActivity() {
28 | private lateinit var binding: ActivityMainBinding
29 | private lateinit var toolbar: Toolbar
30 | private lateinit var readCardBroadcastReceiver: ReadCardBroadcastReceiver
31 | private lateinit var nfcFComponentName: ComponentName
32 | private lateinit var jsonFile: File
33 | private lateinit var prefs: SharedPreferences
34 | private var nfcAdapter: NfcAdapter? = null
35 | private var nfcFCardEmulation: NfcFCardEmulation? = null
36 | private var nfcPendingIntent: PendingIntent? = null
37 | private var cards = mutableListOf()
38 | private val gson = Gson()
39 | private val cardsJsonPath = "card.json"
40 | private val TAG = "AICEmu"
41 | private var showCardID: Boolean = false
42 | private var compatibleID: Boolean = false
43 | private var currentCardId: Int = -1
44 |
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | WindowCompat.setDecorFitsSystemWindows(window, false)
47 | super.onCreate(savedInstanceState)
48 |
49 | // ui
50 | binding = ActivityMainBinding.inflate(layoutInflater)
51 | setContentView(binding.root)
52 | toolbar = findViewById(R.id.toolbar)
53 | setSupportActionBar(toolbar)
54 | findViewById(R.id.fab_add_card).setOnClickListener {
55 | val readCardIntent = Intent(this, ReadCard::class.java)
56 | startActivity(readCardIntent)
57 | }
58 |
59 | // load json file
60 | jsonFile = File(filesDir, cardsJsonPath)
61 | loadCards()
62 |
63 | // read card callback
64 | readCardBroadcastReceiver = ReadCardBroadcastReceiver()
65 | val intentFilter = IntentFilter("moe.tqlwsl.aicemu.READ_CARD")
66 | registerReceiver(readCardBroadcastReceiver, intentFilter)
67 |
68 | // check nfc
69 | nfcAdapter = NfcAdapter.getDefaultAdapter(this)
70 | if (nfcAdapter == null) {
71 | Log.e(TAG, "NFC not supported")
72 | AlertDialog.Builder(this)
73 | .setTitle(R.string.error).setMessage(R.string.nfc_not_supported).setCancelable(true).show()
74 | return
75 | }
76 | if (!nfcAdapter!!.isEnabled) {
77 | Log.e(TAG, "NFC is off")
78 | AlertDialog.Builder(this)
79 | .setTitle(R.string.error).setMessage(R.string.nfc_not_on).setCancelable(true).show()
80 | return
81 | }
82 |
83 | // set default payment app
84 | var cardEmulation = CardEmulation.getInstance(nfcAdapter)
85 | val componentName = ComponentName(applicationContext, ApduService::class.java)
86 | val isDefault =
87 | cardEmulation.isDefaultServiceForCategory(componentName, CardEmulation.CATEGORY_PAYMENT)
88 | if (!isDefault) {
89 | val intent = Intent(CardEmulation.ACTION_CHANGE_DEFAULT)
90 | intent.putExtra(CardEmulation.EXTRA_CATEGORY, CardEmulation.CATEGORY_PAYMENT)
91 | intent.putExtra(CardEmulation.EXTRA_SERVICE_COMPONENT, componentName)
92 | startActivity(intent)
93 | }
94 |
95 | // add pendingintent in order not to read tag at home
96 | val intent = Intent(this, javaClass).apply {
97 | addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
98 | }
99 | nfcPendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
100 |
101 |
102 | // load hcefservice
103 | nfcFCardEmulation = NfcFCardEmulation.getInstance(nfcAdapter)
104 | nfcFComponentName = ComponentName(
105 | "moe.tqlwsl.aicemu",
106 | "moe.tqlwsl.aicemu.EmuCard"
107 | )
108 |
109 | // setting prefs
110 | prefs = applicationContext.getSharedPreferences("AICEmu", Context.MODE_PRIVATE)
111 | currentCardId = prefs.getInt("currentCardId", -1)
112 | compatibleID = prefs.getBoolean("compatibleID", false)
113 | val compatibleButton: Button = findViewById(R.id.button_compatible)
114 | compatibleButton.text = (if (compatibleID) {
115 | getString(R.string.mode_compatible)
116 | }
117 | else {
118 | getString(R.string.mode_commmon)
119 | })
120 | compatibleButton.setOnClickListener {
121 | switchCompatible()
122 | }
123 | }
124 |
125 | override fun onResume() {
126 | super.onResume()
127 | if (nfcPendingIntent != null) {
128 | nfcAdapter?.enableForegroundDispatch(this, nfcPendingIntent, null, null)
129 | }
130 | nfcFCardEmulation?.enableService(this, nfcFComponentName)
131 | emuCurrentCard()
132 | }
133 |
134 | override fun onPause() {
135 | super.onPause()
136 | nfcAdapter?.disableForegroundDispatch(this)
137 | nfcFCardEmulation?.disableService(this)
138 | }
139 |
140 | override fun onNewIntent(intent: Intent?) {
141 | super.onNewIntent(intent)
142 | }
143 |
144 | override fun onDestroy() {
145 | super.onDestroy()
146 | unregisterReceiver(readCardBroadcastReceiver)
147 | }
148 |
149 | override fun onCreateOptionsMenu(menu: Menu?): Boolean {
150 | menuInflater.inflate(R.menu.toolbar_menu, menu)
151 | return super.onCreateOptionsMenu(menu)
152 | }
153 |
154 | override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
155 | menu?.findItem(R.id.toolbar_menu_compatible)?.setTitle(if (compatibleID) {
156 | R.string.compatible_off
157 | } else {
158 | R.string.compatible_on
159 | })
160 | return super.onPrepareOptionsMenu(menu)
161 | }
162 |
163 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
164 | return when (item.itemId) {
165 | R.id.toolbar_menu_hide_id -> {
166 | showCardID = !showCardID
167 | item.setTitle(if (showCardID) {
168 | R.string.hide_idm
169 | }
170 | else {
171 | R.string.show_idm
172 | })
173 | checkCardIDShadow()
174 | true
175 | }
176 | R.id.toolbar_menu_compatible -> {
177 | switchCompatible()
178 | true
179 | }
180 | R.id.toolbar_menu_add_test_card -> {
181 | addCard("Test Card", "012e000000114514")
182 | true
183 | }
184 | R.id.toolbar_menu_settings -> {
185 | // Toast.makeText(applicationContext, "还没做完()\nUnder constuction...", Toast.LENGTH_LONG).show()
186 | val settingIntent = Intent(this, SettingActivity::class.java)
187 | startActivity(settingIntent)
188 | true
189 | }
190 | else -> super.onOptionsItemSelected(item)
191 | }
192 | }
193 |
194 | private fun switchCompatible() {
195 | compatibleID = !compatibleID
196 | val compatibleButton: Button = findViewById(R.id.button_compatible)
197 | compatibleButton.text = (if (compatibleID) {
198 | getString(R.string.mode_compatible)
199 | }
200 | else {
201 | getString(R.string.mode_commmon)
202 | })
203 | val editor = prefs.edit()
204 | editor.putBoolean("compatibleID", compatibleID)
205 | editor.apply()
206 | emuCurrentCard()
207 | }
208 |
209 | private fun checkCardIDShadow() {
210 | val cardsLayout: ViewGroup = findViewById(R.id.mainList)
211 | for (i in 0 until cardsLayout.childCount) {
212 | val child = cardsLayout.getChildAt(i)
213 | if (child is CardView) {
214 | val idView = child.findViewById(R.id.card_id)
215 | // val idShadowView = child.findViewById(R.id.card_id_shadow)
216 | if (showCardID) {
217 | idView.visibility = View.VISIBLE
218 | // idShadowView.visibility = View.GONE
219 | }
220 | else {
221 | idView.visibility = View.GONE
222 | // idShadowView.visibility = View.VISIBLE
223 | }
224 | }
225 | }
226 | }
227 |
228 | private fun showCardMenu(v: View, cardView: View) {
229 | val popupMenu = PopupMenu(this, v)
230 | popupMenu.inflate(R.menu.card_menu)
231 | popupMenu.setOnMenuItemClickListener { item: MenuItem ->
232 | when (item.itemId) {
233 | R.id.card_menu_rename -> {
234 | val textView = cardView.findViewById(R.id.card_name)
235 | val editText = cardView.findViewById(R.id.card_name_edit)
236 | textView.visibility = View.GONE
237 | editText.visibility = View.VISIBLE
238 | editText.setText(textView.text)
239 | editText.requestFocus()
240 | editText.setOnFocusChangeListener { _, hasFocus ->
241 | if (!hasFocus) {
242 | textView.text = editText.text
243 | textView.visibility = View.VISIBLE
244 | editText.visibility = View.GONE
245 | saveCards()
246 | }
247 | }
248 | true
249 | }
250 | R.id.card_menu_delete -> {
251 | val parentLayout = cardView.parent as? ViewGroup
252 | parentLayout?.removeView(cardView)
253 | saveCards()
254 | true
255 | }
256 | else -> false
257 | }
258 | }
259 | popupMenu.show()
260 | }
261 |
262 |
263 | private fun setIDm(idm: String): Boolean {
264 | nfcFCardEmulation?.disableService(this)
265 | val resultIdm = nfcFCardEmulation?.setNfcid2ForService(nfcFComponentName, idm)
266 | nfcFCardEmulation?.enableService(this, nfcFComponentName)
267 | return resultIdm == true
268 | }
269 |
270 | private fun setSys(sys: String): Boolean {
271 | nfcFCardEmulation?.disableService(this)
272 | val resultSys = nfcFCardEmulation?.registerSystemCodeForService(nfcFComponentName, sys)
273 | nfcFCardEmulation?.enableService(this, nfcFComponentName)
274 | return resultSys == true
275 | }
276 |
277 | private fun emuCurrentCard() {
278 | if (currentCardId != -1) {
279 | val cardsLayout: ViewGroup = findViewById(R.id.mainList)
280 | val currentCard = cardsLayout.getChildAt(currentCardId)
281 | if (currentCard != null) {
282 | emuCardview(currentCard)
283 | }
284 | else {
285 | currentCardId = -1
286 | val editor = prefs.edit()
287 | editor.putInt("currentCardId", currentCardId)
288 | editor.apply()
289 | }
290 | }
291 | }
292 |
293 | private fun emuCardview(cardView: View) {
294 | val cardIDmTextView = cardView.findViewById(R.id.card_id)
295 | val cardNameTextView = cardView.findViewById(R.id.card_name)
296 | emuCard(cardIDmTextView.text.toString(), cardNameTextView.text.toString())
297 |
298 | val mainLayout: ViewGroup = findViewById(R.id.mainList)
299 | for (i in 0 until mainLayout.childCount) {
300 | val child = mainLayout.getChildAt(i)
301 | if (child is CardView) {
302 | val emuMark: ImageButton = child.findViewById(R.id.card_emu_mark_on)
303 | emuMark.visibility = View.GONE
304 | if (child == cardView) {
305 | currentCardId = i
306 | val editor = prefs.edit()
307 | editor.putInt("currentCardId", currentCardId)
308 | editor.apply()
309 | }
310 | }
311 | }
312 | val emuMark: ImageButton = cardView.findViewById(R.id.card_emu_mark_on)
313 | emuMark.visibility = View.VISIBLE
314 | }
315 |
316 | private fun emuCard(cardId: String, cardName: String) {
317 | val globalVar = this.applicationContext as GlobalVar
318 | globalVar.IDm = cardId
319 |
320 | var resultIdm = if (compatibleID) {
321 | // hardcoded idm for specific model e.g. Samsung S8
322 | // idm needs to start with 02, or syscode won't be added to polling ack
323 | // konmai reader reads this idm while sbga reader does not check this
324 | setIDm("02fe001145141919")
325 | }
326 | else {
327 | setIDm(globalVar.IDm)
328 | }
329 |
330 | val resultSys = setSys("88B4") // hardcoded syscode for sbga
331 | globalVar.isHCEFUnlocked = resultSys
332 |
333 | if (!resultIdm) {
334 | Toast.makeText(applicationContext, "Error IDm", Toast.LENGTH_LONG).show()
335 | }
336 | if (!resultSys) {
337 | Toast.makeText(applicationContext, "Error Sys", Toast.LENGTH_LONG).show()
338 | }
339 | if (resultIdm && resultSys) {
340 | if (compatibleID) {
341 | Toast.makeText(applicationContext, getString(R.string.Emulating_compatible, cardName), Toast.LENGTH_LONG).show()
342 | }
343 | else {
344 | Toast.makeText(applicationContext, getString(R.string.Emulating_common, cardName), Toast.LENGTH_LONG).show()
345 | }
346 | }
347 | }
348 |
349 | private fun addCard(name: String, IDm: String?) {
350 | val cardsLayout: ViewGroup = findViewById(R.id.mainList)
351 | val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
352 | val cardView = inflater.inflate(R.layout.card, cardsLayout, false)
353 | cardsLayout.addView(cardView)
354 | val nameTextView = cardView.findViewById(R.id.card_name)
355 | nameTextView.text = name
356 | val IDmTextView = cardView.findViewById(R.id.card_id)
357 | IDmTextView.text = IDm
358 | checkCardIDShadow()
359 | val menuButton: ImageButton = cardView.findViewById(R.id.card_menu_button)
360 | menuButton.setOnClickListener {
361 | showCardMenu(it, cardView)
362 | }
363 | val emuButton: ImageButton = cardView.findViewById(R.id.card_emu_mark)
364 | emuButton.setOnClickListener {
365 | emuCardview(cardView)
366 | }
367 | cardView.setOnTouchListener { v, _ ->
368 | val editText = v.findViewById(R.id.card_name_edit)
369 | editText.clearFocus()
370 | v.performClick()
371 | true
372 | }
373 | }
374 |
375 | private fun loadCards() {
376 | val mutableListCard = object : TypeToken>() {}.type
377 | try {
378 | val jsonCards = gson.fromJson>(jsonFile.readText(), mutableListCard)
379 | if (jsonCards != null) {
380 | cards = jsonCards
381 | }
382 | } catch (e: IOException) {
383 | Log.e(TAG, "$cardsJsonPath Read Error")
384 | } catch (e: JsonSyntaxException) {
385 | Log.e(TAG, "$cardsJsonPath Syntax Error")
386 | }
387 | val mainLayout: ViewGroup = findViewById(R.id.mainList)
388 | mainLayout.removeAllViews()
389 | for (card in cards) {
390 | addCard(card.name, card.idm)
391 | }
392 | }
393 |
394 | private fun saveCards() {
395 | cards.clear()
396 | val mainLayout: ViewGroup = findViewById(R.id.mainList)
397 | for (i in 0 until mainLayout.childCount) {
398 | val child = mainLayout.getChildAt(i)
399 | if (child is CardView) {
400 | val nameView = child.findViewById(R.id.card_name)
401 | val idView = child.findViewById(R.id.card_id)
402 | cards.add(Card(nameView.text.toString(), idView.text as String))
403 | }
404 | }
405 | try {
406 | jsonFile.writeText(gson.toJson(cards).toString())
407 | } catch (e: IOException) {
408 | Log.e(TAG, "$cardsJsonPath Write Error")
409 | }
410 | }
411 |
412 | inner class ReadCardBroadcastReceiver : BroadcastReceiver() {
413 | override fun onReceive(context: Context, intent: Intent) {
414 | val idmString = intent.getStringExtra("Card_IDm")
415 | addCard("AIC Card", idmString)
416 | saveCards()
417 | }
418 | }
419 | }
--------------------------------------------------------------------------------