diff --git a/app/src/main/java/com/hiddenramblings/tagmo/EditorSSB.java b/app/src/main/java/com/hiddenramblings/tagmo/EditorSSB.java index 7cd09578b..a17be652d 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/EditorSSB.java +++ b/app/src/main/java/com/hiddenramblings/tagmo/EditorSSB.java @@ -1,23 +1,32 @@ package com.hiddenramblings.tagmo; import android.app.Activity; +import android.content.DialogInterface; import android.content.Intent; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.Spinner; import android.widget.TextView; -import android.widget.Toast; + +import com.hiddenramblings.tagmo.amiibo.AmiiboSeries; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; +import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; +import java.nio.ByteBuffer; + @EActivity(R.layout.activity_editor_ssb) @OptionsMenu({R.menu.editor_menu}) public class EditorSSB extends AppCompatActivity { + public static final long SSB_AMIIBO_SERIES = 0x0000000000000000L; + public static final int APP_ID = 0x10110E00; + private static final String TAG = "EditorSSB"; @ViewById(R.id.spnAppearance) @@ -61,28 +70,48 @@ public class EditorSSB extends AppCompatActivity { @AfterViews void afterViews() { setListForSpinners(new Spinner[]{spnAppearance}, - R.array.ssb_appearance_values, android.R.layout.simple_list_item_1); + R.array.ssb_appearance_values, android.R.layout.simple_list_item_1); setListForSpinners(new Spinner[]{spnSpecial1, spnSpecial2, spnSpecial3, spnSpecial4}, - R.array.ssb_specials_values, android.R.layout.simple_list_item_1); + R.array.ssb_specials_values, android.R.layout.simple_list_item_1); setListForSpinners(new Spinner[]{spnEffect1, spnEffect2, spnEffect3}, - R.array.ssb_bonus_effects, android.R.layout.simple_list_item_1); + R.array.ssb_bonus_effects, android.R.layout.simple_list_item_1); setListForSpinners(new Spinner[]{spnStatAttack, spnStatDefense, spnStatSpeed}, - R.array.ssb_stat_values, android.R.layout.simple_list_item_1); + R.array.ssb_stat_values, android.R.layout.simple_list_item_1); setListForSpinners(new Spinner[]{spnLevel}, - R.array.ssb_levels, android.R.layout.simple_list_item_1); + R.array.ssb_levels, android.R.layout.simple_list_item_1); keyManager = new KeyManager(this); try { byte[] tagData = getIntent().getByteArrayExtra(Actions.EXTRA_TAG_DATA); + + long amiiboId; + try { + amiiboId = TagUtil.amiiboIdFromTag(tagData); + } catch (Exception e) { + e.printStackTrace(); + LogError("Unable read Amiibo ID"); + return; + } + + if (!canEditAmiibo(amiiboId)) { + LogError("This Amiibo is not compatible with this editor"); + return; + } + tagData = TagUtil.decrypt(keyManager, tagData); this.loadData(tagData); } catch (Exception e) { - Log.d(TAG, "Error decrypting data", e); - finish(); + e.printStackTrace(); + + LogError("Error decyrpting data"); } } + public static boolean canEditAmiibo(long amiiboId) { + return (amiiboId & AmiiboSeries.MASK) == SSB_AMIIBO_SERIES; + } + //all offsets for decrypted (internal) format of tags private static final int OFFSET_APP_DATA = 0xDC; @@ -105,8 +134,8 @@ void afterViews() { private static final int OFFSET_LEVEL = OFFSET_APP_DATA + 0x7C; int readStat(byte[] data, int offset) { - short value = (short)((data[offset] & 0xFF) << 8); - value |= data[offset+1] & 0xFF; + short value = (short) ((data[offset] & 0xFF) << 8); + value |= data[offset + 1] & 0xFF; int res = value; res += 200; //shift the value to make it positive value as the seek bar doesnt support negative @@ -121,8 +150,8 @@ int readStat(byte[] data, int offset) { void writeStat(byte[] data, int value, int offset) { value -= 200; - data[offset] = (byte)(((short)value) >> 8); - data[offset+1] = (byte)(((short)value) & 0xFF); + data[offset] = (byte) (((short) value) >> 8); + data[offset + 1] = (byte) (((short) value) & 0xFF); } void setEffectValue(Spinner spinner, int value) { @@ -161,20 +190,20 @@ void setSpinnerValue(Spinner spinner, int value) { spinner.setSelection(value); } - private static final int[] LEVEL_THRESHOLDS = new int[]{ 0x00, 0x08, 0x010, 0x01D, 0x02D, 0x048, - 0x05B, 0x075, 0x08D, 0x0AF, 0x0E1, 0x0103, 0x0126, 0x0149, 0x0172, 0x0196, 0x01BE, 0x01F7, - 0x0216, 0x0240, 0x0278, 0x02A4, 0x02D6, 0x030E, 0x034C, 0x037C, 0x03BB, 0x03F4, 0x042A, 0x0440, - 0x048A, 0x04B6,0x04E3, 0x053F, 0x056D, 0x059C, 0x0606, 0x0641, 0x0670, 0x069E, 0x06FC, 0x072E, - 0x075D, 0x07B9, 0x07E7, 0x0844, 0x0875, 0x08D3, 0x0902, 0x093E + private static final int[] LEVEL_THRESHOLDS = new int[]{0x00, 0x08, 0x010, 0x01D, 0x02D, 0x048, + 0x05B, 0x075, 0x08D, 0x0AF, 0x0E1, 0x0103, 0x0126, 0x0149, 0x0172, 0x0196, 0x01BE, 0x01F7, + 0x0216, 0x0240, 0x0278, 0x02A4, 0x02D6, 0x030E, 0x034C, 0x037C, 0x03BB, 0x03F4, 0x042A, 0x0440, + 0x048A, 0x04B6, 0x04E3, 0x053F, 0x056D, 0x059C, 0x0606, 0x0641, 0x0670, 0x069E, 0x06FC, 0x072E, + 0x075D, 0x07B9, 0x07E7, 0x0844, 0x0875, 0x08D3, 0x0902, 0x093E }; int readLevel(byte[] data) { int value = (data[OFFSET_LEVEL] & 0xFF) << 8; - value |= (data[OFFSET_LEVEL+1] & 0xFF); + value |= (data[OFFSET_LEVEL + 1] & 0xFF); - for (int i= LEVEL_THRESHOLDS.length-1; i>=0; i--) { + for (int i = LEVEL_THRESHOLDS.length - 1; i >= 0; i--) { if (LEVEL_THRESHOLDS[i] <= value) - return i+1; + return i + 1; } return 1; } @@ -191,6 +220,12 @@ void writeLevel(byte[] data, int level) { } void loadData(final byte[] data) { + int appId = ByteBuffer.wrap(data, TagUtil.APP_ID_OFFSET, TagUtil.APP_ID_LENGTH).getInt(); + if (appId != APP_ID) { + LogError("The Amiibo's app data is not formatted for Super Smash Bros."); + return; + } + try { setSpinnerValue(spnAppearance, data[OFFSET_APPEARANCE] & 0xFF); @@ -208,34 +243,35 @@ void loadData(final byte[] data) { setEffectValue(spnEffect3, data[OFFSET_BONUS_EFFECT3] & 0xFF); setSpinnerValue(spnLevel, readLevel(data) - 1); - } catch (Exception ex) { - Log.e(TAG, "Error loading SSB data", ex); - Toast.makeText(this, "Error loading data. Tag may not be a SSB", Toast.LENGTH_LONG); + } catch (Exception e) { + e.printStackTrace(); + + LogError("Error parsing data"); } } void updateData(byte[] data) { - data[OFFSET_APPEARANCE] = (byte)spnAppearance.getSelectedItemPosition(); - data[OFFSET_SPECIAL_NEUTRAL] = (byte)spnSpecial1.getSelectedItemPosition(); - data[OFFSET_SPECIAL_SIDE_TO_SIDE] = (byte)spnSpecial2.getSelectedItemPosition(); - data[OFFSET_SPECIAL_UP] = (byte)spnSpecial3.getSelectedItemPosition(); - data[OFFSET_SPECIAL_DOWN] = (byte)spnSpecial4.getSelectedItemPosition(); + data[OFFSET_APPEARANCE] = (byte) spnAppearance.getSelectedItemPosition(); + data[OFFSET_SPECIAL_NEUTRAL] = (byte) spnSpecial1.getSelectedItemPosition(); + data[OFFSET_SPECIAL_SIDE_TO_SIDE] = (byte) spnSpecial2.getSelectedItemPosition(); + data[OFFSET_SPECIAL_UP] = (byte) spnSpecial3.getSelectedItemPosition(); + data[OFFSET_SPECIAL_DOWN] = (byte) spnSpecial4.getSelectedItemPosition(); - writeStat(data, (short)spnStatAttack.getSelectedItemPosition(), OFFSET_STATS_ATTACK); - writeStat(data, (short)spnStatDefense.getSelectedItemPosition(), OFFSET_STATS_DEFENSE); - writeStat(data, (short)spnStatSpeed.getSelectedItemPosition(), OFFSET_STATS_SPEED); + writeStat(data, (short) spnStatAttack.getSelectedItemPosition(), OFFSET_STATS_ATTACK); + writeStat(data, (short) spnStatDefense.getSelectedItemPosition(), OFFSET_STATS_DEFENSE); + writeStat(data, (short) spnStatSpeed.getSelectedItemPosition(), OFFSET_STATS_SPEED); - data[OFFSET_BONUS_EFFECT1] = (byte)getEffectValue(spnEffect1); - data[OFFSET_BONUS_EFFECT2] = (byte)getEffectValue(spnEffect2); - data[OFFSET_BONUS_EFFECT3] = (byte)getEffectValue(spnEffect3); + data[OFFSET_BONUS_EFFECT1] = (byte) getEffectValue(spnEffect1); + data[OFFSET_BONUS_EFFECT2] = (byte) getEffectValue(spnEffect2); + data[OFFSET_BONUS_EFFECT3] = (byte) getEffectValue(spnEffect3); - writeLevel(data, spnLevel.getSelectedItemPosition()+1); + writeLevel(data, spnLevel.getSelectedItemPosition() + 1); } void setListForSpinners(Spinner[] controls, int list, int layout) { - for(int i=0; i adapter = ArrayAdapter.createFromResource(this, list, layout); - controls[i].setAdapter(adapter); + control.setAdapter(adapter); } } @@ -255,5 +291,17 @@ void save() { finish(); } - + @UiThread + void LogError(String msg) { + new AlertDialog.Builder(this) + .setTitle("Error") + .setMessage(msg) + .setPositiveButton("Close", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + EditorSSB.this.finish(); + } + }) + .show(); + } } diff --git a/app/src/main/java/com/hiddenramblings/tagmo/EditorTP.java b/app/src/main/java/com/hiddenramblings/tagmo/EditorTP.java index 1ea1df540..ea76f1c12 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/EditorTP.java +++ b/app/src/main/java/com/hiddenramblings/tagmo/EditorTP.java @@ -1,23 +1,28 @@ package com.hiddenramblings.tagmo; import android.app.Activity; +import android.content.DialogInterface; import android.content.Intent; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.widget.ArrayAdapter; import android.widget.Spinner; -import android.widget.Toast; import org.androidannotations.annotations.AfterViews; import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.OptionsItem; import org.androidannotations.annotations.OptionsMenu; +import org.androidannotations.annotations.UiThread; import org.androidannotations.annotations.ViewById; +import java.nio.ByteBuffer; + @EActivity(R.layout.activity_editor_tp) @OptionsMenu({R.menu.editor_menu}) public class EditorTP extends AppCompatActivity { public static final long WOLF_LINK_ID = 0x01030000024F0902L; + public static final int APP_ID = 0x1019C800; private static final String TAG = "EditorTP"; @@ -31,45 +36,68 @@ public class EditorTP extends AppCompatActivity { @AfterViews void afterViews() { setListForSpinners(new Spinner[]{spnShadowCaveLevel}, - R.array.editor_tp_levels, android.R.layout.simple_list_item_1); + R.array.editor_tp_levels, android.R.layout.simple_list_item_1); setListForSpinners(new Spinner[]{spnHearts}, - R.array.editor_tp_hearts, android.R.layout.simple_list_item_1); + R.array.editor_tp_hearts, android.R.layout.simple_list_item_1); keyManager = new KeyManager(this); try { byte[] tagData = getIntent().getByteArrayExtra(Actions.EXTRA_TAG_DATA); + + long amiiboId; + try { + amiiboId = TagUtil.amiiboIdFromTag(tagData); + } catch (Exception e) { + e.printStackTrace(); + LogError("Unable read Amiibo ID"); + return; + } + + if (!canEditAmiibo(amiiboId)) { + LogError("This Amiibo is not compatible with this editor"); + return; + } + tagData = TagUtil.decrypt(keyManager, tagData); this.loadData(tagData); } catch (Exception e) { - Log.d(TAG, "Error decyrpting data", e); - finish(); + e.printStackTrace(); + + LogError("Error decyrpting data"); } } + public static boolean canEditAmiibo(long amiiboId) { + return amiiboId == WOLF_LINK_ID; + } //all offsets for decrypted (internal) format of tags private static final int OFFSET_APP_DATA = 0xED; - private static final int OFFSET_LEVEL = OFFSET_APP_DATA; private static final int OFFSET_HEARTS = OFFSET_APP_DATA + 0x01; - void loadData(final byte[] data) { + int appId = ByteBuffer.wrap(data, TagUtil.APP_ID_OFFSET, TagUtil.APP_ID_LENGTH).getInt(); + if (appId != APP_ID) { + LogError("This Amiibo's app data is not formatted for The Legend of Zelda: Twilight Princess HD HD Cave of Shadows."); + return; + } + try { if (data[OFFSET_LEVEL] < 0 || data[OFFSET_LEVEL] > spnShadowCaveLevel.getAdapter().getCount()) { - throw new IndexOutOfBoundsException("OFFSET_LEVEL Value invalid: "+data[OFFSET_LEVEL]); + throw new IndexOutOfBoundsException("OFFSET_LEVEL Value invalid: " + data[OFFSET_LEVEL]); } if (data[OFFSET_HEARTS] < 0 || data[OFFSET_HEARTS] > spnHearts.getAdapter().getCount()) { - throw new IndexOutOfBoundsException("OFFSET_HEARTS Value invalid: "+data[OFFSET_HEARTS]); + throw new IndexOutOfBoundsException("OFFSET_HEARTS Value invalid: " + data[OFFSET_HEARTS]); } spnShadowCaveLevel.setSelection(data[OFFSET_LEVEL]); spnHearts.setSelection(data[OFFSET_HEARTS]); } catch (IndexOutOfBoundsException e) { - Log.d(TAG, "Error loading data", e); - Toast.makeText(this, R.string.editor_error_outofbounds, Toast.LENGTH_SHORT).show(); - finish(); + e.printStackTrace(); + + LogError("Error parsing data"); } } @@ -100,4 +128,18 @@ void save() { } finish(); } + + @UiThread + void LogError(String msg) { + new AlertDialog.Builder(this) + .setTitle("Error") + .setMessage(msg) + .setPositiveButton("Close", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + EditorTP.this.finish(); + } + }) + .show(); + } } diff --git a/app/src/main/java/com/hiddenramblings/tagmo/MainActivity.java b/app/src/main/java/com/hiddenramblings/tagmo/MainActivity.java index d41c22ea5..183bac9c5 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/MainActivity.java +++ b/app/src/main/java/com/hiddenramblings/tagmo/MainActivity.java @@ -592,9 +592,10 @@ public void onDismissed(Snackbar snackbar, int event) { int tpVisibility = View.GONE; try { long amiiboId = TagUtil.amiiboIdFromTag(currentTagData); - if (amiiboId == EditorTP.WOLF_LINK_ID) { + if (EditorTP.canEditAmiibo(amiiboId)) { tpVisibility = View.VISIBLE; - } else { + } + if (EditorSSB.canEditAmiibo(amiiboId)) { ssbVisibility = View.VISIBLE; } } catch (Exception e) { diff --git a/app/src/main/java/com/hiddenramblings/tagmo/TagUtil.java b/app/src/main/java/com/hiddenramblings/tagmo/TagUtil.java index 9f518e9fe..8e296e7cc 100644 --- a/app/src/main/java/com/hiddenramblings/tagmo/TagUtil.java +++ b/app/src/main/java/com/hiddenramblings/tagmo/TagUtil.java @@ -1,16 +1,15 @@ package com.hiddenramblings.tagmo; -import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; public class TagUtil { public static final int TAG_FILE_SIZE = 532; public static final int PAGE_SIZE = 4; public static final int AMIIBO_ID_OFFSET = 0x54; + public static final int APP_ID_OFFSET = 0xB6; + public static final int APP_ID_LENGTH = 4; public static byte[] keygen(byte[] uuid) { //from AmiiManage (GPL) @@ -53,7 +52,7 @@ public static long amiiboIdFromTag(byte[] data) throws Exception { if (data.length < TAG_FILE_SIZE) throw new Exception("Invalid tag data"); - byte[] amiiboId = new byte[4*2]; + byte[] amiiboId = new byte[4 * 2]; System.arraycopy(data, AMIIBO_ID_OFFSET, amiiboId, 0, amiiboId.length); return ByteBuffer.wrap(amiiboId).getLong(); } @@ -102,7 +101,7 @@ public static byte[] decrypt(KeyManager keyManager, byte[] tagData) throws Excep AmiiTool tool = new AmiiTool(); if (tool.setKeysFixed(keyManager.fixedKey, keyManager.fixedKey.length) == 0) throw new Exception("Failed to initialise amiitool"); - if (tool.setKeysUnfixed(keyManager.unfixedKey, keyManager.unfixedKey.length)== 0) + if (tool.setKeysUnfixed(keyManager.unfixedKey, keyManager.unfixedKey.length) == 0) throw new Exception("Failed to initialise amiitool"); byte[] decrypted = new byte[TagUtil.TAG_FILE_SIZE]; if (tool.unpack(tagData, tagData.length, decrypted, decrypted.length) == 0) @@ -118,7 +117,7 @@ public static byte[] encrypt(KeyManager keyManager, byte[] tagData) throws Excep AmiiTool tool = new AmiiTool(); if (tool.setKeysFixed(keyManager.fixedKey, keyManager.fixedKey.length) == 0) throw new Exception("Failed to initialise amiitool"); - if (tool.setKeysUnfixed(keyManager.unfixedKey, keyManager.unfixedKey.length)== 0) + if (tool.setKeysUnfixed(keyManager.unfixedKey, keyManager.unfixedKey.length) == 0) throw new Exception("Failed to initialise amiitool"); byte[] encrypted = new byte[TagUtil.TAG_FILE_SIZE]; if (tool.pack(tagData, tagData.length, encrypted, encrypted.length) == 0) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f86273b3c..2c1a9c322 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -124,32 +124,36 @@ android:layout_toRightOf="@+id/btnCreateTag" android:text="Allow restore to different tag"/> -