Skip to content

Commit

Permalink
Add support for writing to PowerTags. #30
Browse files Browse the repository at this point in the history
  • Loading branch information
HiddenRambler committed Sep 11, 2017
1 parent 648c267 commit 3ccfe0a
Show file tree
Hide file tree
Showing 9 changed files with 65,766 additions and 41 deletions.
65,546 changes: 65,546 additions & 0 deletions app/src/main/assets/keytable.json

Large diffs are not rendered by default.

37 changes: 28 additions & 9 deletions app/src/main/java/com/hiddenramblings/tagmo/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.bumptech.glide.request.transition.Transition;
import com.hiddenramblings.tagmo.amiibo.Amiibo;
import com.hiddenramblings.tagmo.amiibo.AmiiboManager;
import com.hiddenramblings.tagmo.ptag.PTagKeyManager;

import org.androidannotations.annotations.AfterViews;
import org.androidannotations.annotations.Background;
Expand Down Expand Up @@ -156,6 +157,7 @@ protected void onResume() {
startNfcMonitor();
keyManager = new KeyManager(this);
this.loadAmiiboManager();
this.loadPTagKeyManager();
}

@Override
Expand All @@ -173,6 +175,21 @@ public boolean onCreateOptionsMenu(Menu menu) {
return result;
}

@Background
void loadPTagKeyManager() {
if (!prefs.enablePowerTagSupport().get()) {
Log.d(TAG, "PowerTag support not enabled.");
return;
}
try {
Log.d(TAG, "Loading PowerTag keyset.");
PTagKeyManager.load(this);
} catch (Exception e) {
e.printStackTrace();
showToast("Failed to load PowerTag keys");
}
}

@Background
void loadAmiiboManager() {
AmiiboManager amiiboManager = null;
Expand Down Expand Up @@ -372,16 +389,18 @@ public void onDismissed(Snackbar snackbar, int event) {

int ssbVisibility = View.GONE;
int tpVisibility = View.GONE;
try {
long amiiboId = TagUtil.amiiboIdFromTag(currentTagData);
if (EditorTP.canEditAmiibo(amiiboId)) {
tpVisibility = View.VISIBLE;
}
if (EditorSSB.canEditAmiibo(amiiboId)) {
ssbVisibility = View.VISIBLE;
if (currentTagData != null) {
try {
long amiiboId = TagUtil.amiiboIdFromTag(currentTagData);
if (EditorTP.canEditAmiibo(amiiboId)) {
tpVisibility = View.VISIBLE;
}
if (EditorSSB.canEditAmiibo(amiiboId)) {
ssbVisibility = View.VISIBLE;
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}

updateAmiiboView();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ void onTagDiscovered(Intent intent) {
data = commandIntent.getByteArrayExtra(EXTRA_TAG_DATA);
if (data == null)
throw new Exception("No data to write");
TagWriter.writeToTagAuto(mifare, data, this.keyManager, prefs.enableTagTypeValidation().get());
TagWriter.writeToTagAuto(mifare, data, this.keyManager, prefs.enableTagTypeValidation().get(), prefs.enablePowerTagSupport().get());
resultCode = Activity.RESULT_OK;
showToast("Done");
break;
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/hiddenramblings/tagmo/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public interface Preferences {
@DefaultBoolean(keyRes=R.string.settings_enable_tag_type_validation, value=true)
boolean enableTagTypeValidation();

@DefaultBoolean(keyRes=R.string.settings_enable_power_tag_support, value=false)
boolean enablePowerTagSupport();

@DefaultInt(BrowserActivity.VIEW_TYPE_COMPACT)
int browserAmiiboView();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
CheckBoxPreference enableAmiiboBrowser;
@PreferenceByKey(R.string.settings_enable_tag_type_validation)
CheckBoxPreference enableTagTypeValidation;
@PreferenceByKey(R.string.settings_enable_power_tag_support)
CheckBoxPreference enablePowerTagSupport;
@PreferenceByKey(R.string.settings_info_amiibos)
Preference amiiboStats;
@PreferenceByKey(R.string.settings_info_game_series)
Expand Down Expand Up @@ -123,6 +125,11 @@ void onEnableTagTypeValidationClicked() {
prefs.enableTagTypeValidation().put(enableTagTypeValidation.isChecked());
}

@PreferenceClick(R.string.settings_enable_power_tag_support)
void onEnablePowerTagSupportClicked() {
prefs.enablePowerTagSupport().put(enablePowerTagSupport.isChecked());
}

@PreferenceClick(R.string.settings_import_info_amiiboapi)
void onSyncAmiiboAPIClicked() {
downloadAmiiboAPIData();
Expand Down
111 changes: 81 additions & 30 deletions app/src/main/java/com/hiddenramblings/tagmo/TagWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import android.nfc.tech.MifareUltralight;
import android.util.Log;

import com.hiddenramblings.tagmo.ptag.PTagKeyManager;

import java.io.IOException;

/**
Expand All @@ -11,6 +13,11 @@
public class TagWriter {
private static final String TAG = "TagWriter";

private static final byte[] POWERTAG_SIGNATURE = Util.hexStringToByteArray("213C65444901602985E9F6B50CACB9C8CA3C4BCD13142711FF571CF01E66BD6F");
private static final byte[] POWERTAG_IDPAGES = Util.hexStringToByteArray("04070883091012131800000000000000");
private static final String POWERTAG_KEY = "FFFFFFFFFFFFFFFF0000000000000000";
private static final byte[] COMP_WRITE_CMD = Util.hexStringToByteArray("a000");

public static void writeToTagRaw(MifareUltralight mifare, byte[] tagData, boolean validateNtag) throws Exception
{
validate(mifare, tagData, validateNtag);
Expand Down Expand Up @@ -48,33 +55,85 @@ private static void validateBlankTag(MifareUltralight mifare) throws Exception {
Log.d(TAG, "not locked");
}

public static void writeToTagAuto(MifareUltralight mifare, byte[] tagData, KeyManager keyManager, boolean validateNtag) throws Exception
{
tagData = adjustTag(keyManager, tagData, mifare);
public static void writeToTagAuto(MifareUltralight mifare, byte[] tagData, KeyManager keyManager, boolean validateNtag, boolean supportPowerTag) throws Exception {
byte[] idPages = mifare.readPages(0);
if (idPages == null || idPages.length != TagUtil.PAGE_SIZE * 4)
throw new Exception("Read failed! Unexpected read size.");

Log.d(TAG, Util.bytesToHex(tagData));
validate(mifare, tagData, validateNtag);
boolean isPowerTag = false;
if (supportPowerTag) {
byte[] sig = mifare.transceive(Util.hexStringToByteArray("3c00"));
isPowerTag = compareRange(sig, POWERTAG_SIGNATURE, 0, POWERTAG_SIGNATURE.length);
}

validateBlankTag(mifare);
Log.d(TAG, "is power tag : " + isPowerTag);

try {
byte[][] pages = TagUtil.splitPages(tagData);
writePages(mifare, 3, 129, pages);
Log.d(TAG, "Wrote main data");
} catch (Exception e) {
throw new Exception("Error while writing main data (stage 1)", e);
if (isPowerTag) {
//use a pre-determined static id for powertag
tagData = TagUtil.patchUid(POWERTAG_IDPAGES, tagData, keyManager, true);
} else {
tagData = TagUtil.patchUid(idPages, tagData, keyManager, true);
}
try {
writePassword(mifare);
Log.d(TAG, "Wrote password");
} catch (Exception e) {
throw new Exception("Error while setting password (stage 2)", e);

Log.d(TAG, Util.bytesToHex(tagData));

if (!isPowerTag) {
validate(mifare, tagData, validateNtag);
validateBlankTag(mifare);
}
try {
writeLockInfo(mifare);
Log.d(TAG, "Wrote lock info");
} catch (Exception e) {
throw new Exception("Error while setting lock info (stage 3)", e);

if (isPowerTag) {
byte[] oldid = mifare.getTag().getId();
if (oldid == null || oldid.length != 7)
throw new Exception("Could not read old UID");

Log.d(TAG, "Old UID " + Util.bytesToHex(oldid));

byte[] page10 = mifare.readPages(0x10);
Log.d(TAG, "page 10 " + Util.bytesToHex(page10));

String page10bytes = Util.bytesToHex(new byte[]{page10[0], page10[3]});

byte[] ptagKeySuffix = PTagKeyManager.getKey(oldid, page10bytes);
byte[] ptagKey = Util.hexStringToByteArray(POWERTAG_KEY);
System.arraycopy(ptagKeySuffix, 0, ptagKey, 8, 8);

Log.d(TAG, "ptag key " + Util.bytesToHex(ptagKey));

mifare.transceive(COMP_WRITE_CMD);
mifare.transceive(ptagKey);

if (!(idPages[0] == (byte) 0xFF && idPages[1] == (byte) 0xFF))
doAuth(mifare);
}

byte[][] pages = TagUtil.splitPages(tagData);
if (isPowerTag) {
byte[] zeropage = Util.hexStringToByteArray("00000000");
mifare.writePage(0x86, zeropage); //PACK
writePages(mifare, 0x01, 0x84, pages);
mifare.writePage(0x85, zeropage); //PWD
mifare.writePage(0x00, pages[0]); //UID
mifare.writePage(0x00, pages[0]); //UID
} else {
try {
writePages(mifare, 3, 129, pages);
Log.d(TAG, "Wrote main data");
} catch (Exception e) {
throw new Exception("Error while writing main data (stage 1)", e);
}
try {
writePassword(mifare);
Log.d(TAG, "Wrote password");
} catch (Exception e) {
throw new Exception("Error while setting password (stage 2)", e);
}
try {
writeLockInfo(mifare);
Log.d(TAG, "Wrote lock info");
} catch (Exception e) {
throw new Exception("Error while setting lock info (stage 3)", e);
}
}
}

Expand All @@ -101,14 +160,6 @@ public static void restoreTag(MifareUltralight mifare, byte[] tagData, boolean i
writePages(mifare, 32, 129, pages);
}

static byte[] adjustTag(KeyManager keyManager, byte[] tagData, MifareUltralight mifare) throws Exception {
byte[] pages = mifare.readPages(0);
if (pages == null || pages.length != TagUtil.PAGE_SIZE * 4)
throw new Exception("Read failed! Unexpected read size.");

return TagUtil.patchUid(pages, tagData, keyManager, true);
}

static void validate(MifareUltralight mifare, byte[] tagData, boolean validateNtag) throws Exception {
if (tagData == null)
throw new Exception("Cannot validate: no source data loaded to compare.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.hiddenramblings.tagmo.ptag;

import android.content.Context;
import android.content.res.AssetManager;
import android.util.Base64;
import android.util.Log;

import com.hiddenramblings.tagmo.Util;
import com.hiddenramblings.tagmo.amiibo.AmiiboManager;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;

public class PTagKeyManager {
private static final String TAG = "PTagKeyManager";
public static final String KEY_LIST_FILE = "keytable.json";

private static HashMap<String, HashMap<String, byte[]>> keys;

public static void load(Context context) throws Exception {
if (keys != null)
return;
AssetManager assetManager = context.getAssets();
InputStream stream = assetManager.open(KEY_LIST_FILE);
try {
byte[] data = new byte[stream.available()];
stream.read(data);

JSONObject obj = new JSONObject(new String(data));
loadJson(obj);
} finally {
stream.close();
}
}

static void loadJson(JSONObject json) throws JSONException {
HashMap<String, HashMap<String, byte[]>> keytable = new HashMap<>();
for (Iterator uidIterator = json.keys(); uidIterator.hasNext(); ) {
String uid = (String) uidIterator.next();
JSONObject pageKeys = json.getJSONObject(uid);

HashMap<String, byte[]> keyvalues = new HashMap<String, byte[]>();
keytable.put(uid, keyvalues);

for (Iterator pageByteIterator = pageKeys.keys(); pageByteIterator.hasNext(); ) {
String pageBytes = (String) pageByteIterator.next();

String keyStr = pageKeys.getString(pageBytes);

byte[] key = Base64.decode(keyStr, Base64.DEFAULT);

keyvalues.put(pageBytes, key);
}
}

keys = keytable;
}

public static byte[] getKey(byte[] uid, String page10bytes) throws Exception {
if (keys == null)
throw new Exception("PowerTag keys not loaded");

byte[] uidc = new byte[7];

uidc[0] = (byte)(uid[0] & 0xFE);
uidc[1] = (byte)(uid[1] & 0xFE);
uidc[2] = (byte)(uid[2] & 0xFE);
uidc[3] = (byte)(uid[3] & 0xFE);
uidc[4] = (byte)(uid[4] & 0xFE);
uidc[5] = (byte)(uid[5] & 0xFE);
uidc[6] = (byte)(uid[6] & 0xFE);

String uidStr = Util.bytesToHex(uidc);

HashMap<String, byte[]> keymap = keys.get(uidStr);
if (keymap == null)
throw new Exception("No available key for UID");

byte[] key = keymap.get(page10bytes);
if (key == null)
throw new Exception("No available key for P10_ID");

return key;
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<string name="settings_reset_info">reset_info</string>
<string name="settings_enable_amiibo_browser">enable_amiibo_browser</string>
<string name="settings_enable_tag_type_validation">enable_tag_type_validation</string>
<string name="settings_enable_power_tag_support">enable_power_tag_support</string>
<string name="settings_export_info">settings_export_info</string>
<string name="settings_info_amiibos">settings_info_amiibos</string>
<string name="settings_info_game_series">settings_info_game_series</string>
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/res/xml/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@
android:summary="Not all NFC chipsets correctly identify NFC tags. Disabling tag verification will write to tags that may not be Amiibo compatible or may fail in unexpected ways."
android:title="Enable NTAG215 validation"/>
</android.support.v7.preference.PreferenceCategory>

<android.support.v7.preference.PreferenceCategory android:title="PowerTag">
<android.support.v7.preference.CheckBoxPreference
android:defaultValue="false"
android:key="@string/settings_enable_power_tag_support"
android:summary="Support is currently under development. See the Wiki on github for limitations and use."
android:title="Enable PowerTag support"/>
</android.support.v7.preference.PreferenceCategory>
<android.support.v7.preference.PreferenceCategory
android:title="Amiibo Info">
<android.support.v7.preference.Preference
Expand Down

0 comments on commit 3ccfe0a

Please sign in to comment.