Merge branch 'amiibo' into 'master'

android: Add Amiibo file support

See merge request CitraInternal/citra-android!34
This commit is contained in:
bunnei 2020-04-26 08:09:07 +00:00 committed by xperia64
parent d3ea35f528
commit 72f5d4e664
13 changed files with 191 additions and 8 deletions

View File

@ -441,6 +441,10 @@ public final class NativeLibrary {
/// Notifies that the activity is now in foreground and camera devices can now be reloaded
public static native void ReloadCameraDevices();
public static native boolean LoadAmiibo(byte[] bytes);
public static native void RemoveAmiibo();
/**
* Button type for use in onTouchEvent
*/

View File

@ -14,6 +14,7 @@ import java.io.File;
public class CustomFilePickerActivity extends FilePickerActivity {
public static final String EXTRA_TITLE = "filepicker.intent.TITLE";
public static final String EXTRA_EXTENSIONS = "filepicker.intent.EXTENSIONS";
@Override
protected AbstractFilePickerFragment<File> getFragment(
@ -29,6 +30,9 @@ public class CustomFilePickerActivity extends FilePickerActivity {
Intent intent = getIntent();
int title = intent == null ? 0 : intent.getIntExtra(EXTRA_TITLE, 0);
fragment.setTitle(title);
String allowedExtensions = intent == null ? "*" : intent.getStringExtra(EXTRA_EXTENSIONS);
fragment.setAllowedExtensions(allowedExtensions);
return fragment;
}
}

View File

@ -34,10 +34,16 @@ import org.citra.citra_emu.features.settings.ui.SettingsActivity;
import org.citra.citra_emu.features.settings.utils.SettingsFile;
import org.citra.citra_emu.camera.StillImageCameraHelper;
import org.citra.citra_emu.fragments.EmulationFragment;
import org.citra.citra_emu.ui.main.MainActivity;
import org.citra.citra_emu.utils.ControllerMappingHelper;
import org.citra.citra_emu.utils.EmulationMenuSettings;
import org.citra.citra_emu.utils.FileBrowserHelper;
import org.citra.citra_emu.utils.FileUtil;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.util.Collections;
import java.util.List;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@ -58,7 +64,10 @@ public final class EmulationActivity extends AppCompatActivity {
public static final int MENU_ACTION_RESET_OVERLAY = 10;
public static final int MENU_ACTION_SHOW_OVERLAY = 11;
public static final int MENU_ACTION_OPEN_SETTINGS = 12;
public static final int MENU_ACTION_LOAD_AMIIBO = 13;
public static final int MENU_ACTION_REMOVE_AMIIBO = 14;
public static final int REQUEST_SELECT_AMIIBO = 2;
private static final int EMULATION_RUNNING_NOTIFICATION = 0x1000;
private static SparseIntArray buttonsActionsMap = new SparseIntArray();
@ -87,6 +96,10 @@ public final class EmulationActivity extends AppCompatActivity {
.append(R.id.menu_emulation_show_overlay, EmulationActivity.MENU_ACTION_SHOW_OVERLAY);
buttonsActionsMap
.append(R.id.menu_emulation_open_settings, EmulationActivity.MENU_ACTION_OPEN_SETTINGS);
buttonsActionsMap
.append(R.id.menu_emulation_amiibo_load, EmulationActivity.MENU_ACTION_LOAD_AMIIBO);
buttonsActionsMap
.append(R.id.menu_emulation_amiibo_remove, EmulationActivity.MENU_ACTION_REMOVE_AMIIBO);
}
private View mDecorView;
@ -375,6 +388,14 @@ public final class EmulationActivity extends AppCompatActivity {
case MENU_ACTION_OPEN_SETTINGS:
SettingsActivity.launch(this, SettingsFile.FILE_NAME_CONFIG, "");
break;
case MENU_ACTION_LOAD_AMIIBO:
FileBrowserHelper.openFilePicker(this, REQUEST_SELECT_AMIIBO, R.string.select_amiibo, Collections.singletonList("bin"));
break;
case MENU_ACTION_REMOVE_AMIIBO:
RemoveAmiibo();
break;
}
return true;
@ -422,6 +443,47 @@ public final class EmulationActivity extends AppCompatActivity {
return NativeLibrary.onGamePadEvent(input.getDescriptor(), button, action);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
super.onActivityResult(requestCode, resultCode, result);
switch (requestCode) {
case REQUEST_SELECT_AMIIBO:
// If the user picked a file, as opposed to just backing out.
if (resultCode == MainActivity.RESULT_OK) {
String[] selectedFiles = FileBrowserHelper.getSelectedFiles(result);
if (selectedFiles == null)
return;
onAmiiboSelected(selectedFiles[0]);
}
break;
}
}
private void onAmiiboSelected(String selectedFile) {
File file = new File(selectedFile);
boolean success = false;
try {
byte[] bytes = FileUtil.getBytesFromFile(file);
success = NativeLibrary.LoadAmiibo(bytes);
} catch (IOException e) {
e.printStackTrace();
}
if (!success) {
new AlertDialog.Builder(this)
.setTitle(R.string.amiibo_load_error)
.setMessage(R.string.amiibo_load_error_message)
.setPositiveButton(android.R.string.ok, null)
.create()
.show();
}
}
private void RemoveAmiibo() {
NativeLibrary.RemoveAmiibo();
}
private void toggleControls() {
final SharedPreferences.Editor editor = mPreferences.edit();
boolean[] enabledButtons = new boolean[14];

View File

@ -17,12 +17,13 @@ import org.citra.citra_emu.R;
import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
import java.util.List;
public class CustomFilePickerFragment extends FilePickerFragment {
private static String ALL_FILES = "*";
private int mTitle;
private static final Set<String> extensions = new HashSet<>(Arrays.asList("elf", "axf", "cci", "3ds", "cxi", "app", "3dsx", "cia"));
private static List<String> extensions = Collections.singletonList(ALL_FILES);
@NonNull
@Override
@ -66,6 +67,12 @@ public class CustomFilePickerFragment extends FilePickerFragment {
mTitle = title;
}
public void setAllowedExtensions(String allowedExtensions) {
if (allowedExtensions == null)
return;
extensions = Arrays.asList(allowedExtensions.split(","));
}
@Override
protected boolean isItemVisible(@NonNull final File file) {
@ -73,7 +80,7 @@ public class CustomFilePickerFragment extends FilePickerFragment {
// files if the files don't show up in the file picker when mode == MODE_DIR.
// To avoid this, show files even when the user needs to select a directory.
return (showHiddenItems || !file.isHidden()) &&
(file.isDirectory() ||
(file.isDirectory() || extensions.contains(ALL_FILES) ||
extensions.contains(fileExtension(file.getName()).toLowerCase()));
}

View File

@ -2,7 +2,6 @@ package org.citra.citra_emu.model;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Environment;
import java.nio.file.Paths;

View File

@ -26,6 +26,8 @@ import org.citra.citra_emu.utils.PicassoUtils;
import org.citra.citra_emu.utils.StartupHandler;
import org.citra.citra_emu.utils.ThemeUtil;
import java.util.Arrays;
/**
* The main Activity of the Lollipop style UI. Manages several PlatformGamesFragments, which
* individually display a grid of available games for each Fragment, in a tabbed layout.
@ -127,7 +129,7 @@ public final class MainActivity extends AppCompatActivity implements MainView {
public void launchFileListActivity() {
if (PermissionsHandler.hasWriteAccess(this)) {
FileBrowserHelper.openDirectoryPicker(this, MainPresenter.REQUEST_ADD_DIRECTORY,
R.string.select_game_folder);
R.string.select_game_folder, Arrays.asList("elf", "axf", "cci", "3ds", "cxi", "app", "3dsx", "cia"));
} else {
PermissionsHandler.checkWritePermission(this);
}

View File

@ -16,7 +16,7 @@ import java.io.File;
import java.util.List;
public final class FileBrowserHelper {
public static void openDirectoryPicker(FragmentActivity activity, int requestCode, int title) {
public static void openDirectoryPicker(FragmentActivity activity, int requestCode, int title, List<String> extensions) {
Intent i = new Intent(activity, CustomFilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
@ -25,11 +25,12 @@ public final class FileBrowserHelper {
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
i.putExtra(CustomFilePickerActivity.EXTRA_TITLE, title);
i.putExtra(CustomFilePickerActivity.EXTRA_EXTENSIONS, String.join(",", extensions));
activity.startActivityForResult(i, requestCode);
}
public static void openFilePicker(FragmentActivity activity, int requestCode, int title) {
public static void openFilePicker(FragmentActivity activity, int requestCode, int title, List<String> extensions) {
Intent i = new Intent(activity, CustomFilePickerActivity.class);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
@ -38,6 +39,7 @@ public final class FileBrowserHelper {
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
i.putExtra(CustomFilePickerActivity.EXTRA_TITLE, title);
i.putExtra(CustomFilePickerActivity.EXTRA_EXTENSIONS, String.join(",", extensions));
activity.startActivityForResult(i, requestCode);
}
@ -53,4 +55,18 @@ public final class FileBrowserHelper {
return null;
}
@Nullable
public static String[] getSelectedFiles(Intent result) {
// Use the provided utility method to parse the result
List<Uri> files = Utils.getSelectedFilesFromResult(result);
if (!files.isEmpty()) {
String[] paths = new String[files.size()];
for (int i = 0; i < files.size(); i++)
paths[i] = Utils.getFileForUri(files.get(i)).getAbsolutePath();
return paths;
}
return null;
}
}

View File

@ -0,0 +1,37 @@
package org.citra.citra_emu.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileUtil {
public static byte[] getBytesFromFile(File file) throws IOException {
final long length = file.length();
// You cannot create an array using a long type.
if (length > Integer.MAX_VALUE) {
// File is too large
throw new IOException("File is too large!");
}
byte[] bytes = new byte[(int) length];
int offset = 0;
int numRead;
try (InputStream is = new FileInputStream(file)) {
while (offset < bytes.length
&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
offset += numRead;
}
}
// Ensure all the bytes have been read in
if (offset < bytes.length) {
throw new IOException("Could not completely read file " + file.getName());
}
return bytes;
}
}

View File

@ -19,6 +19,7 @@
#include "core/frontend/camera/factory.h"
#include "core/frontend/scope_acquire_context.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/settings.h"
#include "jni/applets/swkbd.h"
#include "jni/button_manager.h"
@ -481,4 +482,30 @@ void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jc
}
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv *env, jclass clazz, jbyteArray bytes) {
Core::System& system{Core::System::GetInstance()};
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (nfc == nullptr || env->GetArrayLength(bytes) != sizeof(Service::NFC::AmiiboData)) {
return static_cast<jboolean>(false);
}
Service::NFC::AmiiboData amiibo_data{};
env->GetByteArrayRegion(bytes, 0, sizeof(Service::NFC::AmiiboData), reinterpret_cast<jbyte*>(&amiibo_data));
nfc->LoadAmiibo(amiibo_data);
return static_cast<jboolean>(true);
}
void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv *env, jclass clazz) {
Core::System &system{Core::System::GetInstance()};
Service::SM::ServiceManager &sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (nfc == nullptr) {
return;
}
nfc->RemoveAmiibo();
}
} // extern "C"

View File

@ -134,6 +134,10 @@ Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEnv* env, jclas
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env,
jclass clazz);
JNIEXPORT jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv *env, jclass clazz, jbyteArray bytes);
JNIEXPORT void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv *env, jclass clazz);
#ifdef __cplusplus
}
#endif

View File

@ -25,6 +25,20 @@
</menu>
</item>
<item
android:id="@+id/menu_emulation_amiibo"
android:title="@string/menu_emulation_amiibo">
<menu>
<item
android:id="@+id/menu_emulation_amiibo_load"
android:title="@string/menu_emulation_amiibo_load"/>
<item
android:id="@+id/menu_emulation_amiibo_remove"
android:title="@string/menu_emulation_amiibo_remove"/>
</menu>
</item>
<item
android:id="@+id/menu_emulation_switch_screen_layout"
app:showAsAction="never"

View File

@ -145,6 +145,12 @@
<string name="emulation_show_overlay">Show Overlay</string>
<string name="emulation_close_game">Close Game</string>
<string name="emulation_close_game_message">Are you sure that you would like to close the current game?</string>
<string name="menu_emulation_amiibo">Amiibo</string>
<string name="menu_emulation_amiibo_load">Load</string>
<string name="menu_emulation_amiibo_remove">Remove</string>
<string name="select_amiibo">Select Amiibo file</string>
<string name="amiibo_load_error">Error loading Amiibo</string>
<string name="amiibo_load_error_message">While loading the specified Amiibo file, an error occurred. Please check that the file is correct.</string>
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
<string name="load_settings">Loading Settings...</string>

View File

@ -29,6 +29,7 @@
<style name="CitraEmulationBase" parent="Theme.AppCompat.DayNight.DarkActionBar">
<item name="colorPrimary">@color/citra_orange</item>
<item name="colorPrimaryDark">@color/citra_orange_dark</item>
<item name="colorAccent">@color/citra_accent</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowBackground">@android:color/black</item>