Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Mifare Classic Tool/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'com.android.application' version '8.7.1'
id 'com.android.application' version '8.7.3'
id 'org.jetbrains.kotlin.android' version '1.9.0'
}

Expand All @@ -26,13 +26,26 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}

tasks.withType(JavaCompile).configureEach {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])

// ▼ NEW: 레이아웃 제어를 위한 ConstraintLayout
implementation "androidx.constraintlayout:constraintlayout:2.1.4"

// 기존 의존성들 …
implementation "androidx.core:core:1.13.1"
implementation "androidx.preference:preference:1.2.1"
implementation "androidx.appcompat:appcompat:1.6.1"
Expand Down
4 changes: 4 additions & 0 deletions Mifare Classic Tool/app/src/main/assets/help/help.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ <h2 id="general_information">1. General Information</h2>
basic familiarity with the MIFARE Classic technology.
You also need an understanding of the hexadecimal number system,
because all data input and output is in hexadecimal.
The user interface is available in multiple languages, including Korean.
<br /><br />
Some important things are:
<ul>
Expand Down Expand Up @@ -158,6 +159,7 @@ <h3 id="features">1.1 Features</h3>
<li>Quick UID clone feature</li>
<li>Import/export/convert files</li>
<li>In-App (offline) help and information</li>
<li>Korean language support</li>
<li>It's open source ;)</li>
</ul>

Expand Down Expand Up @@ -309,6 +311,8 @@ <h3 id="write_dump">4.2 Write Dump (Clone)</h3>
After selecting the dump, the sectors, and the key files, the App will check
everything for you! If there are issues like 'block is read-only', 'key
with write access not known', etc., you will get a report before writing.
ModuKey cloning is done in two steps. First, use <b>Write Dump (Clone)</b> with the <i>Write to Manufacturer Block</i> option enabled to load the ModuKey template. After writing completes, press the hardware button on the ModuKey to finalize.

<br><br>
<b>Options:</b>
<ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Service 코드. READ → 덤프 생성, WRITE → 제조사 블록 쓰기까지 자동 실행.
// 콜백으로 ViewModel에 단계별 이벤트 전달.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package de.svws_nfc.simpleclone

import android.app.Application
import android.nfc.Tag
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData

class CloneViewModel(app: Application) : AndroidViewModel(app) {

enum class Phase { WAIT_READ, READ_RUNNING, WAIT_WRITE, WRITE_RUNNING, DONE, ERROR }

data class UiState(
val phase: Phase = Phase.WAIT_READ,
val message: String = "",
val progress: Int = 0
)

val uiState = MutableLiveData(UiState())

fun onTagScanned(tag: Tag) {
when (uiState.value?.phase) {
Phase.WAIT_READ -> {
service?.startRead(tag)
}
Phase.WAIT_WRITE -> {
service?.startWrite(tag)
}
else -> {} // DONE, ERROR 일 땐 무시
}
}

/** Service 콜백이 호출할 메서드 */
fun update(phase: CloneService.Phase, msg: String) {
when (phase) {
CloneService.Phase.READ ->
uiState.postValue(uiState.value?.copy(phase = Phase.READ_RUNNING, message = msg))
CloneService.Phase.WRITE ->
uiState.postValue(uiState.value?.copy(phase = Phase.WRITE_RUNNING, message = msg))
}
}

// ...추가 로직
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package de.svws_nfc.simpleclone;

import android.content.Intent;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Bundle;
import androidx.lifecycle.ViewModelProvider;

import de.syss.MifareClassicTool.Activities.BasicActivity;

/**
* 단순 2-단계 카드 복제를 위한 전용 화면.
* READ 단계 → WRITE 단계로만 흐르며, 나머지 세부 옵션은 자동 처리된다.
*/
public class SimpleCloneActivity extends BasicActivity {
private CloneViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_clone); // layout 은 다음 단계에서 생성
viewModel = new ViewModelProvider(this).get(CloneViewModel.class);

viewModel.getUiState().observe(this, state -> {
// TODO: 단계별 메시지/버튼 상태 업데이트
});
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
if (tag != null) viewModel.onTagScanned(tag);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import android.os.Handler;
import android.os.Looper;
import android.util.SparseArray;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
Expand All @@ -47,6 +48,7 @@ public class ReadTag extends AppCompatActivity {

private final Handler mHandler = new Handler(Looper.getMainLooper());
private SparseArray<String[]> mRawDump;
private TextView mReadStatus;

/**
* Show the {@link KeyMapCreator}.
Expand All @@ -55,6 +57,10 @@ public class ReadTag extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_read_tag);
mReadStatus = findViewById(R.id.textViewReadTag);
mReadStatus.setText(R.string.text_hold_existing_card);
Toast.makeText(this, R.string.text_hold_existing_card,
Toast.LENGTH_SHORT).show();

Intent intent = new Intent(this, KeyMapCreator.class);
intent.putExtra(KeyMapCreator.EXTRA_KEYS_DIR,
Expand Down Expand Up @@ -143,6 +149,11 @@ private void createTagDump(SparseArray<String[]> rawDump) {
Intent intent = new Intent(this, DumpEditor.class);
intent.putExtra(DumpEditor.EXTRA_DUMP, dump);
startActivity(intent);
if (mReadStatus != null) {
mReadStatus.setText(R.string.text_copy_step1);
}
Toast.makeText(this, R.string.text_copy_step1,
Toast.LENGTH_SHORT).show();
} else {
// Error, keys from key map are not valid for reading.
Toast.makeText(this, R.string.info_none_key_valid_for_reading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_write_tag);

Toast.makeText(this, R.string.text_modukey_present,
Toast.LENGTH_SHORT).show();

mSectorTextBlock = findViewById(R.id.editTextWriteTagSector);
mBlockTextBlock = findViewById(R.id.editTextWriteTagBlock);
mDataText = findViewById(R.id.editTextWriteTagData);
Expand Down Expand Up @@ -1215,6 +1218,9 @@ private void writeDump(
return;
}

Toast.makeText(this, R.string.text_copy_step2,
Toast.LENGTH_SHORT).show();

// Create reader.
final MCReader reader = Common.checkForTagAndCreateReader(this);
if (reader == null) {
Expand All @@ -1231,7 +1237,7 @@ private void writeDump(
pad = Common.dpToPx(20);
progressBar.setPadding(0, 0, pad, 0);
TextView tv = new TextView(this);
tv.setText(getString(R.string.dialog_wait_write_tag));
tv.setText(getString(R.string.text_copy_step2));
tv.setTextSize(18);
ll.addView(progressBar);
ll.addView(tv);
Expand Down
42 changes: 42 additions & 0 deletions Mifare Classic Tool/app/src/main/res/layout/activity_modukey.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<TextView
android:id="@+id/textHoldExistingCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/text_hold_existing_card" />

<TextView
android:id="@+id/textModukeyPresent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/text_modukey_present" />

<TextView
android:id="@+id/textCopyStep"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/text_copy_step1" />

<Button
android:id="@+id/buttonStartCopy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_start_copy" />

<Button
android:id="@+id/buttonStartCopyStep2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/action_start_copy_step2" />

</LinearLayout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/tvMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="카드키를 스마트폰 뒷면에 인식하세요"
android:textSize="18sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:padding="16dp"/>

<Button
android:id="@+id/btnAction"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="복사시작"
app:layout_constraintTop_toBottomOf="@id/tvMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
11 changes: 11 additions & 0 deletions Mifare Classic Tool/app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MIFARE 클래식 툴</string>
<string name="app_version">앱 버전</string>
<string name="text_hold_existing_card">기존 카드키를 스마트폰 뒷면에 인식한 채로 유지하세요</string>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Go

<string name="text_modukey_present">모두키를 스마트폰 뒷면에 인식시켜주세요</string>
<string name="text_copy_step1">복사1단계 진행중</string>
<string name="text_copy_step2">복사2단계 진행중</string>
<string name="action_start_copy">복사시작</string>
<string name="action_start_copy_step2">복사2단계 시작</string>
</resources>
8 changes: 8 additions & 0 deletions Mifare Classic Tool/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,12 @@
NXP\'s specifications.
\n\nProceed at your own risk!</string>

<string name="text_hold_existing_card">Hold the existing key card on the back of your phone</string>
<string name="text_modukey_present">Present the ModuKey on the back of your phone</string>
<string name="text_copy_step1">Copy step 1 in progress</string>
<string name="text_copy_step2">Copy step 2 in progress</string>
<string name="action_start_copy">Start Copy</string>
<string name="action_start_copy_step2">Start Copy Step 2</string>
<!-- Hints -->
<string name="hint_hex_16_byte">HEX, 16 bytes (e.g. 0A4F&#8230;)</string>
<string name="hint_hex_3_byte">HEX, 3 bytes</string>
Expand All @@ -673,6 +679,8 @@
<string name="hint_custom_retry_authentication_count">Number of retries</string>
<string name="hint_key">HEX, 6 bytes per line</string>

<!-- Copy/Clone wizard messages -->

<!-- Supported locales. No need for translation! -->
<string-array name="supported_locales" translatable="false">
<item>English (en)</item>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// JUnit 테스트: Phase 전이, 에러 처리 확인
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ An Android NFC app for reading, writing, analyzing, etc. MIFARE Classic RFID tag
Read this information in other languages:
* [English](README.md)
* [简体中文](README.zh-CN.md)
* Korean language support in the app UI

Helpful links:
* [MIFARE Classic Tool (Donate Version) on Google Play](https://play.google.com/store/apps/details?id=de.syss.MifareClassicToolDonate)
Expand All @@ -36,6 +37,7 @@ Features
See chapter [Getting Started](#getting-started).
* Format a tag back to the factory/delivery state
* Write the manufacturer block (block 0) of special MIFARE Classic tags
* Support two-step ModuKey cloning workflow
* Use external NFC readers like ACR 122U
(See the [Help & Info section](https://publications.icaria.de/mct/help-and-info/#external_nfc)
for more information.)
Expand All @@ -52,6 +54,7 @@ Features
* Quick UID clone feature
* Import/export/convert files
* In-App (offline) help and information
* Korean language support
* It's free software (open source) ;)


Expand Down Expand Up @@ -96,6 +99,10 @@ Some important things are:
Also, make sure the BCC value (check out the "BCC Calculator Tool"),
the SAK and the ATQA values are correct. If you just want to clone a UID,
please use the "Clone UID Tool".
ModuKey cards are cloned in two steps:
1. Use **Write Dump (Clone)** with "Write to Manufacturer Block" enabled to load the ModuKey template.
2. After writing completes, press the ModuKey button to finalize the data.

* This app **will not work** on some devices because their hardware
(NFC-controller) does not support MIFARE Classic
([read more](https://github.com/ikarus23/MifareClassicTool/issues/1)).
Expand All @@ -109,6 +116,15 @@ or read the
[MIFARE Classic (1k) 'Datasheet'](https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf)
(PDF) from NXP.

Building from Source
--------------------

The actual Android Studio project is located inside the `Mifare Classic Tool`
directory. To build the app yourself, open Android Studio and choose **Open**
to select this folder (not the repository root). Once Android Studio has
created a `local.properties` file, press *Sync Project with Gradle Files* to
download dependencies and compile the application.



Getting Started
Expand Down