├── android ├── settings_aar.gradle ├── gradle.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── chameleon.png │ │ │ │ └── launch_background.xml │ │ │ ├── xml │ │ │ │ └── device_filter.xml │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ ├── java │ │ │ └── tw │ │ │ │ └── kgame │ │ │ │ ├── crapto1 │ │ │ │ ├── Nonce.java │ │ │ │ ├── Crypto1State.java │ │ │ │ ├── Span.java │ │ │ │ ├── MfKey.java │ │ │ │ ├── Crypto1.java │ │ │ │ └── Crapto1.java │ │ │ │ └── chameleonminiapp │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── build.gradle └── settings.gradle ├── l10n.yaml ├── Crapto1Native ├── README.md ├── Crapto1Native.csproj ├── build.cmd ├── Library.cs └── .gitignore ├── lib ├── main.dart ├── l10n │ ├── app_zh.arb │ ├── app_zh_CN.arb │ ├── app_zh_TW.arb │ ├── app_ja.arb │ ├── app_en.arb │ ├── app_localizations_ja.dart │ ├── app_localizations_en.dart │ ├── app_localizations_zh.dart │ └── app_localizations.dart ├── views │ ├── home │ │ ├── deviceInfoDialog.dart │ │ ├── homePage.dart │ │ └── slotView.dart │ └── settings │ │ ├── languagePage.dart │ │ └── settingsPage.dart ├── myApp.dart ├── services │ ├── ffiService.dart │ ├── settings.dart │ ├── xmodem.dart │ ├── chameleonClient.dart │ └── crapto1.dart └── view_models │ └── slotViewModel.dart ├── .metadata ├── test └── widget_test.dart ├── .gitignore ├── README.md ├── pubspec.yaml └── LICENSE /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /l10n.yaml: -------------------------------------------------------------------------------- 1 | arb-dir: lib/l10n 2 | template-arb-file: app_en.arb 3 | -------------------------------------------------------------------------------- /Crapto1Native/README.md: -------------------------------------------------------------------------------- 1 | ## Environment Requirements 2 | 3 | - .NET 8 SDK 4 | - bflat tool 5 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | android.enableJetifier=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/chameleon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kgamecarter/ChameleonMiniApp/HEAD/android/app/src/main/res/drawable/chameleon.png -------------------------------------------------------------------------------- /android/app/src/main/res/xml/device_filter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/Nonce.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | public class Nonce { 4 | public long nt; 5 | 6 | public long nr; 7 | 8 | public long ar; 9 | } 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jan 23 13:23:02 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip 7 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'services/settings.dart'; 3 | import 'myApp.dart'; 4 | 5 | void main() async { 6 | WidgetsFlutterBinding.ensureInitialized(); 7 | final settings = Settings(); 8 | await settings.load(); 9 | runApp(MyApp(settings: settings)); 10 | } 11 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = '../build' 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(':app') 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /Crapto1Native/Crapto1Native.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Crapto1Native/build.cmd: -------------------------------------------------------------------------------- 1 | dotnet publish 2 | rmdir /s /q obj 3 | bflat build --no-reflection --no-stacktrace-data --no-globalization --no-exception-messages -Os --no-pie --separate-symbols --os:linux --arch:arm64 --libc:bionic -r:bin\Release\net8.0\publish\Crapto1Sharp.dll 4 | del libCrapto1Native.so.dwo 5 | 6 | if not exist ..\android\app\src\main\jniLibs\arm64-v8a\ mkdir ..\android\app\src\main\jniLibs\arm64-v8a\ 7 | 8 | move libCrapto1Native.so ..\android\app\src\main\jniLibs\arm64-v8a\libCrapto1Native.so -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Crapto1Native/Library.cs: -------------------------------------------------------------------------------- 1 | using Crapto1Sharp; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Crapto1Native; 5 | 6 | public static class Library 7 | { 8 | [UnmanagedCallersOnly(EntryPoint = "MfKey32")] 9 | public static unsafe ulong MfKey32(uint uid, int cnt, Nonce* nonces) 10 | { 11 | var arr = new Nonce[cnt]; 12 | for (int i = 0; i < cnt; i++) 13 | arr[i] = nonces[i]; 14 | return MfKey.MfKey32(uid, arr); 15 | } 16 | 17 | [UnmanagedCallersOnly(EntryPoint = "MfKey64")] 18 | public static ulong MfKey64(uint uid, uint nt, uint nr, uint ar, uint at) 19 | => MfKey.MfKey64(uid, nt, nr, ar, at); 20 | } 21 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | } 19 | 20 | plugins { 21 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 22 | id "com.android.application" version "8.6.0" apply false 23 | id "org.jetbrains.kotlin.android" version "2.1.0" apply false 24 | } 25 | 26 | include ":app" 27 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/Crypto1State.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | public class Crypto1State { 4 | public long odd; 5 | public long even; 6 | 7 | byte bit(long v, int n) { 8 | return (byte)(v >> n & 1); 9 | } 10 | 11 | byte beBit(long v, int n) { 12 | return bit(v, n ^ 24); 13 | } 14 | 15 | public Crypto1State(long odd, long even) { 16 | this.odd = odd; 17 | this.even = even; 18 | } 19 | 20 | public Crypto1State(long key) { 21 | for (int i = 47; i > 0; i -= 2) 22 | { 23 | odd = odd << 1 | bit(key, (i - 1) ^ 7); 24 | even = even << 1 | bit(key, i ^ 7); 25 | } 26 | } 27 | 28 | public long getLfsr() { 29 | long lfsr = 0; 30 | for (int i = 23; i >= 0; --i) 31 | { 32 | lfsr = lfsr << 1 | bit(odd, i ^ 3); 33 | lfsr = lfsr << 1 | bit(even, i ^ 3); 34 | } 35 | return lfsr; 36 | } 37 | } -------------------------------------------------------------------------------- /lib/l10n/app_zh.arb: -------------------------------------------------------------------------------- 1 | { 2 | "apply": "套用", 3 | "attacking": "解密中", 4 | "button": "按鈕", 5 | "clear": "清除", 6 | "close": "關閉", 7 | "confirmClear": "確定要清除嗎?", 8 | "crapto1Implementation": "Crapto1 實作", 9 | "crapto1Dart": "Dart 單執行緒", 10 | "crapto1Java": "Java 多執行緒", 11 | "crapto1Online": "線上 (伺服器可能離線)", 12 | "crapto1Native": ".NET8 NativeAOT (僅限 ARM64)", 13 | "deviceInfo": "裝置資訊", 14 | "disconnect": "斷線", 15 | "download": "下載", 16 | "downloading": "下載中", 17 | "chameleonMiniApp": "Chameleon Mini App", 18 | "english": "英文", 19 | "generalSetting": "一般設定", 20 | "language": "語言", 21 | "longPressButton": "長按按鈕", 22 | "memorySize": "記憶體大小", 23 | "mfkey32": "解密", 24 | "mode": "模式", 25 | "notAvailable": "不可用", 26 | "refresh": "刷新", 27 | "reset": "重置", 28 | "slot": "槽位", 29 | "selectLanguage": "選擇語言", 30 | "settings": "設定", 31 | "traditionalChinese": "正體中文", 32 | "uid": "UID", 33 | "upload": "上傳", 34 | "usbDeviceNotFound": "找不到 USB 裝置", 35 | "usbDisconnected": "USB 已斷線", 36 | "systemDefault": "系統預設", 37 | "simplifiedChinese": "簡體中文", 38 | "japanese": "日文" 39 | } 40 | -------------------------------------------------------------------------------- /lib/l10n/app_zh_CN.arb: -------------------------------------------------------------------------------- 1 | { 2 | "apply": "应用", 3 | "attacking": "解密中", 4 | "button": "按钮", 5 | "clear": "清除", 6 | "close": "关闭", 7 | "confirmClear": "确定要清除吗?", 8 | "crapto1Implementation": "Crapto1 实现", 9 | "crapto1Dart": "Dart 单线程", 10 | "crapto1Java": "Java 多线程", 11 | "crapto1Online": "在线 (服务器可能离线)", 12 | "crapto1Native": ".NET8 NativeAOT (仅限 ARM64)", 13 | "deviceInfo": "设备信息", 14 | "disconnect": "断开连接", 15 | "download": "下载", 16 | "downloading": "下载中", 17 | "chameleonMiniApp": "Chameleon Mini App", 18 | "english": "英语", 19 | "generalSetting": "通用设置", 20 | "language": "语言", 21 | "longPressButton": "长按按钮", 22 | "memorySize": "内存大小", 23 | "mfkey32": "解密", 24 | "mode": "模式", 25 | "notAvailable": "不可用", 26 | "refresh": "刷新", 27 | "reset": "重置", 28 | "slot": "卡槽", 29 | "selectLanguage": "选择语言", 30 | "settings": "设置", 31 | "traditionalChinese": "繁体中文", 32 | "uid": "UID", 33 | "upload": "上传", 34 | "usbDeviceNotFound": "未找到 USB 设备", 35 | "usbDisconnected": "USB 已断开", 36 | "systemDefault": "系统默认", 37 | "simplifiedChinese": "简体中文", 38 | "japanese": "日语" 39 | } 40 | -------------------------------------------------------------------------------- /lib/l10n/app_zh_TW.arb: -------------------------------------------------------------------------------- 1 | { 2 | "apply": "套用", 3 | "attacking": "解密中", 4 | "button": "按鈕", 5 | "clear": "清除", 6 | "close": "關閉", 7 | "confirmClear": "確定要清除嗎?", 8 | "crapto1Implementation": "Crapto1 實作", 9 | "crapto1Dart": "Dart 單執行緒", 10 | "crapto1Java": "Java 多執行緒", 11 | "crapto1Online": "線上 (伺服器可能離線)", 12 | "crapto1Native": ".NET8 NativeAOT (僅限 ARM64)", 13 | "deviceInfo": "裝置資訊", 14 | "disconnect": "斷線", 15 | "download": "下載", 16 | "downloading": "下載中", 17 | "chameleonMiniApp": "Chameleon Mini App", 18 | "english": "英文", 19 | "generalSetting": "一般設定", 20 | "language": "語言", 21 | "longPressButton": "長按按鈕", 22 | "memorySize": "記憶體大小", 23 | "mfkey32": "解密", 24 | "mode": "模式", 25 | "notAvailable": "不可用", 26 | "refresh": "刷新", 27 | "reset": "重置", 28 | "slot": "槽位", 29 | "selectLanguage": "選擇語言", 30 | "settings": "設定", 31 | "traditionalChinese": "正體中文", 32 | "uid": "UID", 33 | "upload": "上傳", 34 | "usbDeviceNotFound": "找不到 USB 裝置", 35 | "usbDisconnected": "USB 已斷線", 36 | "systemDefault": "系統預設", 37 | "simplifiedChinese": "簡體中文", 38 | "japanese": "日文" 39 | } 40 | -------------------------------------------------------------------------------- /lib/l10n/app_ja.arb: -------------------------------------------------------------------------------- 1 | { 2 | "apply": "適用", 3 | "attacking": "解読中", 4 | "button": "ボタン", 5 | "clear": "クリア", 6 | "close": "閉じる", 7 | "confirmClear": "クリアしてもよろしいですか?", 8 | "crapto1Implementation": "Crapto1 実装", 9 | "crapto1Dart": "Dart シングルスレッド", 10 | "crapto1Java": "Java マルチスレッド", 11 | "crapto1Online": "オンライン (サーバーオフラインの可能性あり)", 12 | "crapto1Native": ".NET8 NativeAOT (ARM64のみ)", 13 | "deviceInfo": "デバイス情報", 14 | "disconnect": "切断", 15 | "download": "ダウンロード", 16 | "downloading": "ダウンロード中", 17 | "chameleonMiniApp": "Chameleon Mini App", 18 | "english": "英語", 19 | "generalSetting": "一般設定", 20 | "language": "言語", 21 | "longPressButton": "ボタン長押し", 22 | "memorySize": "メモリサイズ", 23 | "mfkey32": "解読", 24 | "mode": "モード", 25 | "notAvailable": "利用不可", 26 | "refresh": "更新", 27 | "reset": "リセット", 28 | "slot": "スロット", 29 | "selectLanguage": "言語を選択", 30 | "settings": "設定", 31 | "traditionalChinese": "繁体字中国語", 32 | "uid": "UID", 33 | "upload": "アップロード", 34 | "usbDeviceNotFound": "USBデバイスが見つかりません", 35 | "usbDisconnected": "USBが切断されました", 36 | "systemDefault": "システムデフォルト", 37 | "simplifiedChinese": "簡体字中国語", 38 | "japanese": "日本語" 39 | } 40 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:chameleon_mini_app/views/home/homePage.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(HomePage()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/views/home/deviceInfoDialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 3 | 4 | class DeviceInfoDialog extends StatelessWidget { 5 | const DeviceInfoDialog(this.version, this.rssi, {Key? key}) : super(key: key); 6 | 7 | final String version, rssi; 8 | 9 | void _reset(BuildContext context) { 10 | Navigator.of(context).pop('reset'); 11 | } 12 | 13 | void _disconnect(BuildContext context) { 14 | Navigator.of(context).pop('disconnect'); 15 | } 16 | 17 | void _close(BuildContext context) { 18 | Navigator.of(context).pop(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return AlertDialog( 24 | title: Text(AppLocalizations.of(context)!.deviceInfo), 25 | content: Text('$version\nRSSI : $rssi'), 26 | actions: [ 27 | TextButton( 28 | child: Text(AppLocalizations.of(context)!.reset), 29 | onPressed: () => _reset(context), 30 | ), 31 | TextButton( 32 | child: Text(AppLocalizations.of(context)!.disconnect), 33 | onPressed: () => _disconnect(context), 34 | ), 35 | TextButton( 36 | child: Text(AppLocalizations.of(context)!.close), 37 | onPressed: () => _close(context), 38 | ), 39 | ], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/l10n/app_en.arb: -------------------------------------------------------------------------------- 1 | { 2 | "apply": "Apply", 3 | "attacking": "Attacking", 4 | "button": "Button", 5 | "clear": "Clear", 6 | "close": "Close", 7 | "confirmClear": "Are you sure you want to clear?", 8 | "crapto1Implementation": "Crapto1 Implementation", 9 | "crapto1Dart": "Dart with Single-Thread", 10 | "crapto1Java": "Java with Multi-Thread", 11 | "crapto1Online": "Online (Server maybe offline)", 12 | "crapto1Native": ".NET8 NativeAOT (ARM64 only)", 13 | "deviceInfo": "Device Info", 14 | "disconnect": "Disconnect", 15 | "download": "Download", 16 | "downloading": "Downloading", 17 | "chameleonMiniApp": "Chameleon Mini App", 18 | "english": "English", 19 | "generalSetting": "General Setting", 20 | "language": "Language", 21 | "longPressButton": "Long Press Button", 22 | "memorySize": "Memory Size", 23 | "mfkey32": "mfkey32", 24 | "mode": "Mode", 25 | "notAvailable": "N/A", 26 | "refresh": "Refresh", 27 | "reset": "Reset", 28 | "slot": "Slot", 29 | "selectLanguage": "Select Language", 30 | "settings": "Settings", 31 | "traditionalChinese": "Traditional Chinese", 32 | "uid": "UID", 33 | "upload": "Upload", 34 | "usbDeviceNotFound": "USB device not found", 35 | "usbDisconnected": "USB Disconnected", 36 | "systemDefault": "System Default", 37 | "simplifiedChinese": "Simplified Chinese", 38 | "japanese": "Japanese" 39 | } 40 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/Span.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | import java.util.Arrays; 4 | 5 | public class Span { 6 | long[] array; 7 | int offset; 8 | int length; 9 | 10 | public Span(long[] array) { 11 | this(array, 0, array.length); 12 | } 13 | 14 | public Span(long[] array, int offset, int length) { 15 | this.array = array; 16 | this.offset = offset; 17 | this.length = length; 18 | } 19 | 20 | public long get(int i) { 21 | return array[i + offset]; 22 | } 23 | 24 | public void set(int i, long v) { 25 | array[i + offset] = v; 26 | } 27 | 28 | public Span slice(int start) { 29 | return new Span(array, offset + start, length - start); 30 | } 31 | 32 | public Span slice(int start, int length) { 33 | return new Span(array, offset + start, length); 34 | } 35 | 36 | public int binarySearch() { 37 | int start = 0, stop = this.length - 1, mid; 38 | long val = this.get(stop) & 0xff000000L; 39 | while (start != stop) 40 | if (this.get(start + (mid = (stop - start) >> 1)) > val) 41 | stop = start + mid; 42 | else 43 | start += mid + 1; 44 | return start; 45 | } 46 | 47 | public void sort() { 48 | Arrays.sort(array, offset, offset + length); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/myApp.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 3 | 4 | import 'services/settings.dart'; 5 | import 'views/home/homePage.dart'; 6 | import 'views/settings/settingsPage.dart'; 7 | import 'views/settings/languagePage.dart'; 8 | 9 | class MyApp extends StatelessWidget { 10 | final Settings settings; 11 | 12 | const MyApp({Key? key, required this.settings}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return AnimatedBuilder( 17 | animation: settings, 18 | builder: (context, child) { 19 | return GestureDetector( 20 | onTap: () => FocusManager.instance.primaryFocus?.unfocus(), 21 | child: MaterialApp( 22 | localizationsDelegates: AppLocalizations.localizationsDelegates, 23 | supportedLocales: AppLocalizations.supportedLocales, 24 | title: 'Chameleon Mini App', 25 | onGenerateTitle: (context) => 26 | AppLocalizations.of(context)!.chameleonMiniApp, 27 | theme: ThemeData( 28 | colorScheme: ColorScheme.fromSeed(seedColor: Colors.lime), 29 | useMaterial3: true, 30 | ), 31 | home: HomePage(), 32 | routes: { 33 | SettingsPage.name: (BuildContext context) => new SettingsPage(), 34 | LanguagePage.name: (BuildContext context) => new LanguagePage(), 35 | }, 36 | locale: settings.locale, 37 | ), 38 | ); 39 | }, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/services/ffiService.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | import 'dart:io'; 3 | import 'package:ffi/ffi.dart'; 4 | import 'package:chameleon_mini_app/services/crapto1.dart'; 5 | 6 | final class LibCrapto1Nonce extends Struct { 7 | @Uint32() 8 | external int nt; 9 | @Uint32() 10 | external int nr; 11 | @Uint32() 12 | external int ar; 13 | } 14 | 15 | final _crapto1Lib = Platform.isAndroid 16 | ? DynamicLibrary.open('libCrapto1Native.so') 17 | : DynamicLibrary.process(); 18 | final _libCrapto1MfKey32 = _crapto1Lib.lookupFunction< 19 | Uint64 Function(Uint32, Uint32, Pointer), 20 | int Function(int, int, Pointer)>('MfKey32'); 21 | final _libCrapto1MfKey64 = _crapto1Lib.lookupFunction< 22 | Uint64 Function(Uint32, Uint32, Uint32, Uint32, Uint32), 23 | int Function(int, int, int, int, int)>('MfKey64'); 24 | 25 | Future mfKey32Native(int uid, List nonces) async { 26 | print('Native mfKey32'); 27 | final key = using((arena) { 28 | final nativeNonces = arena(nonces.length); 29 | for (var i = 0; i < nonces.length; i++) { 30 | nativeNonces[i].nt = nonces[i].nt; 31 | nativeNonces[i].nr = nonces[i].nr; 32 | nativeNonces[i].ar = nonces[i].ar; 33 | } 34 | return _libCrapto1MfKey32(uid, nonces.length, nativeNonces); 35 | }); 36 | return key.toRadixString(16).toUpperCase().padLeft(12, '0'); 37 | } 38 | 39 | Future mfKey64Native(int uid, int nt, int nr, int ar, int at) async { 40 | print('Native mfKey64'); 41 | final key = _libCrapto1MfKey64(uid, nt, nr, ar, at); 42 | return key.toRadixString(16).toUpperCase().padLeft(12, '0'); 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | android/key.properties 73 | ios/Flutter/flutter_export_environment.sh 74 | .flutter-plugins-dependencies 75 | android/app/src/main/jniLibs/arm64-v8a/*.so 76 | -------------------------------------------------------------------------------- /lib/views/settings/languagePage.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 3 | 4 | class LanguagePage extends StatefulWidget { 5 | static const String name = '/Settings'; 6 | 7 | LanguagePage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _LanguagePageState createState() => _LanguagePageState(); 11 | } 12 | 13 | class _LanguagePageState extends State { 14 | Function() _pop(Object value) { 15 | return () => Navigator.of(context).pop(value); 16 | } 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return new Scaffold( 21 | appBar: new AppBar( 22 | title: new Text(AppLocalizations.of(context)!.selectLanguage), 23 | ), 24 | body: ListView( 25 | children: [ 26 | ListTile( 27 | title: Text(AppLocalizations.of(context)!.systemDefault), 28 | subtitle: const Text('default'), 29 | onTap: _pop('default'), 30 | ), 31 | ListTile( 32 | title: Text(AppLocalizations.of(context)!.english), 33 | subtitle: const Text('en'), 34 | onTap: _pop(const Locale('en')), 35 | ), 36 | ListTile( 37 | title: Text(AppLocalizations.of(context)!.traditionalChinese), 38 | subtitle: const Text('zh-Hant-TW'), 39 | onTap: _pop( 40 | const Locale.fromSubtags( 41 | languageCode: "zh", 42 | scriptCode: "Hant", 43 | countryCode: "TW", 44 | ), 45 | ), 46 | ), 47 | ListTile( 48 | title: Text(AppLocalizations.of(context)!.simplifiedChinese), 49 | subtitle: const Text('zh-Hans-CN'), 50 | onTap: _pop( 51 | const Locale.fromSubtags( 52 | languageCode: "zh", 53 | scriptCode: "Hans", 54 | countryCode: "CN", 55 | ), 56 | ), 57 | ), 58 | ListTile( 59 | title: Text(AppLocalizations.of(context)!.japanese), 60 | subtitle: const Text('ja'), 61 | onTap: _pop(const Locale('ja')), 62 | ), 63 | ], 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/MfKey.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | import java.util.*; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | import java.util.Spliterator; 7 | import java.util.Spliterators; 8 | import java.util.stream.*; 9 | 10 | public class MfKey { 11 | static long swapEndian(long x) { 12 | x = (x >> 8 & 0xff00ffL) | (x & 0xff00ffL) << 8; 13 | x &= 0xFFFFFFFFL; 14 | x = x >> 16 | x << 16; 15 | x &= 0xFFFFFFFFL; 16 | return x; 17 | } 18 | 19 | static long prngSuccessor(long x, int n) { 20 | x = swapEndian(x); 21 | while (n-- > 0) 22 | x = x >> 1 | ((x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) & 1) << 31; 23 | return swapEndian(x); 24 | } 25 | 26 | public static long mfKey32(long uid, List nonces) { 27 | Nonce nonce = nonces.get(0); 28 | nonces.remove(0); 29 | long p640 = prngSuccessor(nonce.nt, 64); 30 | System.out.println(Long.toHexString(p640)); 31 | List list = Crapto1.lfsrRecovery32(nonce.ar ^ p640, 0); 32 | System.out.println(list.size()); 33 | List keys = new CopyOnWriteArrayList<>(); 34 | Spliterator sp = Spliterators.spliterator(list, Spliterator.CONCURRENT); 35 | StreamSupport.stream(sp, true).forEach(s -> { 36 | Crapto1 crapto1 = new Crapto1(s); 37 | crapto1.lfsrRollbackWord(0, false); 38 | crapto1.lfsrRollbackWord(nonce.nr, true); 39 | crapto1.lfsrRollbackWord(uid ^ nonce.nt, false); 40 | boolean allPass = true; 41 | Crypto1 crypto1 = new Crypto1(new Crypto1State(0, 0)); 42 | for (Nonce n : nonces) { 43 | crypto1.state.odd = s.odd; 44 | crypto1.state.even = s.even; 45 | crypto1.crypto1Word(uid ^ n.nt, false); 46 | crypto1.crypto1Word(n.nr, true); 47 | long p641 = prngSuccessor(n.nt, 64); 48 | if (n.ar != (crypto1.crypto1Word(0, false) ^ p641)) { 49 | allPass = false; 50 | break; 51 | } 52 | } 53 | if (allPass) 54 | keys.add(s.getLfsr()); 55 | }); 56 | return keys.size() == 1 ? keys.get(0) : -1; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chameleon Mini App 2 | 3 | A Android GUI for the ChameleonMini device. 4 | 5 | * **[Chameleon Mini App on Google Play](https://play.google.com/store/apps/details?id=tw.kgame.chameleonminiapp)** 6 | 7 | ## Features 8 | 9 | * **USB OTG Connection**: Seamlessly connect to your ChameleonMini RevE device. 10 | * **Slot Management**: Configure and control all 8 slots. 11 | * **Card Emulation**: View and modify UID, Memory Size, and Mode for each slot. 12 | * **Button Configuration**: Customize Button and Long Press Button actions. 13 | * **Data Transfer**: Upload and Download card dump files. 14 | * **Tools**: Includes `mfkey32` attack support. 15 | * **Device Info**: View firmware version and other device details. 16 | * **Localization**: Supports English and Traditional Chinese. 17 | 18 | ## Screenshots 19 | ![1](https://lh3.googleusercontent.com/oNf-5xB5Wljd4djUl1dxC2osHZqO588JDIZbdUlnpS7mxFH_4X1jhVKthCYR6-pJH-w=w1440-h620-rw) ![2](https://lh3.googleusercontent.com/WWI59xq7yiwfioDavACOhPKoRg3rdUfD-g7TLnvwXXgpV6ubmk0RppVQe0DaNfPlcA=w1440-h620-rw) 20 | ![3](https://lh3.googleusercontent.com/Vew9KLMw9SFsscQY0ur3QA8uDQjO0bjbH53y6vHTs9MZp_LiKuDQ1dOufqmcbvmdyNG2=w1440-h620-rw) 21 | 22 | ## Requirements 23 | 24 | * ChameleonMini RevE 25 | * Android 4.4 or later 26 | * USB OTG cable 27 | 28 | ## Installation 29 | 30 | ### Google Play 31 | Download the app directly from the [Google Play Store](https://play.google.com/store/apps/details?id=tw.kgame.chameleonminiapp). 32 | 33 | ### Build from Source 34 | 1. Ensure you have the [Flutter SDK](https://flutter.dev/docs/get-started/install) installed. 35 | 2. Clone this repository. 36 | 3. Run `flutter pub get` to install dependencies. 37 | 4. Connect your Android device. 38 | 5. Run `flutter run` to build and install the app. 39 | 40 | ## Usage 41 | 42 | 1. Connect your ChameleonMini RevE to your Android phone using a USB OTG cable. 43 | 2. Open the Chameleon Mini App. 44 | 3. Grant USB permissions if prompted. 45 | 4. The app should automatically detect and connect to the device. 46 | 5. Swipe between tabs to manage different slots. 47 | 6. Use the menu to access Settings, Device Info, or perform actions like Upload/Download. 48 | 49 | ## ChameleonMini Compatible Firmware 50 | 51 | * [Origin Firmware](https://github.com/kgamecarter/ChameleonMiniApp/files/3639628/20170729.zip) *recommended 52 | * [ChameleonMini-rebooted](https://github.com/iceman1001/ChameleonMini-rebooted) 53 | 54 | ## Video 55 | [![.](https://i.ytimg.com/vi/WoU58GzxsAY/mqdefault.jpg)](https://youtu.be/WoU58GzxsAY) 56 | 57 | ## License 58 | 59 | This project is licensed under the terms of the LICENSE file found in the root of this repository. 60 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: chameleon_mini_app 2 | description: ChameleonMini RevE Android Client. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 0.5.0+33 11 | 12 | environment: 13 | sdk: ^3.10.1 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | flutter_localizations: 19 | sdk: flutter 20 | intl: ^0.20.2 21 | 22 | # The following adds the Cupertino Icons font to your application. 23 | # Use with the CupertinoIcons class for iOS style icons. 24 | cupertino_icons: ^1.0.8 25 | usb_serial: ^0.5.2 26 | shared_preferences: ^2.5.4 27 | flutter_file_dialog: ^3.0.3 28 | nfc_manager: ^4.1.1 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://www.dartlang.org/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | generate: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.io/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.io/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.io/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 16 | 20 | 28 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | -------------------------------------------------------------------------------- /lib/services/settings.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | enum Crapto1Implementation { Dart, Java, Online, Native } 7 | 8 | class Settings extends ChangeNotifier { 9 | static final Settings _instance = Settings._internal(); 10 | 11 | factory Settings() { 12 | return _instance; 13 | } 14 | 15 | Settings._internal(); 16 | 17 | SharedPreferences? _prefs; 18 | 19 | Locale? _locale; 20 | Locale? get locale => _locale; 21 | 22 | set locale(Locale? value) { 23 | if (_locale == value) return; 24 | _locale = value; 25 | _saveLocale(); 26 | notifyListeners(); 27 | } 28 | 29 | Crapto1Implementation? _crapto1Implementation; 30 | Crapto1Implementation? get crapto1Implementation => _crapto1Implementation; 31 | 32 | set crapto1Implementation(Crapto1Implementation? value) { 33 | if (value == null || _crapto1Implementation == value) return; 34 | _crapto1Implementation = value; 35 | _saveCrapto1Implementation(); 36 | notifyListeners(); 37 | } 38 | 39 | Future load() async { 40 | _prefs = await SharedPreferences.getInstance(); 41 | 42 | // Load Locale 43 | final String? localeStr = _prefs?.getString('locale'); 44 | if (localeStr == 'en') { 45 | _locale = const Locale('en'); 46 | } else if (localeStr == 'zh_Hant_TW') { 47 | _locale = const Locale.fromSubtags( 48 | languageCode: "zh", 49 | scriptCode: "Hant", 50 | countryCode: "TW", 51 | ); 52 | } else { 53 | _locale = null; 54 | } 55 | 56 | // Load Crapto1Implementation 57 | int? v = _prefs?.getInt('crapto1Implementation'); 58 | if (v == null) { 59 | if (Platform.isAndroid) { 60 | if (Platform.version.contains('arm64')) { 61 | v = Crapto1Implementation.Native.index; 62 | } else { 63 | v = Crapto1Implementation.Java.index; 64 | } 65 | } else { 66 | v = Crapto1Implementation.Dart.index; 67 | } 68 | } 69 | 70 | if (v >= 0 && v < Crapto1Implementation.values.length) { 71 | _crapto1Implementation = Crapto1Implementation.values[v]; 72 | } else { 73 | _crapto1Implementation = Crapto1Implementation.Dart; 74 | } 75 | 76 | notifyListeners(); 77 | } 78 | 79 | Future _saveLocale() async { 80 | if (_prefs == null) return; 81 | if (_locale != null) { 82 | await _prefs!.setString('locale', _locale.toString()); 83 | } else { 84 | await _prefs!.remove('locale'); 85 | } 86 | } 87 | 88 | Future _saveCrapto1Implementation() async { 89 | if (_prefs == null || _crapto1Implementation == null) return; 90 | await _prefs!.setInt( 91 | 'crapto1Implementation', 92 | _crapto1Implementation!.index, 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/l10n/app_localizations_ja.dart: -------------------------------------------------------------------------------- 1 | // ignore: unused_import 2 | import 'package:intl/intl.dart' as intl; 3 | import 'app_localizations.dart'; 4 | 5 | // ignore_for_file: type=lint 6 | 7 | /// The translations for Japanese (`ja`). 8 | class AppLocalizationsJa extends AppLocalizations { 9 | AppLocalizationsJa([String locale = 'ja']) : super(locale); 10 | 11 | @override 12 | String get apply => '適用'; 13 | 14 | @override 15 | String get attacking => '解読中'; 16 | 17 | @override 18 | String get button => 'ボタン'; 19 | 20 | @override 21 | String get clear => 'クリア'; 22 | 23 | @override 24 | String get close => '閉じる'; 25 | 26 | @override 27 | String get confirmClear => 'クリアしてもよろしいですか?'; 28 | 29 | @override 30 | String get crapto1Implementation => 'Crapto1 実装'; 31 | 32 | @override 33 | String get crapto1Dart => 'Dart シングルスレッド'; 34 | 35 | @override 36 | String get crapto1Java => 'Java マルチスレッド'; 37 | 38 | @override 39 | String get crapto1Online => 'オンライン (サーバーオフラインの可能性あり)'; 40 | 41 | @override 42 | String get crapto1Native => '.NET8 NativeAOT (ARM64のみ)'; 43 | 44 | @override 45 | String get deviceInfo => 'デバイス情報'; 46 | 47 | @override 48 | String get disconnect => '切断'; 49 | 50 | @override 51 | String get download => 'ダウンロード'; 52 | 53 | @override 54 | String get downloading => 'ダウンロード中'; 55 | 56 | @override 57 | String get chameleonMiniApp => 'Chameleon Mini App'; 58 | 59 | @override 60 | String get english => '英語'; 61 | 62 | @override 63 | String get generalSetting => '一般設定'; 64 | 65 | @override 66 | String get language => '言語'; 67 | 68 | @override 69 | String get longPressButton => 'ボタン長押し'; 70 | 71 | @override 72 | String get memorySize => 'メモリサイズ'; 73 | 74 | @override 75 | String get mfkey32 => '解読'; 76 | 77 | @override 78 | String get mode => 'モード'; 79 | 80 | @override 81 | String get notAvailable => '利用不可'; 82 | 83 | @override 84 | String get refresh => '更新'; 85 | 86 | @override 87 | String get reset => 'リセット'; 88 | 89 | @override 90 | String get slot => 'スロット'; 91 | 92 | @override 93 | String get selectLanguage => '言語を選択'; 94 | 95 | @override 96 | String get settings => '設定'; 97 | 98 | @override 99 | String get traditionalChinese => '繁体字中国語'; 100 | 101 | @override 102 | String get uid => 'UID'; 103 | 104 | @override 105 | String get upload => 'アップロード'; 106 | 107 | @override 108 | String get usbDeviceNotFound => 'USBデバイスが見つかりません'; 109 | 110 | @override 111 | String get usbDisconnected => 'USBが切断されました'; 112 | 113 | @override 114 | String get systemDefault => 'システムデフォルト'; 115 | 116 | @override 117 | String get simplifiedChinese => '簡体字中国語'; 118 | 119 | @override 120 | String get japanese => '日本語'; 121 | } 122 | -------------------------------------------------------------------------------- /lib/l10n/app_localizations_en.dart: -------------------------------------------------------------------------------- 1 | // ignore: unused_import 2 | import 'package:intl/intl.dart' as intl; 3 | import 'app_localizations.dart'; 4 | 5 | // ignore_for_file: type=lint 6 | 7 | /// The translations for English (`en`). 8 | class AppLocalizationsEn extends AppLocalizations { 9 | AppLocalizationsEn([String locale = 'en']) : super(locale); 10 | 11 | @override 12 | String get apply => 'Apply'; 13 | 14 | @override 15 | String get attacking => 'Attacking'; 16 | 17 | @override 18 | String get button => 'Button'; 19 | 20 | @override 21 | String get clear => 'Clear'; 22 | 23 | @override 24 | String get close => 'Close'; 25 | 26 | @override 27 | String get confirmClear => 'Are you sure you want to clear?'; 28 | 29 | @override 30 | String get crapto1Implementation => 'Crapto1 Implementation'; 31 | 32 | @override 33 | String get crapto1Dart => 'Dart with Single-Thread'; 34 | 35 | @override 36 | String get crapto1Java => 'Java with Multi-Thread'; 37 | 38 | @override 39 | String get crapto1Online => 'Online (Server maybe offline)'; 40 | 41 | @override 42 | String get crapto1Native => '.NET8 NativeAOT (ARM64 only)'; 43 | 44 | @override 45 | String get deviceInfo => 'Device Info'; 46 | 47 | @override 48 | String get disconnect => 'Disconnect'; 49 | 50 | @override 51 | String get download => 'Download'; 52 | 53 | @override 54 | String get downloading => 'Downloading'; 55 | 56 | @override 57 | String get chameleonMiniApp => 'Chameleon Mini App'; 58 | 59 | @override 60 | String get english => 'English'; 61 | 62 | @override 63 | String get generalSetting => 'General Setting'; 64 | 65 | @override 66 | String get language => 'Language'; 67 | 68 | @override 69 | String get longPressButton => 'Long Press Button'; 70 | 71 | @override 72 | String get memorySize => 'Memory Size'; 73 | 74 | @override 75 | String get mfkey32 => 'mfkey32'; 76 | 77 | @override 78 | String get mode => 'Mode'; 79 | 80 | @override 81 | String get notAvailable => 'N/A'; 82 | 83 | @override 84 | String get refresh => 'Refresh'; 85 | 86 | @override 87 | String get reset => 'Reset'; 88 | 89 | @override 90 | String get slot => 'Slot'; 91 | 92 | @override 93 | String get selectLanguage => 'Select Language'; 94 | 95 | @override 96 | String get settings => 'Settings'; 97 | 98 | @override 99 | String get traditionalChinese => 'Traditional Chinese'; 100 | 101 | @override 102 | String get uid => 'UID'; 103 | 104 | @override 105 | String get upload => 'Upload'; 106 | 107 | @override 108 | String get usbDeviceNotFound => 'USB device not found'; 109 | 110 | @override 111 | String get usbDisconnected => 'USB Disconnected'; 112 | 113 | @override 114 | String get systemDefault => 'System Default'; 115 | 116 | @override 117 | String get simplifiedChinese => 'Simplified Chinese'; 118 | 119 | @override 120 | String get japanese => 'Japanese'; 121 | } 122 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/Crypto1.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | public class Crypto1 { 4 | public static final long LF_POLY_ODD = 0x29CE5CL; 5 | public static final long LF_POLY_EVEN = 0x870804L; 6 | 7 | public Crypto1State state; 8 | 9 | public Crypto1() { } 10 | 11 | public Crypto1(Crypto1State state) { this.state = state; } 12 | 13 | static byte bit(long v, int n) { 14 | return (byte)(v >> n & 1); 15 | } 16 | 17 | static byte beBit(long v, int n) { 18 | return bit(v, n ^ 24); 19 | } 20 | 21 | public byte crypto1Bit(byte _in, boolean isEncrypted) { 22 | byte ret = filter(state.odd); 23 | 24 | long feedin = ret & (isEncrypted ? 1 : 0); 25 | feedin ^= _in != 0 ? 1 : 0; 26 | feedin ^= LF_POLY_ODD & state.odd; 27 | feedin ^= LF_POLY_EVEN & state.even; 28 | state.even = state.even << 1 | evenParity32(feedin); 29 | 30 | long x = state.odd; 31 | state.odd = state.even; 32 | state.even = x; 33 | 34 | return ret; 35 | } 36 | 37 | public short crypto1Byte(short _in, boolean isEncrypted) { 38 | short ret = 0; 39 | 40 | for (int i = 0; i < 8; ++i) 41 | ret |= (short)(crypto1Bit(bit(_in, i), isEncrypted) << i); 42 | 43 | return (short)(ret & 0xFF); 44 | } 45 | 46 | public long crypto1Word(long _in, boolean isEncrypted) { 47 | long ret = 0; 48 | 49 | for (int i = 0; i < 32; ++i) 50 | ret |= (long)crypto1Bit(beBit(_in, i), isEncrypted) << (i ^ 24); 51 | 52 | return ret & 0xFFFFFFFFL; 53 | } 54 | 55 | protected static byte filter(long x) { 56 | long f; 57 | f = 0xf22c0 >> (x & 0xf) & 16; 58 | f |= 0x6c9c0 >> (x >> 4 & 0xf) & 8; 59 | f |= 0x3c8b0 >> (x >> 8 & 0xf) & 4; 60 | f |= 0x1e458 >> (x >> 12 & 0xf) & 2; 61 | f |= 0x0d938 >> (x >> 16 & 0xf) & 1; 62 | return (byte)(0xEC57E80A >> f & 1); 63 | } 64 | 65 | static byte[] _oddintParity = new byte[] { 66 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 67 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 68 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 69 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 70 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 71 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 72 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 73 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 74 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 75 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 76 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 77 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 78 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 79 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 80 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 81 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 82 | }; 83 | 84 | protected static byte oddParity8(short x) { return _oddintParity[x & 0xFF]; } 85 | 86 | protected static byte evenParity8(short x) { return (byte)(_oddintParity[x & 0xFF] ^ 1); } 87 | 88 | protected static byte evenParity32(long x) { 89 | x ^= x >> 16; 90 | x ^= x >> 8; 91 | return evenParity8((short)(x & 0xFF)); 92 | } 93 | } -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | def keystorePropertiesFile = rootProject.file("key.properties") 26 | def keyExists = keystorePropertiesFile.exists() 27 | def keystoreProperties = new Properties() 28 | if (keyExists) { 29 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 30 | } 31 | 32 | android { 33 | namespace "tw.kgame.chameleonminiapp" 34 | compileSdkVersion 36 35 | 36 | lintOptions { 37 | disable 'InvalidPackage' 38 | } 39 | 40 | defaultConfig { 41 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 42 | applicationId "tw.kgame.chameleonminiapp" 43 | minSdkVersion flutter.minSdkVersion 44 | targetSdkVersion 36 45 | versionCode flutterVersionCode.toInteger() 46 | versionName flutterVersionName 47 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 48 | } 49 | 50 | signingConfigs { 51 | release { 52 | if (keyExists) { 53 | keyAlias keystoreProperties['keyAlias'] 54 | keyPassword keystoreProperties['keyPassword'] 55 | storeFile file(keystoreProperties['storeFile']) 56 | storePassword keystoreProperties['storePassword'] 57 | } 58 | } 59 | } 60 | 61 | buildTypes { 62 | debug { 63 | signingConfig signingConfigs.debug 64 | } 65 | release { 66 | signingConfig signingConfigs.release 67 | } 68 | } 69 | 70 | compileOptions { 71 | sourceCompatibility 1.8 72 | targetCompatibility 1.8 73 | } 74 | 75 | task dotnetPublish(type: Exec) { 76 | workingDir '../../Crapto1Native/' 77 | commandLine 'dotnet', 'publish' 78 | doLast { 79 | delete '../../Crapto1Native/obj' 80 | } 81 | } 82 | 83 | task buildBflat(type: Exec) { 84 | dependsOn dotnetPublish 85 | doFirst { 86 | file('src/main/jniLibs/arm64-v8a/').mkdirs() 87 | } 88 | workingDir '../../Crapto1Native/' 89 | commandLine 'bflat', 'build', '--no-reflection', '--no-stacktrace-data', '--no-globalization', '--no-exception-messages', '-Os', '--no-pie', '--separate-symbols', '--os:linux', '--arch:arm64', '--libc:bionic', '-r:bin/Release/net8.0/publish/Crapto1Sharp.dll', '-o:../android/app/src/main/jniLibs/arm64-v8a/libCrapto1Native.so' 90 | doLast { 91 | delete 'src/main/jniLibs/arm64-v8a/libCrapto1Native.so.dwo' 92 | } 93 | } 94 | 95 | preBuild.dependsOn(dotnetPublish, buildBflat) 96 | } 97 | 98 | flutter { 99 | source '../..' 100 | } 101 | 102 | dependencies {} 103 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/chameleonminiapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.chameleonminiapp; 2 | 3 | import java.util.*; 4 | 5 | import androidx.annotation.NonNull; 6 | import io.flutter.embedding.android.FlutterActivity; 7 | import io.flutter.embedding.engine.FlutterEngine; 8 | import io.flutter.plugin.common.BinaryMessenger; 9 | import io.flutter.plugin.common.MethodChannel; 10 | import io.flutter.plugins.GeneratedPluginRegistrant; 11 | import tw.kgame.crapto1.MfKey; 12 | import tw.kgame.crapto1.Nonce; 13 | 14 | public class MainActivity extends FlutterActivity { 15 | 16 | private static final String CHANNEL_MAIN = "tw.kgame.crapto1/main"; 17 | private static final String CHANNEL_MFKEY = "tw.kgame.crapto1/mfkey"; 18 | MethodChannel channelMfkey; 19 | MethodChannel channelMain; 20 | 21 | BinaryMessenger getBinaryMessenger(){ 22 | return getFlutterEngine().getDartExecutor().getBinaryMessenger(); 23 | } 24 | 25 | @Override 26 | public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { 27 | GeneratedPluginRegistrant.registerWith(flutterEngine); 28 | 29 | channelMain = new MethodChannel(getBinaryMessenger(), CHANNEL_MAIN); 30 | channelMfkey = new MethodChannel(getBinaryMessenger(), CHANNEL_MFKEY); 31 | channelMfkey.setMethodCallHandler( 32 | (call, result) -> { 33 | System.out.println(call.method); 34 | @SuppressWarnings("unchecked") 35 | Map map = (Map)call.arguments; 36 | Object obj = map.get("uid"); 37 | long uid = obj instanceof Long ? (long)obj : (long)(int)obj; 38 | @SuppressWarnings("unchecked") 39 | List> ns = (List>)map.get("nonces"); 40 | List nonces = new ArrayList<>(); 41 | long v = 0; 42 | for (Map n: ns) { 43 | Nonce nonce = new Nonce(); 44 | obj = n.get("nt"); 45 | nonce.nt = obj instanceof Long ? (long)obj : (long)(int)obj; 46 | obj = n.get("nr"); 47 | nonce.nr = obj instanceof Long ? (long)obj : (long)(int)obj; 48 | obj = n.get("ar"); 49 | nonce.ar = obj instanceof Long ? (long)obj : (long)(int)obj; 50 | nonces.add(nonce); 51 | v ^= nonce.nt ^ nonce.nr ^ nonce.ar; 52 | } 53 | final long id = v; 54 | new Thread(() -> { 55 | Long k = null; 56 | try { 57 | k = MfKey.mfKey32(uid, nonces); 58 | } catch (Exception ex) { 59 | } 60 | Map m = new HashMap(); 61 | m.put("id", id); 62 | m.put("key", k == -1 ? null : k); 63 | runOnUiThread(() -> { 64 | channelMfkey.invokeMethod("mfKey32Result", m, new MethodChannel.Result() { 65 | @Override 66 | public void success(Object result) { 67 | } 68 | 69 | @Override 70 | public void error(String errorCode, String errorMessage, Object errorDetails) { 71 | } 72 | 73 | @Override 74 | public void notImplemented() { 75 | } 76 | }); 77 | }); 78 | }).start(); 79 | result.success(id); 80 | } 81 | ); 82 | } 83 | 84 | @Override 85 | public void onNewIntent(android.content.Intent intent) { 86 | System.out.println(intent.getAction()); 87 | channelMain.invokeMethod("onNewIntent", intent.getAction(), new MethodChannel.Result() { 88 | @Override 89 | public void success(Object result) { 90 | } 91 | 92 | @Override 93 | public void error(String errorCode, String errorMessage, Object errorDetails) { 94 | } 95 | 96 | @Override 97 | public void notImplemented() { 98 | } 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/services/xmodem.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:async'; 3 | import 'dart:collection'; 4 | 5 | class _ByteReader { 6 | Stream _sream; 7 | late Queue _queue; 8 | late StreamSubscription _subscription; 9 | late Completer _c; 10 | 11 | _ByteReader(this._sream) { 12 | _queue = Queue(); 13 | _subscription = _sream.listen(_onData); 14 | _c = Completer(); 15 | } 16 | 17 | void _onData(Uint8List data) { 18 | _queue.addAll(data); 19 | _c.complete(); 20 | _c = Completer(); 21 | } 22 | 23 | Future get read async { 24 | if (_queue.length == 0) 25 | await _c.future; 26 | return _queue.removeFirst(); 27 | } 28 | 29 | Future cancel() => _subscription.cancel(); 30 | } 31 | 32 | class Xmodem { 33 | static const SOH = 0x01; 34 | static const EOT = 0x04; 35 | static const ACK = 0x06; 36 | static const NAK = 0x15; 37 | static const CAN = 0x18; 38 | 39 | static const SECTOR_SIZE = 128; 40 | static const MAX_ERRORS = 10; 41 | 42 | final void Function(Uint8List) output; 43 | final Stream input; 44 | 45 | Xmodem(this.input, this.output); 46 | 47 | Future send(Uint8List data) async { 48 | int errorCount; 49 | int blockNumber = 0x01; 50 | int checkSum; 51 | int nbytes; 52 | var buffer = Uint8List(SECTOR_SIZE); 53 | 54 | var queue = Queue.from(data); 55 | int read() { 56 | var len = 0; 57 | while (len < buffer.length && queue.length > 0) 58 | buffer[len++] = queue.removeFirst(); 59 | return len; 60 | } 61 | 62 | var reader = _ByteReader(input); 63 | 64 | while ((nbytes = read()) > 0) { 65 | // less 128, padding 0xFF 66 | if (nbytes < SECTOR_SIZE) { 67 | for (int i = nbytes; i < SECTOR_SIZE; i++) { 68 | buffer[i] = 0xFF; 69 | } 70 | } 71 | 72 | errorCount = 0; 73 | while (errorCount < MAX_ERRORS) { 74 | output(Uint8List.fromList([ 75 | SOH, 76 | blockNumber, 77 | ~blockNumber & 0xFF 78 | ])); 79 | output(buffer); 80 | checkSum = buffer.reduce((v, e) => v + e); 81 | putData(checkSum); 82 | 83 | // get ACK 84 | var data = await reader.read; 85 | if (data == ACK) { 86 | break; 87 | } else { 88 | ++errorCount; 89 | } 90 | } 91 | blockNumber = (blockNumber + 1) & 0xFF; 92 | } 93 | 94 | // 所有数据发送完成后,发送结束标识 95 | var isAck = false; 96 | while (!isAck) { 97 | putData(EOT); 98 | isAck = await reader.read == ACK; 99 | } 100 | reader.cancel(); 101 | } 102 | 103 | Future receive() async { 104 | var output = []; 105 | var reader = _ByteReader(input); 106 | 107 | int errorCount = 0; 108 | var blockNumber = 1; 109 | int data; 110 | var buffer = Uint8List(SECTOR_SIZE); 111 | 112 | // Checksum type 113 | putData(NAK); 114 | 115 | while (true) { 116 | if (errorCount > MAX_ERRORS) { 117 | return null; 118 | } 119 | 120 | data = await reader.read; 121 | if (data != EOT) { 122 | try { 123 | if (data != SOH) { 124 | errorCount++; 125 | continue; 126 | } 127 | 128 | // block number 129 | data = await reader.read; 130 | // check block number 131 | if (data != (blockNumber & 0xFF)) { 132 | errorCount++; 133 | continue; 134 | } 135 | 136 | // check ~blockNumber 137 | int _blockNumber = await reader.read; 138 | if (data + _blockNumber != 255) { 139 | errorCount++; 140 | continue; 141 | } 142 | 143 | var sum = 0; 144 | // get data 145 | for (var i = 0; i < SECTOR_SIZE; i++) { 146 | buffer[i] = await reader.read; 147 | sum += buffer[i]; 148 | } 149 | 150 | int checksum = await reader.read; 151 | if (sum & 0xFF != checksum) { 152 | errorCount++; 153 | continue; 154 | } 155 | 156 | putData(ACK); 157 | blockNumber = (blockNumber + 1) & 0xFF; 158 | output.addAll(buffer); 159 | errorCount = 0; 160 | } catch (e) { 161 | print(e); 162 | } finally { 163 | if (errorCount != 0) { 164 | putData(NAK); 165 | } 166 | } 167 | } else { 168 | break; 169 | } 170 | } 171 | putData(ACK); 172 | reader.cancel(); 173 | return Uint8List.fromList(output); 174 | } 175 | 176 | void putData(int data) { 177 | output(Uint8List.fromList([data])); 178 | } 179 | } -------------------------------------------------------------------------------- /lib/views/settings/settingsPage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 5 | 6 | import '../../services/settings.dart'; 7 | import 'languagePage.dart'; 8 | 9 | class SettingsPage extends StatefulWidget { 10 | static const String name = '/Settings/Language'; 11 | 12 | SettingsPage({Key? key}) : super(key: key); 13 | 14 | @override 15 | _SettingsPageState createState() => _SettingsPageState(); 16 | } 17 | 18 | class _SettingsPageState extends State { 19 | final Settings settings = Settings(); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)), 25 | body: bodyData(), 26 | ); 27 | } 28 | 29 | String _localToString(Locale? locale) { 30 | if (locale?.languageCode == 'en') 31 | return AppLocalizations.of(context)!.english; 32 | if (locale?.languageCode == 'zh') { 33 | if (locale?.scriptCode == 'Hant') 34 | return AppLocalizations.of(context)!.traditionalChinese; 35 | } 36 | return AppLocalizations.of(context)!.systemDefault; 37 | } 38 | 39 | String _crapto1ImplementationToString( 40 | Crapto1Implementation crapto1implementation, 41 | ) { 42 | switch (crapto1implementation) { 43 | case Crapto1Implementation.Dart: 44 | return AppLocalizations.of(context)!.crapto1Dart; 45 | case Crapto1Implementation.Java: 46 | return AppLocalizations.of(context)!.crapto1Java; 47 | case Crapto1Implementation.Online: 48 | return AppLocalizations.of(context)!.crapto1Online; 49 | case Crapto1Implementation.Native: 50 | return AppLocalizations.of(context)!.crapto1Native; 51 | } 52 | } 53 | 54 | void _pushLanguagePage() async { 55 | var value = await Navigator.of(context).pushNamed(LanguagePage.name); 56 | print(value); 57 | if (value == null) return; 58 | 59 | setState(() { 60 | settings.locale = value == 'default' ? null : value as Locale; 61 | }); 62 | } 63 | 64 | void _showCrapto1ImplementationDialog() { 65 | showDialog( 66 | context: context, 67 | builder: (BuildContext context) => SimpleDialog( 68 | title: Text(AppLocalizations.of(context)!.crapto1Implementation), 69 | children: [ 70 | RadioGroup( 71 | groupValue: settings.crapto1Implementation, 72 | onChanged: _selectCrapto1Implementation, 73 | child: Column( 74 | children: [ 75 | RadioListTile( 76 | selected: 77 | settings.crapto1Implementation == 78 | Crapto1Implementation.Dart, 79 | value: Crapto1Implementation.Dart, 80 | title: Text( 81 | _crapto1ImplementationToString(Crapto1Implementation.Dart), 82 | ), 83 | ), 84 | RadioListTile( 85 | selected: 86 | settings.crapto1Implementation == 87 | Crapto1Implementation.Java, 88 | value: Crapto1Implementation.Java, 89 | title: Text( 90 | _crapto1ImplementationToString(Crapto1Implementation.Java), 91 | ), 92 | ), 93 | RadioListTile( 94 | selected: 95 | settings.crapto1Implementation == 96 | Crapto1Implementation.Native, 97 | value: Crapto1Implementation.Native, 98 | title: Text( 99 | _crapto1ImplementationToString( 100 | Crapto1Implementation.Native, 101 | ), 102 | ), 103 | enabled: Platform.version.contains('arm64'), 104 | ), 105 | ], 106 | ), 107 | ), 108 | ], 109 | ), 110 | ); 111 | } 112 | 113 | void _selectCrapto1Implementation(Crapto1Implementation? value) { 114 | setState(() { 115 | settings.crapto1Implementation = value!; 116 | }); 117 | Navigator.pop(context); 118 | } 119 | 120 | Widget bodyData() { 121 | final colorScheme = Theme.of(context).colorScheme; 122 | return ListView( 123 | padding: const EdgeInsets.all(16.0), 124 | children: [ 125 | Padding( 126 | padding: const EdgeInsets.only(left: 8.0, bottom: 8.0), 127 | child: Text( 128 | AppLocalizations.of(context)!.generalSetting, 129 | style: TextStyle( 130 | color: colorScheme.primary, 131 | fontWeight: FontWeight.bold, 132 | ), 133 | ), 134 | ), 135 | Card( 136 | elevation: 2.0, 137 | clipBehavior: Clip.antiAlias, 138 | shape: RoundedRectangleBorder( 139 | borderRadius: BorderRadius.circular(12.0), 140 | ), 141 | child: Column( 142 | children: [ 143 | ListTile( 144 | leading: const Icon(Icons.language), 145 | title: Text(AppLocalizations.of(context)!.language), 146 | subtitle: Text(_localToString(settings.locale)), 147 | trailing: const Icon(Icons.chevron_right), 148 | onTap: _pushLanguagePage, 149 | ), 150 | const Divider(height: 1, indent: 16, endIndent: 16), 151 | ListTile( 152 | leading: const Icon(Icons.functions), 153 | title: Text('Crapto1 & mfkey32 implementation'), 154 | subtitle: Text( 155 | _crapto1ImplementationToString( 156 | settings.crapto1Implementation!, 157 | ), 158 | ), 159 | trailing: const Icon(Icons.chevron_right), 160 | onTap: _showCrapto1ImplementationDialog, 161 | ), 162 | ], 163 | ), 164 | ), 165 | ], 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /lib/views/home/homePage.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:usb_serial/usb_serial.dart'; 5 | 6 | import '../../services/chameleonClient.dart'; 7 | import 'slotView.dart'; 8 | import 'deviceInfoDialog.dart'; 9 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 10 | import '../settings/settingsPage.dart'; 11 | 12 | class HomePage extends StatefulWidget { 13 | HomePage({Key? key}) : super(key: key); 14 | 15 | @override 16 | _HomePageState createState() => _HomePageState(); 17 | } 18 | 19 | class _HomePageState extends State 20 | with SingleTickerProviderStateMixin { 21 | TabController? _tabController; 22 | final channel = const MethodChannel('tw.kgame.crapto1/main'); 23 | 24 | final ChameleonClient client = ChameleonClient(); 25 | List slots = [ 26 | Slot(0), 27 | Slot(1), 28 | Slot(2), 29 | Slot(3), 30 | Slot(4), 31 | Slot(5), 32 | Slot(6), 33 | Slot(7), 34 | ]; 35 | final List slotIcons = const [ 36 | const Icon(Icons.filter_1), 37 | const Icon(Icons.filter_2), 38 | const Icon(Icons.filter_3), 39 | const Icon(Icons.filter_4), 40 | const Icon(Icons.filter_5), 41 | const Icon(Icons.filter_6), 42 | const Icon(Icons.filter_7), 43 | const Icon(Icons.filter_8), 44 | ]; 45 | 46 | void _pushSettings() { 47 | Navigator.of(context).pushNamed(SettingsPage.name); 48 | } 49 | 50 | Future _disconnected() async { 51 | await client.close(); 52 | setState(() { 53 | version = null; 54 | commands = null; 55 | modes = null; 56 | buttonModes = null; 57 | longPressButtonModes = null; 58 | for (var slot in slots) { 59 | slot.uid = null; 60 | slot.memorySize = null; 61 | slot.mode = null; 62 | slot.button = null; 63 | slot.longPressButton = null; 64 | } 65 | }); 66 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 67 | content: Text(AppLocalizations.of(context)!.usbDisconnected), 68 | duration: Duration(seconds: 10), 69 | )); 70 | } 71 | 72 | Future _connect() async { 73 | if (client.connected) { 74 | var rssi = await client.getRssi(); 75 | var result = await showDialog( 76 | context: context, 77 | builder: (_) => DeviceInfoDialog( 78 | version!, 79 | rssi, 80 | ), 81 | ); 82 | if (result == 'disconnect') { 83 | await _disconnected(); 84 | } else if (result == 'reset') { 85 | client.reset(); 86 | } 87 | return; 88 | } 89 | List devices = await UsbSerial.listDevices(); 90 | print(devices); 91 | 92 | if (devices.length == 0) { 93 | ScaffoldMessenger.of(context).showSnackBar(SnackBar( 94 | content: Text(AppLocalizations.of(context)!.usbDeviceNotFound), 95 | duration: Duration(seconds: 3), 96 | )); 97 | return; 98 | } 99 | var port = await devices[0].create(); 100 | 101 | if (port == null) { 102 | print("Failed to create"); 103 | return; 104 | } 105 | 106 | bool openResult = await port.open(); 107 | if (!openResult) { 108 | print("Failed to open"); 109 | return; 110 | } 111 | 112 | await port.setDTR(true); 113 | await port.setRTS(true); 114 | 115 | port.setPortParameters( 116 | 115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE); 117 | 118 | client.port = port; 119 | await client.checkCommand(); 120 | version = await client.getVersion(); 121 | commands = await client.getCommands(); 122 | modes = await client.getModes(); 123 | buttonModes = await client.getButtonModes(); 124 | try { 125 | longPressButtonModes = await client.getLongPressButtonModes(); 126 | } catch (e) {} 127 | slots = await client.refreshAll(); 128 | setState(() => client.connected); 129 | } 130 | 131 | String? version; 132 | List? commands, modes, buttonModes, longPressButtonModes; 133 | StreamSubscription? usbEventStreamSubscription; 134 | 135 | @override 136 | void initState() { 137 | super.initState(); 138 | _tabController = TabController(length: slots.length, vsync: this); 139 | 140 | channel.setMethodCallHandler((call) async { 141 | switch (call.method) { 142 | case 'onNewIntent': 143 | if (call.arguments == 144 | 'android.hardware.usb.action.USB_DEVICE_ATTACHED') _connect(); 145 | break; 146 | default: 147 | break; 148 | } 149 | }); 150 | 151 | usbEventStreamSubscription = 152 | UsbSerial.usbEventStream?.listen((UsbEvent msg) { 153 | print("Usb Event $msg"); 154 | if (msg.event == UsbEvent.ACTION_USB_DETACHED) { 155 | _disconnected(); 156 | } 157 | }); 158 | _connect(); 159 | } 160 | 161 | @override 162 | void dispose() { 163 | usbEventStreamSubscription?.cancel(); 164 | _tabController?.dispose(); 165 | super.dispose(); 166 | } 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | return Scaffold( 171 | appBar: AppBar( 172 | title: Text(AppLocalizations.of(context)!.chameleonMiniApp), 173 | bottom: TabBar( 174 | controller: _tabController, 175 | isScrollable: true, 176 | tabs: slots.map((Slot slot) { 177 | return Tab( 178 | icon: slotIcons[slot.index], 179 | text: '${AppLocalizations.of(context)!.slot} ${slot.index + 1}', 180 | ); 181 | }).toList(), 182 | ), 183 | actions: [ 184 | IconButton( 185 | icon: Icon( 186 | Icons.usb, 187 | color: client.connected ? Colors.blue : null, 188 | ), 189 | onPressed: _connect, 190 | ), 191 | IconButton( 192 | icon: const Icon(Icons.settings), 193 | onPressed: _pushSettings, 194 | ), 195 | ], 196 | ), 197 | body: TabBarView( 198 | controller: _tabController, 199 | children: slots.map((Slot slot) { 200 | return SlotView( 201 | slot, 202 | client, 203 | modes: modes, 204 | buttonModes: buttonModes, 205 | longPressButtonModes: longPressButtonModes, 206 | ); 207 | }).toList(), 208 | ), 209 | ); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /android/app/src/main/java/tw/kgame/crapto1/Crapto1.java: -------------------------------------------------------------------------------- 1 | package tw.kgame.crapto1; 2 | 3 | import java.util.*; 4 | 5 | public class Crapto1 extends Crypto1 { 6 | public Crapto1() { } 7 | 8 | public Crapto1(Crypto1State state) { super(state); } 9 | 10 | public byte lfsrRollbackBit(byte _in, boolean isEncrypted) { 11 | long _out; 12 | byte ret; 13 | 14 | state.odd &= 0xffffff; 15 | long t = state.odd; 16 | state.odd = state.even; 17 | state.even = t; 18 | 19 | _out = state.even & 1; 20 | _out ^= LF_POLY_EVEN & (state.even >>= 1); 21 | _out ^= LF_POLY_ODD & state.odd; 22 | _out ^= _in != 0 ? 1 : 0; 23 | _out ^= (ret = filter(state.odd)) & (isEncrypted ? 1 : 0); 24 | 25 | state.even |= (long)evenParity32(_out) << 23; 26 | return ret; 27 | } 28 | 29 | public short lfsrRollbackByte(short _in, boolean isEncrypted) { 30 | short ret = 0; 31 | for (int i = 7; i >= 0; --i) 32 | ret |= (short)(lfsrRollbackBit(bit(_in, i), isEncrypted) << i); 33 | return (short)(ret & 0xFF); 34 | } 35 | 36 | public long lfsrRollbackWord(long _in, boolean isEncrypted) { 37 | long ret = 0; 38 | for (int i = 31; i >= 0; --i) 39 | ret |= (long)lfsrRollbackBit(beBit(_in, i), isEncrypted) << (i ^ 24); 40 | return ret; 41 | } 42 | 43 | private static long updateContribution(long item, long mask1, long mask2) { 44 | long p = item >> 25; 45 | p = p << 1 | evenParity32(item & mask1); 46 | p = p << 1 | evenParity32(item & mask2); 47 | item = p << 24 | (item & 0xffffff); 48 | return item & 0xFFFFFFFFL; 49 | } 50 | 51 | static int extendTable(Span tbl, int end, long bit, long m1, long m2, long _in) { 52 | _in <<= 24; 53 | int i = 0; 54 | for (tbl.set(i, tbl.get(i) << 1); i <= end; ++i, tbl.set(i, tbl.get(i) << 1)) 55 | if ((filter(tbl.get(i)) ^ filter(tbl.get(i) | 1)) != 0) 56 | { 57 | tbl.set(i, tbl.get(i) | (filter(tbl.get(i)) ^ bit)); 58 | tbl.set(i, updateContribution(tbl.get(i), m1, m2)); 59 | tbl.set(i, tbl.get(i) ^ _in); 60 | } 61 | else if (filter(tbl.get(i)) == bit) 62 | { 63 | tbl.set(++end, tbl.get(i + 1)); 64 | tbl.set(i + 1, tbl.get(i) | 1); 65 | tbl.set(i, updateContribution(tbl.get(i), m1, m2)); 66 | tbl.set(i, tbl.get(i) ^ _in); 67 | i++; 68 | tbl.set(i, updateContribution(tbl.get(i), m1, m2)); 69 | tbl.set(i, tbl.get(i) ^ _in); 70 | } 71 | else 72 | tbl.set(i--, tbl.get(end--)); 73 | return end; 74 | } 75 | 76 | static int extendTableSimple(long[] tbl, int end, long bit) { 77 | int i = 0; 78 | for (tbl[i] <<= 1; i <= end; tbl[++i] <<= 1) 79 | { 80 | if ((filter(tbl[i]) ^ filter(tbl[i] | 1)) != 0) 81 | { 82 | tbl[i] |= filter(tbl[i]) ^ bit; 83 | } 84 | else if (filter(tbl[i]) == bit) 85 | { 86 | tbl[++end] = tbl[++i]; 87 | tbl[i] = tbl[i - 1] | 1; 88 | } 89 | else 90 | { 91 | tbl[i--] = tbl[end--]; 92 | } 93 | } 94 | return end; 95 | } 96 | 97 | static void recover(Span odd, int oddTail, long oks, Span even, int evenTail, long eks, int rem, List sl, long _in) { 98 | int o = 0; 99 | int e = 0; 100 | 101 | if (rem == -1) { 102 | for (e = 0; e <= evenTail; e++) { 103 | even.set(e, even.get(e) << 1 ^ evenParity32(even.get(e) & Crypto1.LF_POLY_EVEN) ^ ((_in & 4) != 0 ? 1 : 0)); 104 | for (o = 0; o <= oddTail; o++) { 105 | sl.add(new Crypto1State(even.get(e) ^ evenParity32(odd.get(o) & Crypto1.LF_POLY_ODD), odd.get(o))); 106 | } 107 | } 108 | return ; 109 | } 110 | 111 | for (int i = 0; i < 4 && rem-- != 0; i++) { 112 | oks >>= 1; 113 | eks >>= 1; 114 | _in >>= 2; 115 | oddTail = extendTable(odd, oddTail, oks & 1, Crypto1.LF_POLY_EVEN << 1 | 1, Crypto1.LF_POLY_ODD << 1, 0); 116 | if (0 > oddTail) 117 | return; 118 | 119 | evenTail = extendTable(even, evenTail, eks & 1, Crypto1.LF_POLY_ODD, Crypto1.LF_POLY_EVEN << 1 | 1, _in & 3); 120 | if (0 > evenTail) 121 | return; 122 | } 123 | 124 | odd.slice(0, oddTail + 1).sort(); 125 | even.slice(0, evenTail + 1).sort(); 126 | 127 | while (oddTail >= 0 && evenTail >= 0) 128 | if (((odd.get(oddTail) ^ even.get(evenTail)) >> 24) == 0) { 129 | oddTail = odd.slice(0, (o = oddTail) + 1).binarySearch(); 130 | evenTail = even.slice(0, (e = evenTail) + 1).binarySearch(); 131 | recover(odd.slice(oddTail), o - oddTail, oks, even.slice(evenTail), e - evenTail, eks, rem, sl, _in); 132 | oddTail--; evenTail--; 133 | } 134 | else if (odd.get(oddTail) > even.get(evenTail)) 135 | oddTail = odd.slice(0, oddTail + 1).binarySearch() - 1; 136 | else 137 | evenTail = even.slice(0, evenTail + 1).binarySearch() - 1; 138 | } 139 | 140 | public static List lfsrRecovery32(long ks2, long _in) { 141 | long oks = 0; 142 | long eks = 0; 143 | 144 | for (int i = 31; i >= 0; i -= 2) 145 | oks = oks << 1 | beBit(ks2, i); 146 | for (int i = 30; i >= 0; i -= 2) 147 | eks = eks << 1 | beBit(ks2, i); 148 | 149 | long[] odd = new long[4 << 21]; 150 | long[] even = new long[4 << 21]; 151 | List statelist = new ArrayList<>(1 << 18); 152 | int oddTail = 0; 153 | int evenTail = 0; 154 | 155 | for (int i = 1 << 20; i >= 0; --i) { 156 | if (filter(i) == (oks & 1)) 157 | odd[++oddTail] = i; 158 | if (filter(i) == (eks & 1)) 159 | even[++evenTail] = i; 160 | } 161 | 162 | for (int i = 0; i < 4; i++) { 163 | oddTail = extendTableSimple(odd, oddTail, (oks >>= 1) & 1); 164 | evenTail = extendTableSimple(even, evenTail, (eks >>= 1) & 1); 165 | } 166 | 167 | _in = (_in >> 16 & 0xff) | (_in << 16) | (_in & 0xff00); 168 | recover(new Span(odd), oddTail, oks, new Span(even), evenTail, eks, 11, statelist, _in << 1); 169 | 170 | return statelist; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/view_models/slotViewModel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | import 'package:flutter/foundation.dart'; 4 | import '../services/chameleonClient.dart'; 5 | import '../services/crapto1.dart'; 6 | import '../services/ffiService.dart'; 7 | import '../services/settings.dart'; 8 | 9 | class Mfkey32Exception implements Exception { 10 | String cause; 11 | Mfkey32Exception(this.cause); 12 | } 13 | 14 | class SlotViewModel extends ChangeNotifier { 15 | final Slot slot; 16 | final ChameleonClient client; 17 | final Settings settings; 18 | 19 | SlotViewModel(this.slot, this.client) : settings = Settings(); 20 | 21 | bool _isBusy = false; 22 | bool get isBusy => _isBusy; 23 | 24 | void setBusy(bool value) { 25 | _isBusy = value; 26 | notifyListeners(); 27 | } 28 | 29 | // Local state updates 30 | void setUid(String uid) { 31 | slot.uid = uid; 32 | notifyListeners(); 33 | } 34 | 35 | void setMode(String? mode) { 36 | slot.mode = mode; 37 | notifyListeners(); 38 | } 39 | 40 | void setButton(String? button) { 41 | slot.button = button; 42 | notifyListeners(); 43 | } 44 | 45 | void setLongPressButton(String? button) { 46 | slot.longPressButton = button; 47 | notifyListeners(); 48 | } 49 | 50 | Future refresh() async { 51 | setBusy(true); 52 | try { 53 | var s = await client.refresh(slot.index); 54 | if (s != null) { 55 | slot.uid = s.uid; 56 | slot.mode = s.mode; 57 | slot.button = s.button; 58 | slot.longPressButton = s.longPressButton; 59 | slot.memorySize = s.memorySize; 60 | notifyListeners(); 61 | } 62 | } finally { 63 | setBusy(false); 64 | } 65 | } 66 | 67 | Future apply() async { 68 | setBusy(true); 69 | try { 70 | await client.active(slot.index); 71 | var selectedSlot = await client.getActive(); 72 | if (selectedSlot != slot.index) return; 73 | 74 | if (slot.mode != null) await client.setMode(slot.mode!); 75 | if (slot.button != null) await client.setButton(slot.button!); 76 | if (slot.longPressButton != null) 77 | await client.setLongPressButton(slot.longPressButton!); 78 | if (slot.uid != null) await client.setUid(slot.uid!); 79 | 80 | await refresh(); 81 | } finally { 82 | setBusy(false); 83 | } 84 | } 85 | 86 | Future upload(Uint8List data) async { 87 | setBusy(true); 88 | try { 89 | await client.active(slot.index); 90 | await client.upload(data); 91 | await refresh(); 92 | } finally { 93 | setBusy(false); 94 | } 95 | } 96 | 97 | Future downloadMct() async { 98 | setBusy(true); 99 | try { 100 | await client.active(slot.index); 101 | var result = await client.download(); 102 | if (result == null) throw Exception("Download failed"); 103 | 104 | var data = result.take(slot.memorySize ?? 0).toList(); 105 | return _toMct(data); 106 | } finally { 107 | setBusy(false); 108 | } 109 | } 110 | 111 | Future clear() async { 112 | setBusy(true); 113 | try { 114 | await client.active(slot.index); 115 | if (await client.getMode() == 'MF_DETECTION') { 116 | await client.clearDetection(); 117 | } else { 118 | await client.clear(); 119 | } 120 | await refresh(); 121 | } finally { 122 | setBusy(false); 123 | } 124 | } 125 | 126 | Future> mfkey32() async { 127 | setBusy(true); 128 | try { 129 | await client.active(slot.index); 130 | var data = await client.getDetection(); 131 | if (data.length == 0) { 132 | throw Mfkey32Exception('No data found on device.'); 133 | } 134 | // no encrypt in 1.4 firmware 135 | int canary = _toUint64(data, 8); 136 | if (canary != 0x5245564556312E34) { 137 | ChameleonClient.decryptData(data, 123321, 208); 138 | } 139 | if (!Crc.checkCrc14443(Crc.CRC16_14443_A, data, 210)) { 140 | throw Mfkey32Exception('Data failed CRC check.'); 141 | } 142 | var uid = _toUint32(data, 0); 143 | var nonces = []; 144 | for (var i = 1; i <= 12; i++) { 145 | var offset = i * 16; 146 | var nonce = Nonce() 147 | ..type = data[offset] 148 | ..block = data[offset + 1] 149 | ..nt = _toUint32(data, offset + 4) 150 | ..nr = _toUint32(data, offset + 8) 151 | ..ar = _toUint32(data, offset + 12); 152 | nonce.sector = _toSector(nonce.block); 153 | if (nonce.type != 0xFF) nonces.add(nonce); 154 | } 155 | if (nonces.length == 0) { 156 | throw Mfkey32Exception('No nonces record.'); 157 | } 158 | 159 | List? list; 160 | switch (settings.crapto1Implementation) { 161 | case Crapto1Implementation.Dart: 162 | list = await compute(keyWork, KeyWorkMessage(mfKey32, uid, nonces)); 163 | break; 164 | case Crapto1Implementation.Java: 165 | case Crapto1Implementation.Online: 166 | list = await keyWork(KeyWorkMessage(mfKey32Java, uid, nonces)); 167 | break; 168 | case Crapto1Implementation.Native: 169 | list = await compute( 170 | keyWork, 171 | KeyWorkMessage(mfKey32Native, uid, nonces), 172 | ); 173 | break; 174 | default: 175 | break; 176 | } 177 | 178 | if (list == null || list.length == 0) { 179 | throw Mfkey32Exception('mfkey32 attack failed, no keys found.'); 180 | } 181 | return list; 182 | } finally { 183 | setBusy(false); 184 | } 185 | } 186 | 187 | // Helpers 188 | static Uint8List stringToBytes(String data) { 189 | var result = Uint8List(data.length ~/ 2); 190 | for (var i = 0; i < result.length; i++) { 191 | result[i] = int.parse(data.substring(i << 1, i + 1 << 1), radix: 16); 192 | } 193 | return result; 194 | } 195 | 196 | int _toUint32(Uint8List data, int offset) { 197 | var v = 0; 198 | for (var i = 0; i < 4; i++) v = v << 8 | data[offset + i]; 199 | return v; 200 | } 201 | 202 | int _toUint64(Uint8List data, int offset) { 203 | var v = 0; 204 | for (var i = 0; i < 8; i++) v = v << 8 | data[offset + i]; 205 | return v; 206 | } 207 | 208 | int _toSector(int block) { 209 | if (block < 128) return block ~/ 4; 210 | return 32 + (block - 128) ~/ 16; 211 | } 212 | 213 | String _bytesToString(Iterable bytes) { 214 | var str = ''; 215 | for (var b in bytes) 216 | str += b.toRadixString(16).padLeft(2, '0').toUpperCase(); 217 | return str; 218 | } 219 | 220 | String _toMct(List data) { 221 | var strs = []; 222 | var is4k = data.length == 4096; 223 | var size = is4k ? 32 : 16; 224 | for (var i = 0; i < size; i++) { 225 | strs.add('+Sector: $i'); 226 | for (var j = 0; j < 4; j++) { 227 | var block = data.skip(i * 64 + j * 16).take(16); 228 | strs.add(_bytesToString(block)); 229 | } 230 | } 231 | if (is4k) { 232 | for (var i = 32; i < 40; i++) { 233 | strs.add('+Sector: $i'); 234 | for (var j = 0; j < 16; j++) { 235 | var block = data.skip(2048 + (i - 32) * 256 + j * 16).take(16); 236 | strs.add(_bytesToString(block)); 237 | } 238 | } 239 | } 240 | return strs.join('\n'); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /lib/l10n/app_localizations_zh.dart: -------------------------------------------------------------------------------- 1 | // ignore: unused_import 2 | import 'package:intl/intl.dart' as intl; 3 | import 'app_localizations.dart'; 4 | 5 | // ignore_for_file: type=lint 6 | 7 | /// The translations for Chinese (`zh`). 8 | class AppLocalizationsZh extends AppLocalizations { 9 | AppLocalizationsZh([String locale = 'zh']) : super(locale); 10 | 11 | @override 12 | String get apply => '套用'; 13 | 14 | @override 15 | String get attacking => '解密中'; 16 | 17 | @override 18 | String get button => '按鈕'; 19 | 20 | @override 21 | String get clear => '清除'; 22 | 23 | @override 24 | String get close => '關閉'; 25 | 26 | @override 27 | String get confirmClear => '確定要清除嗎?'; 28 | 29 | @override 30 | String get crapto1Implementation => 'Crapto1 實作'; 31 | 32 | @override 33 | String get crapto1Dart => 'Dart 單執行緒'; 34 | 35 | @override 36 | String get crapto1Java => 'Java 多執行緒'; 37 | 38 | @override 39 | String get crapto1Online => '線上 (伺服器可能離線)'; 40 | 41 | @override 42 | String get crapto1Native => '.NET8 NativeAOT (僅限 ARM64)'; 43 | 44 | @override 45 | String get deviceInfo => '裝置資訊'; 46 | 47 | @override 48 | String get disconnect => '斷線'; 49 | 50 | @override 51 | String get download => '下載'; 52 | 53 | @override 54 | String get downloading => '下載中'; 55 | 56 | @override 57 | String get chameleonMiniApp => 'Chameleon Mini App'; 58 | 59 | @override 60 | String get english => '英文'; 61 | 62 | @override 63 | String get generalSetting => '一般設定'; 64 | 65 | @override 66 | String get language => '語言'; 67 | 68 | @override 69 | String get longPressButton => '長按按鈕'; 70 | 71 | @override 72 | String get memorySize => '記憶體大小'; 73 | 74 | @override 75 | String get mfkey32 => '解密'; 76 | 77 | @override 78 | String get mode => '模式'; 79 | 80 | @override 81 | String get notAvailable => '不可用'; 82 | 83 | @override 84 | String get refresh => '刷新'; 85 | 86 | @override 87 | String get reset => '重置'; 88 | 89 | @override 90 | String get slot => '槽位'; 91 | 92 | @override 93 | String get selectLanguage => '選擇語言'; 94 | 95 | @override 96 | String get settings => '設定'; 97 | 98 | @override 99 | String get traditionalChinese => '正體中文'; 100 | 101 | @override 102 | String get uid => 'UID'; 103 | 104 | @override 105 | String get upload => '上傳'; 106 | 107 | @override 108 | String get usbDeviceNotFound => '找不到 USB 裝置'; 109 | 110 | @override 111 | String get usbDisconnected => 'USB 已斷線'; 112 | 113 | @override 114 | String get systemDefault => '系統預設'; 115 | 116 | @override 117 | String get simplifiedChinese => '簡體中文'; 118 | 119 | @override 120 | String get japanese => '日文'; 121 | } 122 | 123 | /// The translations for Chinese, as used in China (`zh_CN`). 124 | class AppLocalizationsZhCn extends AppLocalizationsZh { 125 | AppLocalizationsZhCn() : super('zh_CN'); 126 | 127 | @override 128 | String get apply => '应用'; 129 | 130 | @override 131 | String get attacking => '解密中'; 132 | 133 | @override 134 | String get button => '按钮'; 135 | 136 | @override 137 | String get clear => '清除'; 138 | 139 | @override 140 | String get close => '关闭'; 141 | 142 | @override 143 | String get confirmClear => '确定要清除吗?'; 144 | 145 | @override 146 | String get crapto1Implementation => 'Crapto1 实现'; 147 | 148 | @override 149 | String get crapto1Dart => 'Dart 单线程'; 150 | 151 | @override 152 | String get crapto1Java => 'Java 多线程'; 153 | 154 | @override 155 | String get crapto1Online => '在线 (服务器可能离线)'; 156 | 157 | @override 158 | String get crapto1Native => '.NET8 NativeAOT (仅限 ARM64)'; 159 | 160 | @override 161 | String get deviceInfo => '设备信息'; 162 | 163 | @override 164 | String get disconnect => '断开连接'; 165 | 166 | @override 167 | String get download => '下载'; 168 | 169 | @override 170 | String get downloading => '下载中'; 171 | 172 | @override 173 | String get chameleonMiniApp => 'Chameleon Mini App'; 174 | 175 | @override 176 | String get english => '英语'; 177 | 178 | @override 179 | String get generalSetting => '通用设置'; 180 | 181 | @override 182 | String get language => '语言'; 183 | 184 | @override 185 | String get longPressButton => '长按按钮'; 186 | 187 | @override 188 | String get memorySize => '内存大小'; 189 | 190 | @override 191 | String get mfkey32 => '解密'; 192 | 193 | @override 194 | String get mode => '模式'; 195 | 196 | @override 197 | String get notAvailable => '不可用'; 198 | 199 | @override 200 | String get refresh => '刷新'; 201 | 202 | @override 203 | String get reset => '重置'; 204 | 205 | @override 206 | String get slot => '卡槽'; 207 | 208 | @override 209 | String get selectLanguage => '选择语言'; 210 | 211 | @override 212 | String get settings => '设置'; 213 | 214 | @override 215 | String get traditionalChinese => '繁体中文'; 216 | 217 | @override 218 | String get uid => 'UID'; 219 | 220 | @override 221 | String get upload => '上传'; 222 | 223 | @override 224 | String get usbDeviceNotFound => '未找到 USB 设备'; 225 | 226 | @override 227 | String get usbDisconnected => 'USB 已断开'; 228 | 229 | @override 230 | String get systemDefault => '系统默认'; 231 | 232 | @override 233 | String get simplifiedChinese => '简体中文'; 234 | 235 | @override 236 | String get japanese => '日语'; 237 | } 238 | 239 | /// The translations for Chinese, as used in Taiwan (`zh_TW`). 240 | class AppLocalizationsZhTw extends AppLocalizationsZh { 241 | AppLocalizationsZhTw() : super('zh_TW'); 242 | 243 | @override 244 | String get apply => '套用'; 245 | 246 | @override 247 | String get attacking => '解密中'; 248 | 249 | @override 250 | String get button => '按鈕'; 251 | 252 | @override 253 | String get clear => '清除'; 254 | 255 | @override 256 | String get close => '關閉'; 257 | 258 | @override 259 | String get confirmClear => '確定要清除嗎?'; 260 | 261 | @override 262 | String get crapto1Implementation => 'Crapto1 實作'; 263 | 264 | @override 265 | String get crapto1Dart => 'Dart 單執行緒'; 266 | 267 | @override 268 | String get crapto1Java => 'Java 多執行緒'; 269 | 270 | @override 271 | String get crapto1Online => '線上 (伺服器可能離線)'; 272 | 273 | @override 274 | String get crapto1Native => '.NET8 NativeAOT (僅限 ARM64)'; 275 | 276 | @override 277 | String get deviceInfo => '裝置資訊'; 278 | 279 | @override 280 | String get disconnect => '斷線'; 281 | 282 | @override 283 | String get download => '下載'; 284 | 285 | @override 286 | String get downloading => '下載中'; 287 | 288 | @override 289 | String get chameleonMiniApp => 'Chameleon Mini App'; 290 | 291 | @override 292 | String get english => '英文'; 293 | 294 | @override 295 | String get generalSetting => '一般設定'; 296 | 297 | @override 298 | String get language => '語言'; 299 | 300 | @override 301 | String get longPressButton => '長按按鈕'; 302 | 303 | @override 304 | String get memorySize => '記憶體大小'; 305 | 306 | @override 307 | String get mfkey32 => '解密'; 308 | 309 | @override 310 | String get mode => '模式'; 311 | 312 | @override 313 | String get notAvailable => '不可用'; 314 | 315 | @override 316 | String get refresh => '刷新'; 317 | 318 | @override 319 | String get reset => '重置'; 320 | 321 | @override 322 | String get slot => '槽位'; 323 | 324 | @override 325 | String get selectLanguage => '選擇語言'; 326 | 327 | @override 328 | String get settings => '設定'; 329 | 330 | @override 331 | String get traditionalChinese => '正體中文'; 332 | 333 | @override 334 | String get uid => 'UID'; 335 | 336 | @override 337 | String get upload => '上傳'; 338 | 339 | @override 340 | String get usbDeviceNotFound => '找不到 USB 裝置'; 341 | 342 | @override 343 | String get usbDisconnected => 'USB 已斷線'; 344 | 345 | @override 346 | String get systemDefault => '系統預設'; 347 | 348 | @override 349 | String get simplifiedChinese => '簡體中文'; 350 | 351 | @override 352 | String get japanese => '日文'; 353 | } 354 | -------------------------------------------------------------------------------- /Crapto1Native/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /lib/services/chameleonClient.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'dart:convert'; 3 | import 'dart:async'; 4 | import 'package:usb_serial/usb_serial.dart'; 5 | 6 | import 'xmodem.dart'; 7 | 8 | class Slot { 9 | Slot(this.index); 10 | 11 | final int index; 12 | String? uid; 13 | int? memorySize; 14 | String? mode; 15 | String? button; 16 | String? longPressButton; 17 | } 18 | 19 | class ChameleonCommands { 20 | static var v1_0 = ChameleonCommands(); 21 | static var v1_3 = ChameleonCommandsV1_3(); 22 | 23 | String get getVersion => 'VERSIONMY?'; 24 | String get active => 'SETTINGMY='; 25 | String get getActive => 'SETTINGMY?'; 26 | String get getCommands => 'HELPMY'; 27 | String get getModes => 'CONFIGMY'; 28 | String get getButtonModes => 'BUTTONMY'; 29 | String get getLongPressButtonModes => 'BUTTON_LONGMY'; 30 | String get getMemorySize => 'MEMSIZEMY?'; 31 | String get getUidSize => 'UIDSIZEMY?'; 32 | String get getUid => 'UIDMY?'; 33 | String get setUid => 'UIDMY='; 34 | String get getMode => 'CONFIGMY?'; 35 | String get setMode => 'CONFIGMY='; 36 | String get getButton => 'BUTTONMY?'; 37 | String get setButton => 'BUTTONMY='; 38 | String get getLongPressButton => 'BUTTON_LONGMY?'; 39 | String get setLongPressButton => 'BUTTON_LONGMY='; 40 | String get getReadOnly => 'READONLYMY?'; 41 | String get setReadOnly => 'READONLYMY='; 42 | String get getDetection => 'DETECTIONMY?'; 43 | String get clearDetection => 'DETECTIONMY='; 44 | String get reset => 'RESETMY'; 45 | String get clear => 'CLEARMY'; 46 | String get getRssi => 'RSSIMY?'; 47 | String get download => 'DOWNLOADMY'; 48 | String get upload => 'UPLOADMY'; 49 | } 50 | 51 | class ChameleonCommandsV1_3 extends ChameleonCommands { 52 | @override 53 | String get getVersion => 'VERSION?'; 54 | @override 55 | String get active => 'SETTING='; 56 | @override 57 | String get getActive => 'SETTING?'; 58 | @override 59 | String get getCommands => 'HELP'; 60 | @override 61 | String get getModes => 'CONFIG'; 62 | @override 63 | String get getButtonModes => 'BUTTON'; 64 | @override 65 | String get getLongPressButtonModes => 'BUTTON_LONG'; 66 | @override 67 | String get getMemorySize => 'MEMSIZE?'; 68 | @override 69 | String get getUidSize => 'UIDSIZE?'; 70 | @override 71 | String get getUid => 'UID?'; 72 | @override 73 | String get setUid => 'UID='; 74 | @override 75 | String get getMode => 'CONFIG?'; 76 | @override 77 | String get setMode => 'CONFIG='; 78 | @override 79 | String get getButton => 'BUTTON?'; 80 | @override 81 | String get setButton => 'BUTTON='; 82 | @override 83 | String get getLongPressButton => 'BUTTON_LONG?'; 84 | @override 85 | String get setLongPressButton => 'BUTTON_LONG='; 86 | @override 87 | String get getReadOnly => 'READONLY?'; 88 | @override 89 | String get setReadOnly => 'READONLY='; 90 | @override 91 | String get getDetection => 'DETECTION?'; 92 | @override 93 | String get clearDetection => 'DETECTION='; 94 | @override 95 | String get reset => 'RESET'; 96 | @override 97 | String get clear => 'CLEAR'; 98 | @override 99 | String get getRssi => 'RSSI?'; 100 | @override 101 | String get download => 'DOWNLOAD'; 102 | @override 103 | String get upload => 'UPLOAD'; 104 | } 105 | 106 | class ChameleonClient { 107 | ChameleonCommands commands = ChameleonCommands.v1_0; 108 | final asciiCodec = AsciiCodec(); 109 | UsbPort? port; 110 | StreamSubscription? subcription; 111 | 112 | ChameleonClient([this.port]); 113 | 114 | Future close() async { 115 | await subcription?.cancel(); 116 | subcription = null; 117 | await port?.close(); 118 | port = null; 119 | } 120 | 121 | Future sendCommandXmodem(String cmd) async { 122 | var xmodem = Xmodem(port!.inputStream!, port!.write); 123 | await sendCommand(cmd); 124 | return xmodem; 125 | } 126 | 127 | void sendCommandWithoutWait(String cmd) { 128 | print(cmd); 129 | var data = asciiCodec.encode('$cmd\r\n'); 130 | port!.write(data); 131 | } 132 | 133 | Future sendCommandRaw(String cmd) async { 134 | print(cmd); 135 | var data = asciiCodec.encode('$cmd\r\n'); 136 | var c = new Completer(); 137 | if (subcription == null) 138 | subcription = port!.inputStream!.listen(null); 139 | subcription!.onData((bytes) { 140 | c.complete(bytes); 141 | subcription!.onData(null); 142 | }); 143 | await port!.write(data); 144 | return await c.future; 145 | } 146 | 147 | Future sendCommand(String cmd) async { 148 | var bytes = await sendCommandRaw(cmd); 149 | var str = asciiCodec.decode(bytes); 150 | print(str); 151 | var strs = str.split('\r\n').where((s) => s.isNotEmpty).toList(); 152 | if (strs[0].startsWith('100:') || // 100:OK 153 | strs[0].startsWith('110:')) { // 110:WAITING FOR XMODEM 154 | return ''; 155 | } else if (strs[0].startsWith('101:')) { // 101:OK WITH TEXT 156 | return strs[strs.length - 1]; 157 | } else { 158 | throw str[0]; 159 | } 160 | } 161 | 162 | bool get connected => port != null; 163 | 164 | Future getVersion() => sendCommand(commands.getVersion); 165 | 166 | Future active(int index) async => await sendCommand(commands.active + index.toString()); 167 | 168 | Future getActive() async { 169 | var result = await sendCommand(commands.getActive); 170 | return int.parse(result[result.length - 1]); 171 | } 172 | 173 | Future> getCommands() async { 174 | var result = await sendCommand(commands.getCommands); 175 | return result.split(','); 176 | } 177 | 178 | Future> getModes() async { 179 | var result = await sendCommand(commands.getModes); 180 | return result.split(','); 181 | } 182 | 183 | Future> getButtonModes() async { 184 | var result = await sendCommand(commands.getButtonModes); 185 | return result.split(','); 186 | } 187 | 188 | Future> getLongPressButtonModes() async { 189 | var result = await sendCommand(commands.getLongPressButtonModes); 190 | return result.split(','); 191 | } 192 | 193 | Future getMemorySize() async => int.parse(await sendCommand(commands.getMemorySize)); 194 | 195 | Future getUidSize() async => int.parse(await sendCommand(commands.getUidSize)); 196 | 197 | Future getUid() => sendCommand(commands.getUid); 198 | 199 | Future setUid(String uid) => sendCommand(commands.setUid + uid); 200 | 201 | Future getMode() => sendCommand(commands.getMode); 202 | 203 | Future setMode(String mode) => sendCommand(commands.setMode + mode); 204 | 205 | Future getButton() => sendCommand(commands.getButton); 206 | 207 | Future setButton(String mode) => sendCommand(commands.setButton + mode); 208 | 209 | Future getLongPressButton() => sendCommand(commands.getLongPressButton); 210 | 211 | Future setLongPressButton(String mode) => sendCommand(commands.setLongPressButton + mode); 212 | 213 | Future getReadOnly() async => (await sendCommand(commands.getReadOnly) == '1' ? true : false); 214 | 215 | Future setReadOnly(bool state) => sendCommand(commands.setReadOnly + (state ? '1' : '0')); 216 | 217 | Future getDetection() => sendCommandRaw(commands.getDetection); 218 | 219 | Future clearDetection() => sendCommand(commands.clearDetection); 220 | 221 | void reset() => sendCommandWithoutWait(commands.reset); 222 | 223 | Future clear() => sendCommand(commands.clear); 224 | 225 | Future getRssi() => sendCommand(commands.getRssi); 226 | 227 | Future download() async { 228 | var xmodem = await sendCommandXmodem(commands.download); 229 | return await xmodem.receive(); 230 | } 231 | 232 | Future upload(Uint8List data) async { 233 | var xmodem = await sendCommandXmodem(commands.upload); 234 | await xmodem.send(data); 235 | } 236 | 237 | Future> refreshAll() async { 238 | var selectedSlot = await getActive(); 239 | var slots = []; 240 | for (int i = 0; i < 8; i++) 241 | slots.add((await refresh(i))!); 242 | await active(selectedSlot); 243 | return slots; 244 | } 245 | 246 | Future refresh(int i) async { 247 | await active(i); 248 | var selectedSlot = await getActive(); 249 | if (selectedSlot != i) 250 | return null; 251 | var slot = Slot(i); 252 | slot.uid = await getUid(); 253 | slot.mode = await getMode(); 254 | slot.button = await getButton(); 255 | try { 256 | slot.longPressButton = await getLongPressButton(); 257 | } catch (e) { } 258 | slot.memorySize = await getMemorySize(); 259 | return slot; 260 | } 261 | 262 | Future checkCommand() async { 263 | try { 264 | this.commands = ChameleonCommands.v1_0; 265 | await this.getVersion(); 266 | } catch (e) { 267 | this.commands = ChameleonCommands.v1_3; 268 | } 269 | } 270 | 271 | static void decryptData(Uint8List arr, int key, int size) 272 | { 273 | for (int i = 0; i < size; i++) 274 | arr[i] = size + key + i - size ~/ key ^ arr[i]; 275 | } 276 | } 277 | 278 | class Crc 279 | { 280 | static const CRC16_14443_A = 0x6363; 281 | static const CRC16_14443_B = 0xFFFF; 282 | 283 | static int _updateCrc14443(int b, int crc) 284 | { 285 | int ch = b ^ (crc & 0x00ff); 286 | ch = (ch ^ (ch << 4)) & 0xFF; 287 | return ((crc >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4)) & 0xFFFF; 288 | } 289 | 290 | static int _computeCrc14443(int crcType, Uint8List bytes, int len) 291 | { 292 | if (len < 2) 293 | return -1; 294 | var res = crcType; 295 | 296 | for (int i = 0; i < len; i++) 297 | res = _updateCrc14443(bytes[i], res); 298 | 299 | if (crcType == CRC16_14443_B) 300 | res = ~res & 0xFFFF; /* ISO/IEC 13239 (formerly ISO/IEC 3309) */ 301 | return res; 302 | } 303 | 304 | static bool checkCrc14443(int crcType, Uint8List bytes, int len) 305 | { 306 | if (len < 3) return false; 307 | 308 | var res = _computeCrc14443(crcType, bytes, len - 2); 309 | if (res == (bytes[len - 2] | bytes[len - 1] << 8)) 310 | return true; 311 | return false; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /lib/l10n/app_localizations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/widgets.dart'; 5 | import 'package:flutter_localizations/flutter_localizations.dart'; 6 | import 'package:intl/intl.dart' as intl; 7 | 8 | import 'app_localizations_en.dart'; 9 | import 'app_localizations_ja.dart'; 10 | import 'app_localizations_zh.dart'; 11 | 12 | // ignore_for_file: type=lint 13 | 14 | /// Callers can lookup localized strings with an instance of AppLocalizations 15 | /// returned by `AppLocalizations.of(context)`. 16 | /// 17 | /// Applications need to include `AppLocalizations.delegate()` in their app's 18 | /// `localizationDelegates` list, and the locales they support in the app's 19 | /// `supportedLocales` list. For example: 20 | /// 21 | /// ```dart 22 | /// import 'l10n/app_localizations.dart'; 23 | /// 24 | /// return MaterialApp( 25 | /// localizationsDelegates: AppLocalizations.localizationsDelegates, 26 | /// supportedLocales: AppLocalizations.supportedLocales, 27 | /// home: MyApplicationHome(), 28 | /// ); 29 | /// ``` 30 | /// 31 | /// ## Update pubspec.yaml 32 | /// 33 | /// Please make sure to update your pubspec.yaml to include the following 34 | /// packages: 35 | /// 36 | /// ```yaml 37 | /// dependencies: 38 | /// # Internationalization support. 39 | /// flutter_localizations: 40 | /// sdk: flutter 41 | /// intl: any # Use the pinned version from flutter_localizations 42 | /// 43 | /// # Rest of dependencies 44 | /// ``` 45 | /// 46 | /// ## iOS Applications 47 | /// 48 | /// iOS applications define key application metadata, including supported 49 | /// locales, in an Info.plist file that is built into the application bundle. 50 | /// To configure the locales supported by your app, you’ll need to edit this 51 | /// file. 52 | /// 53 | /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. 54 | /// Then, in the Project Navigator, open the Info.plist file under the Runner 55 | /// project’s Runner folder. 56 | /// 57 | /// Next, select the Information Property List item, select Add Item from the 58 | /// Editor menu, then select Localizations from the pop-up menu. 59 | /// 60 | /// Select and expand the newly-created Localizations item then, for each 61 | /// locale your application supports, add a new item and select the locale 62 | /// you wish to add from the pop-up menu in the Value field. This list should 63 | /// be consistent with the languages listed in the AppLocalizations.supportedLocales 64 | /// property. 65 | abstract class AppLocalizations { 66 | AppLocalizations(String locale) 67 | : localeName = intl.Intl.canonicalizedLocale(locale.toString()); 68 | 69 | final String localeName; 70 | 71 | static AppLocalizations? of(BuildContext context) { 72 | return Localizations.of(context, AppLocalizations); 73 | } 74 | 75 | static const LocalizationsDelegate delegate = 76 | _AppLocalizationsDelegate(); 77 | 78 | /// A list of this localizations delegate along with the default localizations 79 | /// delegates. 80 | /// 81 | /// Returns a list of localizations delegates containing this delegate along with 82 | /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, 83 | /// and GlobalWidgetsLocalizations.delegate. 84 | /// 85 | /// Additional delegates can be added by appending to this list in 86 | /// MaterialApp. This list does not have to be used at all if a custom list 87 | /// of delegates is preferred or required. 88 | static const List> localizationsDelegates = 89 | >[ 90 | delegate, 91 | GlobalMaterialLocalizations.delegate, 92 | GlobalCupertinoLocalizations.delegate, 93 | GlobalWidgetsLocalizations.delegate, 94 | ]; 95 | 96 | /// A list of this localizations delegate's supported locales. 97 | static const List supportedLocales = [ 98 | Locale('en'), 99 | Locale('ja'), 100 | Locale('zh'), 101 | Locale('zh', 'CN'), 102 | Locale('zh', 'TW'), 103 | ]; 104 | 105 | /// No description provided for @apply. 106 | /// 107 | /// In en, this message translates to: 108 | /// **'Apply'** 109 | String get apply; 110 | 111 | /// No description provided for @attacking. 112 | /// 113 | /// In en, this message translates to: 114 | /// **'Attacking'** 115 | String get attacking; 116 | 117 | /// No description provided for @button. 118 | /// 119 | /// In en, this message translates to: 120 | /// **'Button'** 121 | String get button; 122 | 123 | /// No description provided for @clear. 124 | /// 125 | /// In en, this message translates to: 126 | /// **'Clear'** 127 | String get clear; 128 | 129 | /// No description provided for @close. 130 | /// 131 | /// In en, this message translates to: 132 | /// **'Close'** 133 | String get close; 134 | 135 | /// No description provided for @confirmClear. 136 | /// 137 | /// In en, this message translates to: 138 | /// **'Are you sure you want to clear?'** 139 | String get confirmClear; 140 | 141 | /// No description provided for @crapto1Implementation. 142 | /// 143 | /// In en, this message translates to: 144 | /// **'Crapto1 Implementation'** 145 | String get crapto1Implementation; 146 | 147 | /// No description provided for @crapto1Dart. 148 | /// 149 | /// In en, this message translates to: 150 | /// **'Dart with Single-Thread'** 151 | String get crapto1Dart; 152 | 153 | /// No description provided for @crapto1Java. 154 | /// 155 | /// In en, this message translates to: 156 | /// **'Java with Multi-Thread'** 157 | String get crapto1Java; 158 | 159 | /// No description provided for @crapto1Online. 160 | /// 161 | /// In en, this message translates to: 162 | /// **'Online (Server maybe offline)'** 163 | String get crapto1Online; 164 | 165 | /// No description provided for @crapto1Native. 166 | /// 167 | /// In en, this message translates to: 168 | /// **'.NET8 NativeAOT (ARM64 only)'** 169 | String get crapto1Native; 170 | 171 | /// No description provided for @deviceInfo. 172 | /// 173 | /// In en, this message translates to: 174 | /// **'Device Info'** 175 | String get deviceInfo; 176 | 177 | /// No description provided for @disconnect. 178 | /// 179 | /// In en, this message translates to: 180 | /// **'Disconnect'** 181 | String get disconnect; 182 | 183 | /// No description provided for @download. 184 | /// 185 | /// In en, this message translates to: 186 | /// **'Download'** 187 | String get download; 188 | 189 | /// No description provided for @downloading. 190 | /// 191 | /// In en, this message translates to: 192 | /// **'Downloading'** 193 | String get downloading; 194 | 195 | /// No description provided for @chameleonMiniApp. 196 | /// 197 | /// In en, this message translates to: 198 | /// **'Chameleon Mini App'** 199 | String get chameleonMiniApp; 200 | 201 | /// No description provided for @english. 202 | /// 203 | /// In en, this message translates to: 204 | /// **'English'** 205 | String get english; 206 | 207 | /// No description provided for @generalSetting. 208 | /// 209 | /// In en, this message translates to: 210 | /// **'General Setting'** 211 | String get generalSetting; 212 | 213 | /// No description provided for @language. 214 | /// 215 | /// In en, this message translates to: 216 | /// **'Language'** 217 | String get language; 218 | 219 | /// No description provided for @longPressButton. 220 | /// 221 | /// In en, this message translates to: 222 | /// **'Long Press Button'** 223 | String get longPressButton; 224 | 225 | /// No description provided for @memorySize. 226 | /// 227 | /// In en, this message translates to: 228 | /// **'Memory Size'** 229 | String get memorySize; 230 | 231 | /// No description provided for @mfkey32. 232 | /// 233 | /// In en, this message translates to: 234 | /// **'mfkey32'** 235 | String get mfkey32; 236 | 237 | /// No description provided for @mode. 238 | /// 239 | /// In en, this message translates to: 240 | /// **'Mode'** 241 | String get mode; 242 | 243 | /// No description provided for @notAvailable. 244 | /// 245 | /// In en, this message translates to: 246 | /// **'N/A'** 247 | String get notAvailable; 248 | 249 | /// No description provided for @refresh. 250 | /// 251 | /// In en, this message translates to: 252 | /// **'Refresh'** 253 | String get refresh; 254 | 255 | /// No description provided for @reset. 256 | /// 257 | /// In en, this message translates to: 258 | /// **'Reset'** 259 | String get reset; 260 | 261 | /// No description provided for @slot. 262 | /// 263 | /// In en, this message translates to: 264 | /// **'Slot'** 265 | String get slot; 266 | 267 | /// No description provided for @selectLanguage. 268 | /// 269 | /// In en, this message translates to: 270 | /// **'Select Language'** 271 | String get selectLanguage; 272 | 273 | /// No description provided for @settings. 274 | /// 275 | /// In en, this message translates to: 276 | /// **'Settings'** 277 | String get settings; 278 | 279 | /// No description provided for @traditionalChinese. 280 | /// 281 | /// In en, this message translates to: 282 | /// **'Traditional Chinese'** 283 | String get traditionalChinese; 284 | 285 | /// No description provided for @uid. 286 | /// 287 | /// In en, this message translates to: 288 | /// **'UID'** 289 | String get uid; 290 | 291 | /// No description provided for @upload. 292 | /// 293 | /// In en, this message translates to: 294 | /// **'Upload'** 295 | String get upload; 296 | 297 | /// No description provided for @usbDeviceNotFound. 298 | /// 299 | /// In en, this message translates to: 300 | /// **'USB device not found'** 301 | String get usbDeviceNotFound; 302 | 303 | /// No description provided for @usbDisconnected. 304 | /// 305 | /// In en, this message translates to: 306 | /// **'USB Disconnected'** 307 | String get usbDisconnected; 308 | 309 | /// No description provided for @systemDefault. 310 | /// 311 | /// In en, this message translates to: 312 | /// **'System Default'** 313 | String get systemDefault; 314 | 315 | /// No description provided for @simplifiedChinese. 316 | /// 317 | /// In en, this message translates to: 318 | /// **'Simplified Chinese'** 319 | String get simplifiedChinese; 320 | 321 | /// No description provided for @japanese. 322 | /// 323 | /// In en, this message translates to: 324 | /// **'Japanese'** 325 | String get japanese; 326 | } 327 | 328 | class _AppLocalizationsDelegate 329 | extends LocalizationsDelegate { 330 | const _AppLocalizationsDelegate(); 331 | 332 | @override 333 | Future load(Locale locale) { 334 | return SynchronousFuture(lookupAppLocalizations(locale)); 335 | } 336 | 337 | @override 338 | bool isSupported(Locale locale) => 339 | ['en', 'ja', 'zh'].contains(locale.languageCode); 340 | 341 | @override 342 | bool shouldReload(_AppLocalizationsDelegate old) => false; 343 | } 344 | 345 | AppLocalizations lookupAppLocalizations(Locale locale) { 346 | // Lookup logic when language+country codes are specified. 347 | switch (locale.languageCode) { 348 | case 'zh': 349 | { 350 | switch (locale.countryCode) { 351 | case 'CN': 352 | return AppLocalizationsZhCn(); 353 | case 'TW': 354 | return AppLocalizationsZhTw(); 355 | } 356 | break; 357 | } 358 | } 359 | 360 | // Lookup logic when only language code is specified. 361 | switch (locale.languageCode) { 362 | case 'en': 363 | return AppLocalizationsEn(); 364 | case 'ja': 365 | return AppLocalizationsJa(); 366 | case 'zh': 367 | return AppLocalizationsZh(); 368 | } 369 | 370 | throw FlutterError( 371 | 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 372 | 'an issue with the localizations generation tool. Please file an issue ' 373 | 'on GitHub with a reproducible sample app and the gen-l10n configuration ' 374 | 'that was used.', 375 | ); 376 | } 377 | -------------------------------------------------------------------------------- /lib/views/home/slotView.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:intl/intl.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/services.dart'; 9 | import 'package:flutter_file_dialog/flutter_file_dialog.dart'; 10 | import 'package:nfc_manager/nfc_manager.dart'; 11 | import 'package:nfc_manager/nfc_manager_android.dart'; 12 | 13 | import '../../services/chameleonClient.dart'; 14 | import '../../view_models/slotViewModel.dart'; 15 | import 'package:chameleon_mini_app/l10n/app_localizations.dart'; 16 | 17 | class SlotView extends StatefulWidget { 18 | SlotView( 19 | this.slot, 20 | this.client, { 21 | Key? key, 22 | this.modes, 23 | this.buttonModes, 24 | this.longPressButtonModes, 25 | }) : super(key: key); 26 | 27 | final Slot slot; 28 | final ChameleonClient client; 29 | final List? modes, buttonModes, longPressButtonModes; 30 | 31 | @override 32 | _SlotViewState createState() => _SlotViewState(); 33 | } 34 | 35 | class _SlotViewState extends State { 36 | late SlotViewModel _viewModel; 37 | final GlobalKey _formKey = GlobalKey(); 38 | FocusNode uidFocusNode = FocusNode(); 39 | 40 | @override 41 | void initState() { 42 | super.initState(); 43 | _viewModel = SlotViewModel(widget.slot, widget.client); 44 | _viewModel.addListener(_onViewModelChanged); 45 | } 46 | 47 | @override 48 | void dispose() { 49 | _viewModel.removeListener(_onViewModelChanged); 50 | _viewModel.dispose(); 51 | super.dispose(); 52 | } 53 | 54 | void _onViewModelChanged() { 55 | setState(() {}); 56 | } 57 | 58 | _uidChanged(String str) => _viewModel.setUid(str); 59 | _uidEditingComplete() { 60 | uidFocusNode.unfocus(); 61 | print(widget.slot.uid); 62 | } 63 | 64 | void _modeChanged(String? str) => _viewModel.setMode(str); 65 | void _buttonModeChanged(String? str) => _viewModel.setButton(str); 66 | void _longPressButtonModeChanged(String? str) => 67 | _viewModel.setLongPressButton(str); 68 | 69 | Future _refresh() async { 70 | await _viewModel.refresh(); 71 | } 72 | 73 | Future _apply() async { 74 | await _viewModel.apply(); 75 | if (!mounted) return; 76 | final snackBar = const SnackBar(content: const Text('Applied')); 77 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 78 | } 79 | 80 | Future _upload() async { 81 | final params = OpenFileDialogParams( 82 | dialogType: OpenFileDialogType.document, 83 | ); 84 | final filePath = await FlutterFileDialog.pickFile(params: params); 85 | print(filePath); 86 | if (filePath == null) return; 87 | var file = File(filePath); 88 | Uint8List data; 89 | if (filePath.endsWith('.bin')) { 90 | data = Uint8List.fromList(await file.readAsBytes()); 91 | } else { 92 | var str = (await file.readAsLines()) 93 | .where((str) => str.length == 32) 94 | .map((str) => str.replaceAll('-', 'F')) 95 | .join(); 96 | data = SlotViewModel.stringToBytes(str); 97 | } 98 | await _viewModel.upload(data); 99 | if (!mounted) return; 100 | final snackBar = const SnackBar( 101 | content: const Text('Upload dump file success.'), 102 | ); 103 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 104 | } 105 | 106 | Future _mfkey32() async { 107 | showDialog( 108 | context: context, 109 | barrierDismissible: false, 110 | builder: (context) => PopScope( 111 | canPop: false, 112 | child: Dialog( 113 | child: Container( 114 | padding: const EdgeInsets.all(16), 115 | child: Row( 116 | mainAxisSize: MainAxisSize.min, 117 | children: [ 118 | const CircularProgressIndicator(), 119 | Container( 120 | padding: const EdgeInsets.only(left: 16), 121 | child: Text(AppLocalizations.of(context)!.attacking), 122 | ), 123 | ], 124 | ), 125 | ), 126 | ), 127 | ), 128 | ); 129 | List? list; 130 | String? errorMessage; 131 | try { 132 | list = await _viewModel.mfkey32(); 133 | } on Mfkey32Exception catch (e) { 134 | errorMessage = e.cause; 135 | } finally { 136 | if (mounted) Navigator.pop(context); 137 | } 138 | if (!mounted) return; 139 | if (errorMessage != null) { 140 | showDialog( 141 | context: context, 142 | barrierDismissible: true, 143 | builder: (BuildContext context) { 144 | return AlertDialog( 145 | title: const Text("mfkey32 result"), 146 | content: Text(errorMessage!), 147 | actions: [ 148 | MaterialButton( 149 | child: const Text("Close"), 150 | onPressed: () { 151 | Navigator.pop(context); 152 | }, 153 | ), 154 | ], 155 | ); 156 | }, 157 | ); 158 | } 159 | if (list != null && list.length > 0) { 160 | final result = list.join('\n'); 161 | var thisContext = context; 162 | showDialog( 163 | context: context, 164 | barrierDismissible: false, 165 | builder: (BuildContext context) { 166 | return AlertDialog( 167 | title: const Text("mfkey32 result"), 168 | content: Text(result), 169 | actions: [ 170 | TextButton( 171 | child: const Text("Copy and Close"), 172 | onPressed: () { 173 | Clipboard.setData(ClipboardData(text: result)); 174 | Navigator.pop(context); 175 | final snackBar = const SnackBar( 176 | content: const Text('Copied to clipboard.'), 177 | duration: Duration(seconds: 3), 178 | ); 179 | ScaffoldMessenger.of(thisContext).showSnackBar(snackBar); 180 | }, 181 | ), 182 | ], 183 | ); 184 | }, 185 | ); 186 | } 187 | } 188 | 189 | Future _download() async { 190 | showDialog( 191 | context: context, 192 | barrierDismissible: false, 193 | builder: (context) => PopScope( 194 | canPop: false, 195 | child: Dialog( 196 | child: Container( 197 | padding: const EdgeInsets.all(16), 198 | child: Row( 199 | mainAxisSize: MainAxisSize.min, 200 | children: [ 201 | const CircularProgressIndicator(), 202 | Container( 203 | padding: const EdgeInsets.only(left: 16), 204 | child: Text(AppLocalizations.of(context)!.downloading), 205 | ), 206 | ], 207 | ), 208 | ), 209 | ), 210 | ), 211 | ); 212 | try { 213 | var mctFormat = await _viewModel.downloadMct(); 214 | 215 | var now = DateTime.now(); 216 | var formatter = DateFormat('yyyy-MM-dd_HH-mm-ss'); 217 | var fileName = 'UID_${_viewModel.slot.uid}_${formatter.format(now)}.mct'; 218 | final params = SaveFileDialogParams( 219 | fileName: fileName, 220 | data: Uint8List.fromList(utf8.encode(mctFormat)), 221 | ); 222 | final filePath = await FlutterFileDialog.saveFile(params: params); 223 | print(filePath); 224 | if (filePath == null) return; 225 | if (!mounted) return; 226 | final snackBar = const SnackBar(content: const Text('Saved')); 227 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 228 | } finally { 229 | if (mounted) Navigator.pop(context); 230 | } 231 | } 232 | 233 | Future _nfc() async { 234 | try { 235 | final snackBar = SnackBar( 236 | content: const Text('Start scan card.'), 237 | duration: const Duration(hours: 1), 238 | action: SnackBarAction( 239 | label: 'Cancel', 240 | onPressed: () async { 241 | await NfcManager.instance.stopSession(); 242 | }, 243 | ), 244 | ); 245 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 246 | NfcManager.instance.startSession( 247 | pollingOptions: {NfcPollingOption.iso14443, NfcPollingOption.iso15693}, 248 | onDiscovered: (NfcTag tag) async { 249 | var nfca = NfcAAndroid.from(tag); 250 | if (nfca != null) { 251 | var str = nfca.tag.id 252 | .map((e) => e.toRadixString(16).toUpperCase().padLeft(2, '0')) 253 | .join(); 254 | print(str); 255 | if (mounted) { 256 | _viewModel.setUid(str); 257 | } 258 | } 259 | if (mounted) ScaffoldMessenger.of(context).hideCurrentSnackBar(); 260 | await Future.delayed(const Duration(seconds: 1)); 261 | await NfcManager.instance.stopSession(); 262 | }, 263 | ); 264 | } on PlatformException {} 265 | } 266 | 267 | Future _clear() async { 268 | final bool? confirmed = await showDialog( 269 | context: context, 270 | builder: (BuildContext context) { 271 | return AlertDialog( 272 | title: Text(AppLocalizations.of(context)!.clear), 273 | content: Text(AppLocalizations.of(context)!.confirmClear), 274 | actions: [ 275 | TextButton( 276 | child: Text(AppLocalizations.of(context)!.close), 277 | onPressed: () => Navigator.of(context).pop(false), 278 | ), 279 | TextButton( 280 | child: Text(AppLocalizations.of(context)!.clear), 281 | onPressed: () => Navigator.of(context).pop(true), 282 | ), 283 | ], 284 | ); 285 | }, 286 | ); 287 | 288 | if (confirmed != true) return; 289 | 290 | await _viewModel.clear(); 291 | if (!mounted) return; 292 | final snackBar = const SnackBar(content: const Text('Cleared')); 293 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 294 | } 295 | 296 | @override 297 | Widget build(BuildContext context) { 298 | return SafeArea( 299 | top: false, 300 | bottom: false, 301 | child: Form( 302 | key: _formKey, 303 | autovalidateMode: AutovalidateMode.always, 304 | child: SingleChildScrollView( 305 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 306 | child: Column( 307 | children: [ 308 | const SizedBox(height: 8.0), 309 | DropdownButtonFormField( 310 | decoration: InputDecoration( 311 | icon: const Icon(Icons.functions), 312 | labelText: AppLocalizations.of(context)!.mode, 313 | ), 314 | disabledHint: Text(AppLocalizations.of(context)!.notAvailable), 315 | initialValue: widget.slot.mode, 316 | isDense: true, 317 | items: widget.modes 318 | ?.map( 319 | (str) => DropdownMenuItem(value: str, child: Text(str)), 320 | ) 321 | .toList(), 322 | onChanged: _modeChanged, 323 | ), 324 | const SizedBox(height: 8.0), 325 | TextField( 326 | enabled: widget.client.connected, 327 | focusNode: uidFocusNode, 328 | controller: TextEditingController(text: widget.slot.uid), 329 | decoration: InputDecoration( 330 | icon: const Icon(Icons.fingerprint), 331 | labelText: AppLocalizations.of(context)!.uid, 332 | suffixIcon: IconButton( 333 | icon: const Icon(Icons.nfc), 334 | onPressed: _nfc, 335 | ), 336 | ), 337 | keyboardType: TextInputType.text, 338 | inputFormatters: [ 339 | FilteringTextInputFormatter.allow( 340 | RegExp(r'^[0-9a-fA-F]{0,14}'), 341 | ), 342 | ], 343 | onChanged: _uidChanged, 344 | onEditingComplete: _uidEditingComplete, 345 | ), 346 | const SizedBox(height: 8.0), 347 | DropdownButtonFormField( 348 | decoration: InputDecoration( 349 | icon: const Icon(Icons.touch_app), 350 | labelText: AppLocalizations.of(context)!.button, 351 | ), 352 | disabledHint: Text(AppLocalizations.of(context)!.notAvailable), 353 | initialValue: widget.slot.button, 354 | isDense: true, 355 | items: widget.buttonModes 356 | ?.map( 357 | (str) => DropdownMenuItem(value: str, child: Text(str)), 358 | ) 359 | .toList(), 360 | onChanged: _buttonModeChanged, 361 | ), 362 | const SizedBox(height: 8.0), 363 | DropdownButtonFormField( 364 | decoration: InputDecoration( 365 | icon: const Icon(Icons.touch_app), 366 | labelText: AppLocalizations.of(context)!.longPressButton, 367 | ), 368 | disabledHint: Text(AppLocalizations.of(context)!.notAvailable), 369 | initialValue: widget.slot.longPressButton, 370 | isDense: true, 371 | items: widget.longPressButtonModes 372 | ?.map( 373 | (str) => DropdownMenuItem(value: str, child: Text(str)), 374 | ) 375 | .toList(), 376 | onChanged: _longPressButtonModeChanged, 377 | ), 378 | const SizedBox(height: 8.0), 379 | TextField( 380 | enabled: false, 381 | controller: TextEditingController( 382 | text: widget.slot.memorySize?.toString(), 383 | ), 384 | decoration: InputDecoration( 385 | icon: const Icon(Icons.memory), 386 | labelText: AppLocalizations.of(context)!.memorySize, 387 | ), 388 | ), 389 | const SizedBox(height: 16.0), 390 | Column( 391 | children: [ 392 | Row( 393 | children: [ 394 | Expanded( 395 | child: FilledButton.icon( 396 | icon: const Icon(Icons.refresh), 397 | label: Text(AppLocalizations.of(context)!.refresh), 398 | onPressed: widget.client.connected ? _refresh : null, 399 | ), 400 | ), 401 | const SizedBox(width: 16.0), 402 | Expanded( 403 | child: FilledButton.icon( 404 | icon: const Icon(Icons.check_circle_outline), 405 | label: Text(AppLocalizations.of(context)!.apply), 406 | onPressed: widget.client.connected ? _apply : null, 407 | ), 408 | ), 409 | ], 410 | ), 411 | const SizedBox(height: 16.0), 412 | Row( 413 | children: [ 414 | Expanded( 415 | child: FilledButton.icon( 416 | icon: const Icon(Icons.file_upload_outlined), 417 | label: Text(AppLocalizations.of(context)!.upload), 418 | onPressed: widget.client.connected ? _upload : null, 419 | ), 420 | ), 421 | const SizedBox(width: 16.0), 422 | Expanded( 423 | child: FilledButton.icon( 424 | icon: const Icon(Icons.file_download_outlined), 425 | label: Text(AppLocalizations.of(context)!.download), 426 | onPressed: widget.client.connected ? _download : null, 427 | ), 428 | ), 429 | ], 430 | ), 431 | const SizedBox(height: 16.0), 432 | Row( 433 | children: [ 434 | Expanded( 435 | child: FilledButton.icon( 436 | icon: const Icon(Icons.delete_outline), 437 | label: Text(AppLocalizations.of(context)!.clear), 438 | style: FilledButton.styleFrom( 439 | backgroundColor: Theme.of( 440 | context, 441 | ).colorScheme.errorContainer, 442 | foregroundColor: Theme.of( 443 | context, 444 | ).colorScheme.onErrorContainer, 445 | ), 446 | onPressed: widget.client.connected ? _clear : null, 447 | ), 448 | ), 449 | const SizedBox(width: 16.0), 450 | Expanded( 451 | child: FilledButton.icon( 452 | icon: const Icon(Icons.key), 453 | label: Text(AppLocalizations.of(context)!.mfkey32), 454 | onPressed: widget.client.connected ? _mfkey32 : null, 455 | ), 456 | ), 457 | ], 458 | ), 459 | ], 460 | ), 461 | ], 462 | ), 463 | ), 464 | ), 465 | ); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /lib/services/crapto1.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:typed_data'; 3 | import 'dart:convert'; 4 | 5 | import 'package:flutter/services.dart'; 6 | 7 | 8 | const _platform = const MethodChannel('tw.kgame.crapto1/mfkey'); 9 | 10 | class Crypto1State { 11 | int odd = 0; 12 | int even = 0; 13 | 14 | Crypto1State(this.odd, this.even); 15 | 16 | Crypto1State.fromKey(String key) { 17 | for (var i = 0; i < 6; i++) { 18 | var b = int.parse(key.substring(i << 1, i + 1 << 1), radix: 16); 19 | for (var j = 0; j < 4; j++) { 20 | even = even << 1 | b & 1; 21 | b >>= 1; 22 | odd = odd << 1 | b & 1; 23 | b >>= 1; 24 | } 25 | } 26 | } 27 | 28 | String get lfsr { 29 | var o = odd, e = even; 30 | var key = ""; 31 | for (var i = 0; i < 6; i++) { 32 | var b = 0; 33 | for (var j = 0; j < 4; j++) { 34 | b = b << 1 | o & 1; 35 | o >>= 1; 36 | b = b << 1 | e & 1; 37 | e >>= 1; 38 | } 39 | key = b.toRadixString(16) + key; 40 | } 41 | return key.toUpperCase(); 42 | } 43 | } 44 | 45 | class Crypto1 { 46 | static const int LF_POLY_ODD = 0x29CE5C; 47 | static const int LF_POLY_EVEN = 0x870804; 48 | 49 | Crypto1State state; 50 | 51 | Crypto1(this.state); 52 | 53 | int crypto1Bit([int _in = 0, bool isEncrypted = false]) { 54 | int feedin; 55 | int ret = filter(state.odd); 56 | 57 | feedin = ret & (isEncrypted ? 1 : 0); 58 | feedin ^= _in != 0 ? 1 : 0; 59 | feedin ^= LF_POLY_ODD & state.odd; 60 | feedin ^= LF_POLY_EVEN & state.even; 61 | state.even = state.even << 1 | evenParity32(feedin); 62 | 63 | int x = state.odd; 64 | state.odd = state.even; 65 | state.even = x; 66 | 67 | return ret; 68 | } 69 | 70 | int crypto1int([int _in = 0, bool isEncrypted = false]) { 71 | int ret = 0; 72 | for (var i = 0; i < 8; ++i) 73 | ret |= crypto1Bit(_bit(_in, i), isEncrypted) << i; 74 | return ret; 75 | } 76 | 77 | int crypto1Word([int _in = 0, bool isEncrypted = false]) { 78 | int ret = 0; 79 | for (var i = 0; i < 32; ++i) 80 | ret |= crypto1Bit(_beBit(_in, i), isEncrypted) << (i ^ 24); 81 | return ret; 82 | } 83 | 84 | int peekCrypto1Bit() => filter(state.odd); 85 | } 86 | 87 | int _bit(int v, int n) => v >> n & 1; 88 | 89 | int _beBit(int v, int n) => _bit(v, n ^ 24); 90 | 91 | int filter(int x) { 92 | int f; 93 | f = 0xf22c0 >> (x & 0xf) & 16; 94 | f |= 0x6c9c0 >> (x >> 4 & 0xf) & 8; 95 | f |= 0x3c8b0 >> (x >> 8 & 0xf) & 4; 96 | f |= 0x1e458 >> (x >> 12 & 0xf) & 2; 97 | f |= 0x0d938 >> (x >> 16 & 0xf) & 1; 98 | return 0xEC57E80A >> f & 1; 99 | } 100 | 101 | const List _oddintParity = [ 102 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 103 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 104 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 105 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 106 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 107 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 108 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 109 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 110 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 111 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 112 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 113 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 114 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 115 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 116 | 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 117 | 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 118 | ]; 119 | 120 | int oddParity8(int x) => _oddintParity[x & 0xFF]; 121 | 122 | int evenParity8(int x) => _oddintParity[x & 0xFF] ^ 1; 123 | 124 | int evenParity32(int x) { 125 | x ^= x >> 16; 126 | x ^= x >> 8; 127 | return evenParity8(x); 128 | } 129 | 130 | int swapEndian(int x) { 131 | x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8; 132 | x &= 0xFFFFFFFF; 133 | x = x >> 16 | x << 16; 134 | x &= 0xFFFFFFFF; 135 | return x; 136 | } 137 | 138 | int prngSuccessor(int x, int n) { 139 | x = swapEndian(x); 140 | while (n-- > 0) 141 | x = x >> 1 | ((x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) & 1) << 31; 142 | return swapEndian(x); 143 | } 144 | 145 | class Span { 146 | Uint32List list; 147 | int offset; 148 | late int length; 149 | 150 | Span(this.list, [this.offset = 0, int? length]) { 151 | if (length == null) 152 | this.length = list.length - offset; 153 | else 154 | this.length = length; 155 | } 156 | 157 | Span slice(int start, [int? len]) { 158 | return Span(list, offset + start, len != null ? len : length - start); 159 | } 160 | 161 | void sort() { 162 | var sublist = list.sublist(offset, length + offset); 163 | sublist.sort(); 164 | for (int i = 0; i < length; i++) 165 | this[i] = sublist[i]; 166 | } 167 | 168 | int binarySearch() { 169 | int start = 0, stop = this.length - 1, mid; 170 | int val = this[stop] & 0xff000000; 171 | while (start != stop) 172 | if (this[start + (mid = (stop - start) >> 1)] > val) 173 | stop = start + mid; 174 | else 175 | start += mid + 1; 176 | return start; 177 | } 178 | 179 | operator [](int i) => list[i + offset]; 180 | operator []=(int i, int value) => list[i + offset] = value; 181 | } 182 | 183 | class Crapto1 extends Crypto1 184 | { 185 | Crapto1(Crypto1State state) : super(state); 186 | 187 | int lfsrRollbackBit([int _in = 0, bool isEncrypted = false]) { 188 | int _out; 189 | int ret; 190 | 191 | state.odd &= 0xffffff; 192 | var t = state.odd; 193 | state.odd = state.even; 194 | state.even = t; 195 | 196 | _out = state.even & 1; 197 | _out ^= Crypto1.LF_POLY_EVEN & (state.even >>= 1); 198 | _out ^= Crypto1.LF_POLY_ODD & state.odd; 199 | _out ^= _in != 0 ? 1 : 0; 200 | _out ^= (ret = filter(state.odd)) & (isEncrypted ? 1 : 0); 201 | 202 | state.even |= evenParity32(_out) << 23; 203 | return ret; 204 | } 205 | 206 | int lfsrRollbackint([int _in = 0, bool isEncrypted = false]) { 207 | int ret = 0; 208 | for (var i = 7; i >= 0; --i) 209 | ret |= lfsrRollbackBit(_bit(_in, i), isEncrypted) << i; 210 | return ret; 211 | } 212 | 213 | int lfsrRollbackWord([int _in = 0, bool isEncrypted = false]) { 214 | int ret = 0; 215 | for (var i = 31; i >= 0; --i) 216 | ret |= lfsrRollbackBit(_beBit(_in, i), isEncrypted) << (i ^ 24); 217 | return ret; 218 | } 219 | 220 | static int _updateContribution(int item, int mask1, int mask2) { 221 | int p = item >> 25; 222 | p = p << 1 | evenParity32(item & mask1); 223 | p = p << 1 | evenParity32(item & mask2); 224 | item = p << 24 | (item & 0xffffff); 225 | return item & 0xFFFFFFFF; 226 | } 227 | 228 | static int _extendTable(Span tbl, int end, int bit, int m1, int m2, int _in) { 229 | _in <<= 24; 230 | var i = 0; 231 | for (tbl[i] <<= 1; i <= end; tbl[++i] <<= 1) 232 | if ((filter(tbl[i]) ^ filter(tbl[i] | 1)) != 0) { 233 | tbl[i] |= filter(tbl[i]) ^ bit; 234 | tbl[i] = _updateContribution(tbl[i], m1, m2); 235 | tbl[i] ^= _in; 236 | } else if (filter(tbl[i]) == bit) { 237 | tbl[++end] = tbl[i + 1]; 238 | tbl[i + 1] = tbl[i] | 1; 239 | tbl[i] = _updateContribution(tbl[i], m1, m2); 240 | tbl[i++] ^= _in; 241 | tbl[i] = _updateContribution(tbl[i], m1, m2); 242 | tbl[i] ^= _in; 243 | } else 244 | tbl[i--] = tbl[end--]; 245 | return end; 246 | } 247 | 248 | static int _extendTableSimple(Uint32List tbl, int end, int bit) { 249 | var i = 0; 250 | for (tbl[i] <<= 1; i <= end; tbl[++i] <<= 1) { 251 | if ((filter(tbl[i]) ^ filter(tbl[i] | 1)) != 0) { 252 | tbl[i] |= filter(tbl[i]) ^ bit; 253 | } else if (filter(tbl[i]) == bit) { 254 | tbl[++end] = tbl[++i]; 255 | tbl[i] = tbl[i - 1] | 1; 256 | } else { 257 | tbl[i--] = tbl[end--]; 258 | } 259 | } 260 | return end; 261 | } 262 | 263 | static void _recover(Span odd, int oddTail, int oks, Span even, int evenTail, int eks, int rem, List sl, int _in) { 264 | var o = 0; 265 | var e = 0; 266 | 267 | if (rem == -1) { 268 | for (e = 0; e <= evenTail; e++) { 269 | even[e] = even[e] << 1 ^ evenParity32(even[e] & Crypto1.LF_POLY_EVEN) ^ ((_in & 4) != 0 ? 1 : 0); 270 | for (o = 0; o <= oddTail; o++) { 271 | sl.add( 272 | Crypto1State( 273 | even[e] ^ evenParity32(odd[o] & Crypto1.LF_POLY_ODD), 274 | odd[o], 275 | ) 276 | ); 277 | } 278 | } 279 | return; 280 | } 281 | 282 | for (var i = 0; i < 4 && rem-- != 0; i++) { 283 | oks >>= 1; 284 | eks >>= 1; 285 | _in >>= 2; 286 | oddTail = _extendTable(odd, oddTail, oks & 1, Crypto1.LF_POLY_EVEN << 1 | 1, Crypto1.LF_POLY_ODD << 1, 0); 287 | if (0 > oddTail) 288 | return; 289 | 290 | evenTail = _extendTable(even, evenTail, eks & 1, Crypto1.LF_POLY_ODD, Crypto1.LF_POLY_EVEN << 1 | 1, _in & 3); 291 | if (0 > evenTail) 292 | return; 293 | } 294 | 295 | odd.slice(0, oddTail + 1).sort(); 296 | even.slice(0, evenTail + 1).sort(); 297 | 298 | while (oddTail >= 0 && evenTail >= 0) 299 | if (((odd[oddTail] ^ even[evenTail]) >> 24) == 0) { 300 | oddTail = odd.slice(0, (o = oddTail) + 1).binarySearch(); 301 | evenTail = even.slice(0, (e = evenTail) + 1).binarySearch(); 302 | _recover(odd.slice(oddTail), o - oddTail, oks, even.slice(evenTail), e - evenTail, eks, rem, sl, _in); 303 | oddTail--; evenTail--; 304 | } 305 | else if (odd[oddTail] > even[evenTail]) 306 | oddTail = odd.slice(0, oddTail + 1).binarySearch() - 1; 307 | else 308 | evenTail = even.slice(0, evenTail + 1).binarySearch() - 1; 309 | } 310 | 311 | static List lfsrRecovery32(int ks2, int _in) { 312 | var oks = 0; 313 | var eks = 0; 314 | 315 | for (var i = 31; i >= 0; i -= 2) 316 | oks = oks << 1 | _beBit(ks2, i); 317 | for (var i = 30; i >= 0; i -= 2) 318 | eks = eks << 1 | _beBit(ks2, i); 319 | 320 | var odd = Uint32List(4 << 21); 321 | var even = Uint32List(4 << 21); 322 | var statelist = []; 323 | var oddTail = 0; 324 | var evenTail = 0; 325 | 326 | for (var i = 1 << 20; i >= 0; --i) { 327 | if (filter(i) == (oks & 1)) 328 | odd[++oddTail] = i; 329 | if (filter(i) == (eks & 1)) 330 | even[++evenTail] = i; 331 | } 332 | 333 | for (var i = 0; i < 4; i++) { 334 | oddTail = _extendTableSimple(odd, oddTail, (oks >>= 1) & 1); 335 | evenTail = _extendTableSimple(even, evenTail, (eks >>= 1) & 1); 336 | } 337 | 338 | _in = (_in >> 16 & 0xff) | (_in << 16) | (_in & 0xff00); 339 | _recover(Span(odd), oddTail, oks, Span(even), evenTail, eks, 11, statelist, _in << 1); 340 | 341 | return statelist; 342 | } 343 | 344 | 345 | static const List _S1 = const [ 346 | 0x62141, 0x310A0, 0x18850, 0x0C428, 0x06214, 0x0310A, 347 | 0x85E30, 0xC69AD, 0x634D6, 0xB5CDE, 0xDE8DA, 0x6F46D, 348 | 0xB3C83, 0x59E41, 0xA8995, 0xD027F, 0x6813F, 0x3409F, 0x9E6FA ]; 349 | 350 | static const List _S2 = const [ 351 | 0x3A557B00, 0x5D2ABD80, 0x2E955EC0, 0x174AAF60, 0x0BA557B0, 352 | 0x05D2ABD8, 0x0449DE68, 0x048464B0, 0x42423258, 0x278192A8, 353 | 0x156042D0, 0x0AB02168, 0x43F89B30, 0x61FC4D98, 0x765EAD48, 354 | 0x7D8FDD20, 0x7EC7EE90, 0x7F63F748, 0x79117020 ]; 355 | static const List _T1 = const [ 356 | 0x4F37D, 0x279BE, 0x97A6A, 0x4BD35, 0x25E9A, 0x12F4D, 0x097A6, 0x80D66, 357 | 0xC4006, 0x62003, 0xB56B4, 0x5AB5A, 0xA9318, 0xD0F39, 0x6879C, 0xB057B, 358 | 0x582BD, 0x2C15E, 0x160AF, 0x8F6E2, 0xC3DC4, 0xE5857, 0x72C2B, 0x39615, 359 | 0x98DBF, 0xC806A, 0xE0680, 0x70340, 0x381A0, 0x98665, 0x4C332, 0xA272C ]; 360 | static const List _T2 = const [ 361 | 0x3C88B810, 0x5E445C08, 0x2982A580, 0x14C152C0, 0x4A60A960, 362 | 0x253054B0, 0x52982A58, 0x2FEC9EA8, 0x1156C4D0, 0x08AB6268, 363 | 0x42F53AB0, 0x217A9D58, 0x161DC528, 0x0DAE6910, 0x46D73488, 364 | 0x25CB11C0, 0x52E588E0, 0x6972C470, 0x34B96238, 0x5CFC3A98, 365 | 0x28DE96C8, 0x12CFC0E0, 0x4967E070, 0x64B3F038, 0x74F97398, 366 | 0x7CDC3248, 0x38CE92A0, 0x1C674950, 0x0E33A4A8, 0x01B959D0, 367 | 0x40DCACE8, 0x26CEDDF0 ]; 368 | 369 | static const List _C1 = const [ 0x846B5, 0x4235A, 0x211AD ]; 370 | static const List _C2 = const [ 0x1A822E0, 0x21A822E0, 0x21A822E0 ]; 371 | 372 | static List lfsrRecovery64(int ks2, int ks3) { 373 | var oks = Uint8List(32); 374 | var eks = Uint8List(32); 375 | var hi = Uint8List(32); 376 | var low = 0; 377 | var win = 0; 378 | var table = Uint32List(1 << 16); 379 | var statelist = []; 380 | 381 | for (var i = 30; i >= 0; i -= 2) { 382 | oks[i >> 1] = _beBit(ks2, i); 383 | oks[16 + (i >> 1)] = _beBit(ks3, i); 384 | } 385 | for (var i = 31; i >= 0; i -= 2) { 386 | eks[i >> 1] = _beBit(ks2, i); 387 | eks[16 + (i >> 1)] = _beBit(ks3, i); 388 | } 389 | 390 | 391 | for (var i = 0xfffff; i >= 0; i--) { 392 | if (filter(i) != oks[0]) 393 | continue; 394 | 395 | var tail = 0; 396 | table[tail] = i; 397 | 398 | for (var j = 1; tail >= 0 && j < 29; j++) 399 | tail = _extendTableSimple(table, tail, oks[j]); 400 | if (tail < 0) 401 | continue; 402 | 403 | for (var j = 0; j < 19; ++j) 404 | low = low << 1 | evenParity32(i & _S1[j]); 405 | for (var j = 0; j < 32; ++j) 406 | hi[j] = evenParity32(i & _T1[j]); 407 | 408 | 409 | for (; tail >= 0; --tail) { 410 | bool continue2 = false; 411 | for (var j = 0; j < 3; j++) { 412 | table[tail] = table[tail] << 1; 413 | table[tail] |= evenParity32((i & _C1[j]) ^ (table[tail] & _C2[j])); 414 | if (filter(table[tail]) != oks[29 + j]) { 415 | continue2 = true; 416 | break; 417 | } 418 | } 419 | if (continue2) continue; 420 | 421 | for (var j = 0; j < 19; j++) 422 | win = win << 1 | evenParity32(table[tail] & _S2[j]); 423 | 424 | win ^= low; 425 | for (var j = 0; j < 32; ++j) { 426 | win = win << 1 ^ hi[j] ^ evenParity32(table[tail] & _T2[j]); 427 | if (filter(win) != eks[j]) { 428 | continue2 = true; 429 | break; 430 | } 431 | } 432 | if (continue2) continue; 433 | 434 | table[tail] = table[tail] << 1 | evenParity32(Crypto1.LF_POLY_EVEN & table[tail]); 435 | statelist.add( 436 | Crypto1State( 437 | table[tail] ^ evenParity32(Crypto1.LF_POLY_ODD & win), 438 | win 439 | ) 440 | ); 441 | } 442 | } 443 | return statelist; 444 | } 445 | } 446 | 447 | class Nonce { 448 | int sector = 0, block = 0, type = 0; 449 | int nt = 0; 450 | int nr = 0; 451 | int ar = 0; 452 | 453 | Nonce(); 454 | 455 | Nonce.fromJson(Map json) 456 | : sector = json['sector'], 457 | block = json['block'], 458 | type = json['type'], 459 | nt = json['nt'], 460 | nr = json['nr'], 461 | ar = json['ar']; 462 | 463 | Map toJson() => { 464 | 'sector': this.sector, 465 | 'block': this.block, 466 | 'type': this.type, 467 | 'nt': this.nt, 468 | 'nr': this.nr, 469 | 'ar': this.ar, 470 | }; 471 | } 472 | 473 | Future mfKey32(int uid, Iterable nonces) { 474 | print('Dart mfKey32'); 475 | print('UID: ${uid.toRadixString(16)}'); 476 | print('Nonces: ${jsonEncode(nonces)}'); 477 | var nonce = nonces.first; 478 | nonces = nonces.skip(1).toList(); 479 | var p640 = prngSuccessor(nonce.nt, 64); 480 | print(p640.toRadixString(16)); 481 | var list = Crapto1.lfsrRecovery32(nonce.ar ^ p640, 0); 482 | print(list.length); 483 | var keys = []; 484 | var crapto1 = Crapto1(Crypto1State(0, 0)); 485 | var crypto1 = Crypto1(Crypto1State(0, 0)); 486 | for (var s in list) { 487 | crapto1.state = s; 488 | crapto1.lfsrRollbackWord(); 489 | crapto1.lfsrRollbackWord(nonce.nr, true); 490 | crapto1.lfsrRollbackWord(uid ^ nonce.nt); 491 | var allPass = true; 492 | for (var n in nonces) { 493 | crypto1.state.odd = s.odd; 494 | crypto1.state.even = s.even; 495 | crypto1.crypto1Word(uid ^ n.nt); 496 | crypto1.crypto1Word(n.nr, true); 497 | var p641 = prngSuccessor(n.nt, 64); 498 | if (n.ar != (crypto1.crypto1Word() ^ p641)) { 499 | allPass = false; 500 | break; 501 | } 502 | } 503 | if (allPass) 504 | keys.add(s.lfsr); 505 | } 506 | return Future.value(keys.length == 1 ? keys[0] : null); 507 | } 508 | 509 | String mfKey64(int uid, int nt, int nr, int ar, int at) { 510 | // Extract the keystream from the messages 511 | var ks2 = ar ^ prngSuccessor(nt, 64); // keystream used to encrypt reader response 512 | var ks3 = at ^ prngSuccessor(nt, 96); // keystream used to encrypt tag response 513 | var revstate = Crapto1.lfsrRecovery64(ks2, ks3)[0]; 514 | var crapto1 = Crapto1(revstate); 515 | crapto1.lfsrRollbackWord(); 516 | crapto1.lfsrRollbackWord(); 517 | crapto1.lfsrRollbackWord(nr, true); 518 | crapto1.lfsrRollbackWord(uid ^ nt); 519 | return crapto1.state.lfsr; 520 | } 521 | 522 | typedef Future MfKey32Function(int uid, List nonces); 523 | 524 | class KeyWorkMessage { 525 | MfKey32Function mfkey32; 526 | int uid; 527 | List nonces; 528 | 529 | KeyWorkMessage(this.mfkey32, this.uid, this.nonces); 530 | } 531 | 532 | extension Iterables on Iterable { 533 | Map> groupBy(K Function(E) keyFunction) => fold( 534 | >{}, 535 | (Map> map, E element) => 536 | map..putIfAbsent(keyFunction(element), () => []).add(element)); 537 | } 538 | 539 | Future> keyWork(KeyWorkMessage msg) async { 540 | var list = >[]; 541 | msg.nonces 542 | .groupBy((n) => 'Sec${n.sector} Key${n.type == 0x60 ? 'A': 'B'}') 543 | .forEach((key, ns) { 544 | if (ns.length < 2) 545 | return; 546 | list.add(ns); 547 | }); 548 | var s = Stopwatch(); 549 | s.start(); 550 | var r = []; 551 | var fs = list 552 | .map((List? ns) => msg.mfkey32(msg.uid, ns!)) 553 | .toList(); 554 | var keys = await Future.wait(fs); 555 | s.stop(); 556 | print('${s.elapsedMilliseconds}ms'); 557 | for (var i = 0; i < list.length; i++) { 558 | if (keys[i] == null) 559 | continue; 560 | var ns = list[i]; 561 | r.add('Sec${ns[0].sector} Key${ns[0].type == 0x60 ? 'A': 'B'} ${keys[i]}'); 562 | } 563 | return r; 564 | } 565 | 566 | bool _init = false; 567 | Map> _tasks = Map>(); 568 | Future mfKey32Java(int uid, List nonces) async { 569 | print('Java mfKey32'); 570 | print('UID: ${uid.toRadixString(16)}'); 571 | print('Nonces: ${jsonEncode(nonces)}'); 572 | if (!_init) { 573 | _init = true; 574 | _platform.setMethodCallHandler((call) async { 575 | int id = call.arguments['id']; 576 | int? key = call.arguments['key']; 577 | final completer = _tasks.remove(id); 578 | completer?.complete(key?.toRadixString(16).toUpperCase().padLeft(12, '0')); 579 | }); 580 | } 581 | var map = Map(); 582 | map['uid'] = uid; 583 | map['nonces'] = nonces.map>((n) { 584 | var map = Map(); 585 | map['nt'] = n.nt; 586 | map['nr'] = n.nr; 587 | map['ar'] = n.ar; 588 | return map; 589 | }).toList(); 590 | int id = await _platform.invokeMethod('mfkey32', map); 591 | var completer = Completer(); 592 | _tasks[id] = completer; 593 | return completer.future; 594 | } 595 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------