From 3639eb0c187edecabe9c407ca8b2fe8be1a794b3 Mon Sep 17 00:00:00 2001 From: Foxushka <135865149+Foxushka@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:43:48 +0300 Subject: [PATCH] feat: NTAG/Ultralight writing support + bug fixes/improvements --- .../lib/gui/component/card_list.dart | 6 +- .../lib/gui/page/saved_cards.dart | 6 +- .../lib/gui/page/write_card.dart | 9 +- chameleonultragui/lib/helpers/general.dart | 11 ++ .../helpers/mifare_classic/write/base.dart | 19 +- .../helpers/mifare_classic/write/gen1.dart | 6 +- .../helpers/mifare_classic/write/gen2.dart | 15 +- .../helpers/mifare_classic/write/gen3.dart | 4 +- .../helpers/mifare_ultralight/write/base.dart | 169 ++++++++++++++++++ .../lib/helpers/t55xx/write/base.dart | 7 +- chameleonultragui/lib/helpers/write.dart | 12 +- chameleonultragui/lib/l10n/app_en.arb | 7 +- 12 files changed, 241 insertions(+), 30 deletions(-) create mode 100644 chameleonultragui/lib/helpers/mifare_ultralight/write/base.dart diff --git a/chameleonultragui/lib/gui/component/card_list.dart b/chameleonultragui/lib/gui/component/card_list.dart index 68710386..55009981 100644 --- a/chameleonultragui/lib/gui/component/card_list.dart +++ b/chameleonultragui/lib/gui/component/card_list.dart @@ -1,6 +1,5 @@ import 'package:chameleonultragui/bridge/chameleon.dart'; import 'package:chameleonultragui/helpers/general.dart'; -import 'package:chameleonultragui/helpers/mifare_classic/general.dart'; import 'package:chameleonultragui/sharedprefsprovider.dart'; import 'package:flutter/material.dart'; @@ -157,10 +156,7 @@ class CardSearchDelegate extends SearchDelegate { color: card.color), title: Text(card.name), subtitle: Text( - chameleonTagToString(card.tag) + - ((chameleonTagSaveCheckForMifareClassicEV1(card)) - ? " EV1" - : ""), + chameleonCardToString(card), maxLines: 2, overflow: TextOverflow.ellipsis, ), diff --git a/chameleonultragui/lib/gui/page/saved_cards.dart b/chameleonultragui/lib/gui/page/saved_cards.dart index 8988ff85..54e2d512 100644 --- a/chameleonultragui/lib/gui/page/saved_cards.dart +++ b/chameleonultragui/lib/gui/page/saved_cards.dart @@ -478,11 +478,7 @@ class SavedCardsPageState extends State { : Icons.wifi, iconColor: tag.color, firstLine: tag.name.isEmpty ? "⠀" : tag.name, - secondLine: chameleonTagToString(tag.tag) + - ((chameleonTagSaveCheckForMifareClassicEV1( - tag)) - ? " EV1" - : ""), + secondLine: chameleonCardToString(tag), itemIndex: index, onPressed: () { showDialog( diff --git a/chameleonultragui/lib/gui/page/write_card.dart b/chameleonultragui/lib/gui/page/write_card.dart index 6d29d97d..fd106e72 100644 --- a/chameleonultragui/lib/gui/page/write_card.dart +++ b/chameleonultragui/lib/gui/page/write_card.dart @@ -280,6 +280,12 @@ class WriteCardPageState extends State { @override Widget build(BuildContext context) { var localizations = AppLocalizations.of(context)!; + var typeLocalization = { + 'gen1': localizations.gen1, + 'gen2': localizations.gen2, + 'gen3': localizations.gen3, + 't55xx': localizations.t55xx, + }; return Scaffold( appBar: AppBar( @@ -345,7 +351,8 @@ class WriteCardPageState extends State { (AbstractWriteHelper helperClass) { return DropdownMenuItem( value: helperClass, - child: Text(helperClass.name), + child: + Text(typeLocalization[helperClass.name]!), ); }).toList(), onChanged: (AbstractWriteHelper? helperClass) { diff --git a/chameleonultragui/lib/helpers/general.dart b/chameleonultragui/lib/helpers/general.dart index 7912591f..a7360300 100644 --- a/chameleonultragui/lib/helpers/general.dart +++ b/chameleonultragui/lib/helpers/general.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:chameleonultragui/bridge/chameleon.dart'; import 'package:chameleonultragui/connector/serial_abstract.dart'; +import 'package:chameleonultragui/helpers/mifare_classic/general.dart'; import 'package:chameleonultragui/main.dart'; import 'package:chameleonultragui/sharedprefsprovider.dart'; import 'package:file_picker/file_picker.dart'; @@ -140,6 +141,16 @@ String chameleonTagToString(TagType tag) { } } +String chameleonCardToString(CardSave card) { + String name = chameleonTagToString(card.tag); + + if (chameleonTagSaveCheckForMifareClassicEV1(card)) { + name += " EV1"; + } + + return name; +} + TagType numberToChameleonTag(int type) { if (type == TagType.mifareMini.value) { return TagType.mifareMini; diff --git a/chameleonultragui/lib/helpers/mifare_classic/write/base.dart b/chameleonultragui/lib/helpers/mifare_classic/write/base.dart index 7547145f..32cd4cd4 100644 --- a/chameleonultragui/lib/helpers/mifare_classic/write/base.dart +++ b/chameleonultragui/lib/helpers/mifare_classic/write/base.dart @@ -18,7 +18,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:provider/provider.dart'; -class BaseMifareClassicMagicCardHelper extends AbstractWriteHelper { +class BaseMifareClassicWriteHelper extends AbstractWriteHelper { late MifareClassicRecovery recovery; late MifareClassicType type; late bool isEV1; @@ -29,7 +29,7 @@ class BaseMifareClassicMagicCardHelper extends AbstractWriteHelper { @override bool get autoDetect => true; - BaseMifareClassicMagicCardHelper(super.communicator, + BaseMifareClassicWriteHelper(super.communicator, {required this.recovery, this.type = MifareClassicType.m1k, this.isEV1 = false}); @@ -92,9 +92,16 @@ class BaseMifareClassicMagicCardHelper extends AbstractWriteHelper { } @override - Future writeData(CardSave card, dynamic update) async { + Future writeData( + CardSave card, Function(int writeProgress) update) async { List data = card.data; + try { + await communicator.scan14443aTag(); + } catch (e) { + return false; + } + if (data.isEmpty || data[0].isEmpty) { if (data.isEmpty) { data = [Uint8List(0)]; @@ -215,6 +222,12 @@ class BaseMifareClassicMagicCardHelper extends AbstractWriteHelper { setState(() { mfcInfo!.recovery = getExtraData()[0]; }); + } else { + setState(() { + hfInfo!.cardExist = false; + }); + + return; } setState(() { diff --git a/chameleonultragui/lib/helpers/mifare_classic/write/gen1.dart b/chameleonultragui/lib/helpers/mifare_classic/write/gen1.dart index 941a1579..08b890d5 100644 --- a/chameleonultragui/lib/helpers/mifare_classic/write/gen1.dart +++ b/chameleonultragui/lib/helpers/mifare_classic/write/gen1.dart @@ -2,13 +2,13 @@ import 'dart:typed_data'; import 'package:chameleonultragui/helpers/mifare_classic/write/base.dart'; -class MifareClassicGen1WriteHelper extends BaseMifareClassicMagicCardHelper { +class MifareClassicGen1WriteHelper extends BaseMifareClassicWriteHelper { MifareClassicGen1WriteHelper(super.communicator, {required super.recovery}); @override - String get name => "Gen1"; + String get name => "gen1"; - static String get staticName => "Gen1"; + static String get staticName => "gen1"; @override Future isMagic(dynamic data) async { diff --git a/chameleonultragui/lib/helpers/mifare_classic/write/gen2.dart b/chameleonultragui/lib/helpers/mifare_classic/write/gen2.dart index 5361b259..2a2a75fb 100644 --- a/chameleonultragui/lib/helpers/mifare_classic/write/gen2.dart +++ b/chameleonultragui/lib/helpers/mifare_classic/write/gen2.dart @@ -6,14 +6,14 @@ import 'package:chameleonultragui/helpers/mifare_classic/recovery.dart'; import 'package:chameleonultragui/helpers/mifare_classic/write/base.dart'; import 'package:chameleonultragui/sharedprefsprovider.dart'; -class MifareClassicGen2WriteHelper extends BaseMifareClassicMagicCardHelper { +class MifareClassicGen2WriteHelper extends BaseMifareClassicWriteHelper { List failedBlocks = []; MifareClassicGen2WriteHelper(super.communicator, {required super.recovery}); @override - String get name => "Gen2 / Generic"; + String get name => "gen2"; - static String get staticName => "Gen2 / Generic"; + static String get staticName => "gen2"; @override Future isMagic(dynamic data) async { @@ -99,11 +99,18 @@ class MifareClassicGen2WriteHelper extends BaseMifareClassicMagicCardHelper { } @override - Future writeData(CardSave card, dynamic update) async { + Future writeData( + CardSave card, Function(int writeProgress) update) async { List data = card.data; List cleanSectors = List.generate(40, (index) => false); failedBlocks = []; + try { + await communicator.scan14443aTag(); + } catch (e) { + return false; + } + if (data.isEmpty || data[0].isEmpty) { if (data.isEmpty) { data = [Uint8List(0)]; diff --git a/chameleonultragui/lib/helpers/mifare_classic/write/gen3.dart b/chameleonultragui/lib/helpers/mifare_classic/write/gen3.dart index 2931ba15..e1d585bc 100644 --- a/chameleonultragui/lib/helpers/mifare_classic/write/gen3.dart +++ b/chameleonultragui/lib/helpers/mifare_classic/write/gen3.dart @@ -11,9 +11,9 @@ class MifareClassicGen3WriteHelper extends MifareClassicGen2WriteHelper { MifareClassicGen3WriteHelper(super.communicator, {required super.recovery}); @override - String get name => "Gen3"; + String get name => "gen3"; - static String get staticName => "Gen3"; + static String get staticName => "gen3"; @override Future isMagic(dynamic data) async { diff --git a/chameleonultragui/lib/helpers/mifare_ultralight/write/base.dart b/chameleonultragui/lib/helpers/mifare_ultralight/write/base.dart new file mode 100644 index 00000000..b30fb1d1 --- /dev/null +++ b/chameleonultragui/lib/helpers/mifare_ultralight/write/base.dart @@ -0,0 +1,169 @@ +import 'dart:typed_data'; + +import 'package:chameleonultragui/gui/page/read_card.dart'; +import 'package:chameleonultragui/helpers/general.dart'; +import 'package:chameleonultragui/helpers/write.dart'; +import 'package:chameleonultragui/sharedprefsprovider.dart'; +import 'package:flutter/material.dart'; + +// Localizations +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class BaseMifareUltralightWriteHelper extends AbstractWriteHelper { + HFCardInfo? hfInfo; + List failedBlocks = []; + + @override + bool get autoDetect => false; + + @override + String get name => "gen2"; + + static String get staticName => "gen2"; + TextEditingController keyController = TextEditingController(); + String? key; + + BaseMifareUltralightWriteHelper(super.communicator); + + @override + List getAvailableMethods() { + return [ + BaseMifareUltralightWriteHelper(communicator), + ]; + } + + @override + List getAvailableMethodsByPriority() { + return [BaseMifareUltralightWriteHelper(communicator)]; + } + + @override + Widget getWriteWidget(BuildContext context, setState) { + var localizations = AppLocalizations.of(context)!; + final GlobalKey formKey = GlobalKey(); + + return Row(children: [ + Form( + key: formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Expanded( + child: Column( + children: [ + TextFormField( + controller: keyController, + decoration: InputDecoration( + labelText: localizations.key, + hintMaxLines: 4, + hintText: localizations + .enter_something(localizations.ultralight_key_prompt)), + validator: (String? value) { + if (value!.isNotEmpty && !isValidHexString(value)) { + return localizations.must_be_valid_hex; + } + + if (value.length != 8) { + return localizations.must_be(4, localizations.key); + } + + return null; + }, + ) + ], + ))), + TextButton( + onPressed: () => { + setState(() { + key = keyController.text; + }) + }, + child: Text(localizations.next), + ), + TextButton( + onPressed: () => { + setState(() { + key = ""; + }) + }, + child: Text(localizations.no_key), + ) + ]); + } + + @override + Future isCompatible(CardSave card) async { + return true; + } + + @override + Future isMagic(data) async { + return false; + } + + @override + bool isReady() { + return key != null; + } + + @override + bool writeWidgetSupported() { + return true; + } + + @override + Future reset() async { + failedBlocks = []; + key = null; + } + + @override + Future writeData( + CardSave card, Function(int writeProgress) update) async { + int totalBlocks = card.data.length; + + try { + await communicator.scan14443aTag(); + } catch (e) { + return false; + } + + if (key!.isNotEmpty) { + Uint8List pack = await communicator.send14ARaw( + Uint8List.fromList([0x1B, ...hexToBytes(key!)]), + keepRfField: true); + if (pack.length < 2) { + return false; + } + } + + for (var block = 0; block < totalBlocks; block++) { + Uint8List write = await communicator.send14ARaw( + Uint8List.fromList([0xA2, block, ...card.data[block]]), + keepRfField: true, + checkResponseCrc: false, + autoSelect: block == 0 || block == 3); + if (write.isEmpty || write[0] != 0x0A || block == 2) { + await communicator.send14ARaw(Uint8List(1)); // reset + + if (key!.isNotEmpty) { + await communicator.send14ARaw( + Uint8List.fromList([0x1B, ...hexToBytes(key!)]), + keepRfField: true); + } + + if (block > 2) { + // block is not UID + failedBlocks.add(block); + } + } + + update((block / totalBlocks * 100).round()); + } + + return failedBlocks.isEmpty; + } + + @override + List getFailedBlocks() { + return failedBlocks; + } +} diff --git a/chameleonultragui/lib/helpers/t55xx/write/base.dart b/chameleonultragui/lib/helpers/t55xx/write/base.dart index 98be8207..e8b937a8 100644 --- a/chameleonultragui/lib/helpers/t55xx/write/base.dart +++ b/chameleonultragui/lib/helpers/t55xx/write/base.dart @@ -14,9 +14,9 @@ class BaseT55XXCardHelper extends AbstractWriteHelper { bool get autoDetect => true; @override - String get name => "T55XX"; + String get name => "t55xx"; - static String get staticName => "T55XX"; + static String get staticName => "t55xx"; TextEditingController newKeyController = TextEditingController(); TextEditingController currentKeyController = TextEditingController(); String currentKey = ""; @@ -157,7 +157,8 @@ class BaseT55XXCardHelper extends AbstractWriteHelper { } @override - Future writeData(CardSave card, update) async { + Future writeData( + CardSave card, Function(int writeProgress) update) async { await communicator.writeEM410XtoT55XX( hexToBytes(card.uid), hexToBytes(newKey), [hexToBytes(currentKey)]); var newCard = await communicator.readEM410X(); diff --git a/chameleonultragui/lib/helpers/write.dart b/chameleonultragui/lib/helpers/write.dart index 791075a8..b07f0e9a 100644 --- a/chameleonultragui/lib/helpers/write.dart +++ b/chameleonultragui/lib/helpers/write.dart @@ -2,6 +2,8 @@ import 'package:chameleonultragui/bridge/chameleon.dart'; import 'package:chameleonultragui/helpers/mifare_classic/general.dart'; import 'package:chameleonultragui/helpers/mifare_classic/recovery.dart'; import 'package:chameleonultragui/helpers/mifare_classic/write/base.dart'; +import 'package:chameleonultragui/helpers/mifare_ultralight/general.dart'; +import 'package:chameleonultragui/helpers/mifare_ultralight/write/base.dart'; import 'package:chameleonultragui/helpers/t55xx/write/base.dart'; import 'package:chameleonultragui/main.dart'; import 'package:chameleonultragui/sharedprefsprovider.dart'; @@ -41,11 +43,15 @@ abstract class AbstractWriteHelper { static AbstractWriteHelper? getClassByCardType( TagType type, ChameleonGUIState appState, void Function() update) { - if (chameleonTagTypeGetMfClassicType(type) != MifareClassicType.none) { - return BaseMifareClassicMagicCardHelper(appState.communicator!, + if (isMifareClassic(type)) { + return BaseMifareClassicWriteHelper(appState.communicator!, recovery: MifareClassicRecovery(appState: appState, update: update)); } + if (isMifareUltralight(type)) { + return BaseMifareUltralightWriteHelper(appState.communicator!); + } + if (type == TagType.em410X) { return BaseT55XXCardHelper(appState.communicator!); } @@ -53,7 +59,7 @@ abstract class AbstractWriteHelper { return null; // writing is not supported } - Future writeData(CardSave card, dynamic update); + Future writeData(CardSave card, Function(int writeProgress) update); Widget getWriteWidget(BuildContext context, dynamic setState); diff --git a/chameleonultragui/lib/l10n/app_en.arb b/chameleonultragui/lib/l10n/app_en.arb index b4279893..5691c2ba 100644 --- a/chameleonultragui/lib/l10n/app_en.arb +++ b/chameleonultragui/lib/l10n/app_en.arb @@ -287,5 +287,10 @@ "read_without_key": "Read without key", "invalid_password": "Invalid password", "ultralight_version": "Ultralight version", - "ultralight_signature": "Ultralight signature" + "ultralight_signature": "Ultralight signature", + "no_key": "No key", + "gen1": "Gen1", + "gen2": "Gen2 / Generic", + "gen3": "Gen3", + "t55xx": "T55XX" } \ No newline at end of file