committed by
bunnei
15 changed files with 523 additions and 766 deletions
-
698src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.java
-
463src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
-
7src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/applets/keyboard/SoftwareKeyboard.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivity.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsActivityPresenter.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt
-
20src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
-
6src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
-
4src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DirectoryInitialization.kt
-
12src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
-
65src/android/app/src/main/jni/native.cpp
@ -1,698 +0,0 @@ |
|||
/* |
|||
* Copyright 2013 Dolphin Emulator Project |
|||
* Licensed under GPLv2+ |
|||
* Refer to the license.txt file included. |
|||
*/ |
|||
|
|||
package org.yuzu.yuzu_emu; |
|||
|
|||
import android.app.Activity; |
|||
import android.app.Dialog; |
|||
import android.content.pm.PackageManager; |
|||
import android.content.res.Configuration; |
|||
import android.os.Bundle; |
|||
import android.text.Html; |
|||
import android.text.method.LinkMovementMethod; |
|||
import android.view.Surface; |
|||
import android.view.ViewGroup; |
|||
import android.widget.EditText; |
|||
import android.widget.FrameLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.fragment.app.DialogFragment; |
|||
|
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder; |
|||
|
|||
import org.yuzu.yuzu_emu.activities.EmulationActivity; |
|||
import org.yuzu.yuzu_emu.utils.DocumentsTree; |
|||
import org.yuzu.yuzu_emu.utils.EmulationMenuSettings; |
|||
import org.yuzu.yuzu_emu.utils.FileUtil; |
|||
import org.yuzu.yuzu_emu.utils.Log; |
|||
|
|||
import java.lang.ref.WeakReference; |
|||
import java.util.Objects; |
|||
|
|||
import static android.Manifest.permission.CAMERA; |
|||
import static android.Manifest.permission.RECORD_AUDIO; |
|||
|
|||
/** |
|||
* Class which contains methods that interact |
|||
* with the native side of the Citra code. |
|||
*/ |
|||
public final class NativeLibrary { |
|||
/** |
|||
* Default controller id for each device |
|||
*/ |
|||
public static final int Player1Device = 0; |
|||
public static final int Player2Device = 1; |
|||
public static final int Player3Device = 2; |
|||
public static final int Player4Device = 3; |
|||
public static final int Player5Device = 4; |
|||
public static final int Player6Device = 5; |
|||
public static final int Player7Device = 6; |
|||
public static final int Player8Device = 7; |
|||
public static final int ConsoleDevice = 8; |
|||
public static WeakReference<EmulationActivity> sEmulationActivity = new WeakReference<>(null); |
|||
|
|||
private static boolean alertResult = false; |
|||
private static String alertPromptResult = ""; |
|||
private static int alertPromptButton = 0; |
|||
private static final Object alertPromptLock = new Object(); |
|||
private static boolean alertPromptInProgress = false; |
|||
private static String alertPromptCaption = ""; |
|||
private static int alertPromptButtonConfig = 0; |
|||
private static EditText alertPromptEditText = null; |
|||
|
|||
static { |
|||
try { |
|||
System.loadLibrary("yuzu-android"); |
|||
} catch (UnsatisfiedLinkError ex) { |
|||
Log.error("[NativeLibrary] " + ex.toString()); |
|||
} |
|||
} |
|||
|
|||
private NativeLibrary() { |
|||
// Disallows instantiation. |
|||
} |
|||
|
|||
public static int openContentUri(String path, String openmode) { |
|||
if (DocumentsTree.isNativePath(path)) { |
|||
return YuzuApplication.documentsTree.openContentUri(path, openmode); |
|||
} |
|||
return FileUtil.openContentUri(YuzuApplication.getAppContext(), path, openmode); |
|||
} |
|||
|
|||
public static long getSize(String path) { |
|||
if (DocumentsTree.isNativePath(path)) { |
|||
return YuzuApplication.documentsTree.getFileSize(path); |
|||
} |
|||
return FileUtil.getFileSize(YuzuApplication.getAppContext(), path); |
|||
} |
|||
|
|||
/** |
|||
* Handles button press events for a gamepad. |
|||
* |
|||
* @param Device The input descriptor of the gamepad. |
|||
* @param Button Key code identifying which button was pressed. |
|||
* @param Action Mask identifying which action is happening (button pressed down, or button released). |
|||
* @return If we handled the button press. |
|||
*/ |
|||
public static native boolean onGamePadButtonEvent(int Device, int Button, int Action); |
|||
|
|||
/** |
|||
* Handles joystick movement events. |
|||
* |
|||
* @param Device The device ID of the gamepad. |
|||
* @param Axis The axis ID |
|||
* @param x_axis The value of the x-axis represented by the given ID. |
|||
* @param y_axis The value of the y-axis represented by the given ID. |
|||
*/ |
|||
public static native boolean onGamePadJoystickEvent(int Device, int Axis, float x_axis, float y_axis); |
|||
|
|||
/** |
|||
* Handles motion events. |
|||
* |
|||
* @param delta_timestamp The finger id corresponding to this event |
|||
* @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor. |
|||
* @param accel_x,accel_y,accel_z The value of the y-axis |
|||
*/ |
|||
|
|||
public static native boolean onGamePadMotionEvent(int Device, long delta_timestamp, float gyro_x, float gyro_y, |
|||
float gyro_z, float accel_x, float accel_y, float accel_z); |
|||
|
|||
/** |
|||
* Signals and load a nfc tag |
|||
* |
|||
* @param data Byte array containing all the data from a nfc tag |
|||
*/ |
|||
public static native boolean onReadNfcTag(byte[] data); |
|||
|
|||
/** |
|||
* Removes current loaded nfc tag |
|||
*/ |
|||
public static native boolean onRemoveNfcTag(); |
|||
|
|||
/** |
|||
* Handles touch press events. |
|||
* |
|||
* @param finger_id The finger id corresponding to this event |
|||
* @param x_axis The value of the x-axis. |
|||
* @param y_axis The value of the y-axis. |
|||
*/ |
|||
public static native void onTouchPressed(int finger_id, float x_axis, float y_axis); |
|||
|
|||
/** |
|||
* Handles touch movement. |
|||
* |
|||
* @param x_axis The value of the instantaneous x-axis. |
|||
* @param y_axis The value of the instantaneous y-axis. |
|||
*/ |
|||
public static native void onTouchMoved(int finger_id, float x_axis, float y_axis); |
|||
|
|||
/** |
|||
* Handles touch release events. |
|||
* |
|||
* @param finger_id The finger id corresponding to this event |
|||
*/ |
|||
public static native void onTouchReleased(int finger_id); |
|||
|
|||
public static native void ReloadSettings(); |
|||
|
|||
public static native String GetUserSetting(String gameID, String Section, String Key); |
|||
|
|||
public static native void SetUserSetting(String gameID, String Section, String Key, String Value); |
|||
|
|||
public static native void InitGameIni(String gameID); |
|||
|
|||
/** |
|||
* Gets the embedded icon within the given ROM. |
|||
* |
|||
* @param filename the file path to the ROM. |
|||
* @return a byte array containing the JPEG data for the icon. |
|||
*/ |
|||
public static native byte[] GetIcon(String filename); |
|||
|
|||
/** |
|||
* Gets the embedded title of the given ISO/ROM. |
|||
* |
|||
* @param filename The file path to the ISO/ROM. |
|||
* @return the embedded title of the ISO/ROM. |
|||
*/ |
|||
public static native String GetTitle(String filename); |
|||
|
|||
public static native String GetDescription(String filename); |
|||
|
|||
public static native String GetGameId(String filename); |
|||
|
|||
public static native String GetRegions(String filename); |
|||
|
|||
public static native String GetCompany(String filename); |
|||
|
|||
public static native String GetGitRevision(); |
|||
|
|||
public static native void SetAppDirectory(String directory); |
|||
|
|||
public static native void InitializeGpuDriver(String hookLibDir, String customDriverDir, String customDriverName, String fileRedirectDir); |
|||
|
|||
public static native boolean ReloadKeys(); |
|||
|
|||
public static native void InitializeEmulation(); |
|||
|
|||
public static native int DefaultCPUCore(); |
|||
|
|||
/** |
|||
* Begins emulation. |
|||
*/ |
|||
public static native void Run(String path); |
|||
|
|||
/** |
|||
* Begins emulation from the specified savestate. |
|||
*/ |
|||
public static native void Run(String path, String savestatePath, boolean deleteSavestate); |
|||
|
|||
// Surface Handling |
|||
public static native void SurfaceChanged(Surface surf); |
|||
|
|||
public static native void SurfaceDestroyed(); |
|||
|
|||
/** |
|||
* Unpauses emulation from a paused state. |
|||
*/ |
|||
public static native void UnPauseEmulation(); |
|||
|
|||
/** |
|||
* Pauses emulation. |
|||
*/ |
|||
public static native void PauseEmulation(); |
|||
|
|||
/** |
|||
* Stops emulation. |
|||
*/ |
|||
public static native void StopEmulation(); |
|||
|
|||
/** |
|||
* Resets the in-memory ROM metadata cache. |
|||
*/ |
|||
public static native void ResetRomMetadata(); |
|||
|
|||
/** |
|||
* Returns true if emulation is running (or is paused). |
|||
*/ |
|||
public static native boolean IsRunning(); |
|||
|
|||
/** |
|||
* Returns the performance stats for the current game |
|||
**/ |
|||
public static native double[] GetPerfStats(); |
|||
|
|||
/** |
|||
* Notifies the core emulation that the orientation has changed. |
|||
*/ |
|||
public static native void NotifyOrientationChange(int layout_option, int rotation); |
|||
|
|||
public enum CoreError { |
|||
ErrorSystemFiles, |
|||
ErrorSavestate, |
|||
ErrorUnknown, |
|||
} |
|||
|
|||
private static boolean coreErrorAlertResult = false; |
|||
private static final Object coreErrorAlertLock = new Object(); |
|||
|
|||
public static class CoreErrorDialogFragment extends DialogFragment { |
|||
static CoreErrorDialogFragment newInstance(String title, String message) { |
|||
CoreErrorDialogFragment frag = new CoreErrorDialogFragment(); |
|||
Bundle args = new Bundle(); |
|||
args.putString("title", title); |
|||
args.putString("message", message); |
|||
frag.setArguments(args); |
|||
return frag; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(Bundle savedInstanceState) { |
|||
final Activity emulationActivity = Objects.requireNonNull(getActivity()); |
|||
|
|||
final String title = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("title")); |
|||
final String message = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("message")); |
|||
|
|||
return new MaterialAlertDialogBuilder(emulationActivity) |
|||
.setTitle(title) |
|||
.setMessage(message) |
|||
.setPositiveButton(R.string.continue_button, (dialog, which) -> { |
|||
coreErrorAlertResult = true; |
|||
synchronized (coreErrorAlertLock) { |
|||
coreErrorAlertLock.notify(); |
|||
} |
|||
}) |
|||
.setNegativeButton(R.string.abort_button, (dialog, which) -> { |
|||
coreErrorAlertResult = false; |
|||
synchronized (coreErrorAlertLock) { |
|||
coreErrorAlertLock.notify(); |
|||
} |
|||
}).setOnDismissListener(dialog -> { |
|||
coreErrorAlertResult = true; |
|||
synchronized (coreErrorAlertLock) { |
|||
coreErrorAlertLock.notify(); |
|||
} |
|||
}).create(); |
|||
} |
|||
} |
|||
|
|||
private static void OnCoreErrorImpl(String title, String message) { |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
if (emulationActivity == null) { |
|||
Log.error("[NativeLibrary] EmulationActivity not present"); |
|||
return; |
|||
} |
|||
|
|||
CoreErrorDialogFragment fragment = CoreErrorDialogFragment.newInstance(title, message); |
|||
fragment.show(emulationActivity.getSupportFragmentManager(), "coreError"); |
|||
} |
|||
|
|||
/** |
|||
* Handles a core error. |
|||
* |
|||
* @return true: continue; false: abort |
|||
*/ |
|||
public static boolean OnCoreError(CoreError error, String details) { |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
if (emulationActivity == null) { |
|||
Log.error("[NativeLibrary] EmulationActivity not present"); |
|||
return false; |
|||
} |
|||
|
|||
String title, message; |
|||
switch (error) { |
|||
case ErrorSystemFiles: { |
|||
title = emulationActivity.getString(R.string.system_archive_not_found); |
|||
message = emulationActivity.getString(R.string.system_archive_not_found_message, details.isEmpty() ? emulationActivity.getString(R.string.system_archive_general) : details); |
|||
break; |
|||
} |
|||
case ErrorSavestate: { |
|||
title = emulationActivity.getString(R.string.save_load_error); |
|||
message = details; |
|||
break; |
|||
} |
|||
case ErrorUnknown: { |
|||
title = emulationActivity.getString(R.string.fatal_error); |
|||
message = emulationActivity.getString(R.string.fatal_error_message); |
|||
break; |
|||
} |
|||
default: { |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
// Show the AlertDialog on the main thread. |
|||
emulationActivity.runOnUiThread(() -> OnCoreErrorImpl(title, message)); |
|||
|
|||
// Wait for the lock to notify that it is complete. |
|||
synchronized (coreErrorAlertLock) { |
|||
try { |
|||
coreErrorAlertLock.wait(); |
|||
} catch (Exception ignored) { |
|||
} |
|||
} |
|||
|
|||
return coreErrorAlertResult; |
|||
} |
|||
|
|||
public static boolean isPortraitMode() { |
|||
return YuzuApplication.getAppContext().getResources().getConfiguration().orientation == |
|||
Configuration.ORIENTATION_PORTRAIT; |
|||
} |
|||
|
|||
public static int landscapeScreenLayout() { |
|||
return EmulationMenuSettings.getLandscapeScreenLayout(); |
|||
} |
|||
|
|||
public static boolean displayAlertMsg(final String caption, final String text, |
|||
final boolean yesNo) { |
|||
Log.error("[NativeLibrary] Alert: " + text); |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
boolean result = false; |
|||
if (emulationActivity == null) { |
|||
Log.warning("[NativeLibrary] EmulationActivity is null, can't do panic alert."); |
|||
} else { |
|||
// Create object used for waiting. |
|||
final Object lock = new Object(); |
|||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity) |
|||
.setTitle(caption) |
|||
.setMessage(text); |
|||
|
|||
// If not yes/no dialog just have one button that dismisses modal, |
|||
// otherwise have a yes and no button that sets alertResult accordingly. |
|||
if (!yesNo) { |
|||
builder |
|||
.setCancelable(false) |
|||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> |
|||
{ |
|||
dialog.dismiss(); |
|||
synchronized (lock) { |
|||
lock.notify(); |
|||
} |
|||
}); |
|||
} else { |
|||
alertResult = false; |
|||
|
|||
builder |
|||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> |
|||
{ |
|||
alertResult = true; |
|||
dialog.dismiss(); |
|||
synchronized (lock) { |
|||
lock.notify(); |
|||
} |
|||
}) |
|||
.setNegativeButton(android.R.string.no, (dialog, whichButton) -> |
|||
{ |
|||
alertResult = false; |
|||
dialog.dismiss(); |
|||
synchronized (lock) { |
|||
lock.notify(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// Show the AlertDialog on the main thread. |
|||
emulationActivity.runOnUiThread(builder::show); |
|||
|
|||
// Wait for the lock to notify that it is complete. |
|||
synchronized (lock) { |
|||
try { |
|||
lock.wait(); |
|||
} catch (Exception e) { |
|||
} |
|||
} |
|||
|
|||
if (yesNo) |
|||
result = alertResult; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
public static void retryDisplayAlertPrompt() { |
|||
if (!alertPromptInProgress) { |
|||
return; |
|||
} |
|||
displayAlertPromptImpl(alertPromptCaption, alertPromptEditText.getText().toString(), alertPromptButtonConfig).show(); |
|||
} |
|||
|
|||
public static String displayAlertPrompt(String caption, String text, int buttonConfig) { |
|||
alertPromptCaption = caption; |
|||
alertPromptButtonConfig = buttonConfig; |
|||
alertPromptInProgress = true; |
|||
|
|||
// Show the AlertDialog on the main thread |
|||
sEmulationActivity.get().runOnUiThread(() -> displayAlertPromptImpl(alertPromptCaption, text, alertPromptButtonConfig).show()); |
|||
|
|||
// Wait for the lock to notify that it is complete |
|||
synchronized (alertPromptLock) { |
|||
try { |
|||
alertPromptLock.wait(); |
|||
} catch (Exception e) { |
|||
} |
|||
} |
|||
alertPromptInProgress = false; |
|||
|
|||
return alertPromptResult; |
|||
} |
|||
|
|||
public static MaterialAlertDialogBuilder displayAlertPromptImpl(String caption, String text, int buttonConfig) { |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
alertPromptResult = ""; |
|||
alertPromptButton = 0; |
|||
|
|||
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
|||
params.leftMargin = params.rightMargin = YuzuApplication.getAppContext().getResources().getDimensionPixelSize(R.dimen.dialog_margin); |
|||
|
|||
// Set up the input |
|||
alertPromptEditText = new EditText(YuzuApplication.getAppContext()); |
|||
alertPromptEditText.setText(text); |
|||
alertPromptEditText.setSingleLine(); |
|||
alertPromptEditText.setLayoutParams(params); |
|||
|
|||
FrameLayout container = new FrameLayout(emulationActivity); |
|||
container.addView(alertPromptEditText); |
|||
|
|||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity) |
|||
.setTitle(caption) |
|||
.setView(container) |
|||
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> |
|||
{ |
|||
alertPromptButton = buttonConfig; |
|||
alertPromptResult = alertPromptEditText.getText().toString(); |
|||
synchronized (alertPromptLock) { |
|||
alertPromptLock.notifyAll(); |
|||
} |
|||
}) |
|||
.setOnDismissListener(dialogInterface -> |
|||
{ |
|||
alertPromptResult = ""; |
|||
synchronized (alertPromptLock) { |
|||
alertPromptLock.notifyAll(); |
|||
} |
|||
}); |
|||
|
|||
if (buttonConfig > 0) { |
|||
builder.setNegativeButton(android.R.string.cancel, (dialogInterface, i) -> |
|||
{ |
|||
alertPromptResult = ""; |
|||
synchronized (alertPromptLock) { |
|||
alertPromptLock.notifyAll(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return builder; |
|||
} |
|||
|
|||
public static int alertPromptButton() { |
|||
return alertPromptButton; |
|||
} |
|||
|
|||
public static void exitEmulationActivity(int resultCode) { |
|||
final int Success = 0; |
|||
final int ErrorNotInitialized = 1; |
|||
final int ErrorGetLoader = 2; |
|||
final int ErrorSystemFiles = 3; |
|||
final int ErrorSharedFont = 4; |
|||
final int ErrorVideoCore = 5; |
|||
final int ErrorUnknown = 6; |
|||
final int ErrorLoader = 7; |
|||
|
|||
int captionId; |
|||
int descriptionId; |
|||
switch (resultCode) { |
|||
case ErrorVideoCore: |
|||
captionId = R.string.loader_error_video_core; |
|||
descriptionId = R.string.loader_error_video_core_description; |
|||
break; |
|||
default: |
|||
captionId = R.string.loader_error_encrypted; |
|||
descriptionId = R.string.loader_error_encrypted_roms_description; |
|||
if (!ReloadKeys()) { |
|||
descriptionId = R.string.loader_error_encrypted_keys_description; |
|||
} |
|||
break; |
|||
} |
|||
|
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
if (emulationActivity == null) { |
|||
Log.warning("[NativeLibrary] EmulationActivity is null, can't exit."); |
|||
return; |
|||
} |
|||
|
|||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(emulationActivity) |
|||
.setTitle(captionId) |
|||
.setMessage(Html.fromHtml(emulationActivity.getString(descriptionId), Html.FROM_HTML_MODE_LEGACY)) |
|||
.setPositiveButton(android.R.string.ok, (dialog, whichButton) -> emulationActivity.finish()) |
|||
.setOnDismissListener(dialogInterface -> emulationActivity.finish()); |
|||
emulationActivity.runOnUiThread(() -> { |
|||
AlertDialog alert = builder.create(); |
|||
alert.show(); |
|||
((TextView) alert.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); |
|||
}); |
|||
} |
|||
|
|||
public static void setEmulationActivity(EmulationActivity emulationActivity) { |
|||
Log.verbose("[NativeLibrary] Registering EmulationActivity."); |
|||
sEmulationActivity = new WeakReference<>(emulationActivity); |
|||
} |
|||
|
|||
public static void clearEmulationActivity() { |
|||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity."); |
|||
|
|||
sEmulationActivity.clear(); |
|||
} |
|||
|
|||
private static final Object cameraPermissionLock = new Object(); |
|||
private static boolean cameraPermissionGranted = false; |
|||
public static final int REQUEST_CODE_NATIVE_CAMERA = 800; |
|||
|
|||
public static boolean RequestCameraPermission() { |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
if (emulationActivity == null) { |
|||
Log.error("[NativeLibrary] EmulationActivity not present"); |
|||
return false; |
|||
} |
|||
if (ContextCompat.checkSelfPermission(emulationActivity, CAMERA) == PackageManager.PERMISSION_GRANTED) { |
|||
// Permission already granted |
|||
return true; |
|||
} |
|||
emulationActivity.requestPermissions(new String[]{CAMERA}, REQUEST_CODE_NATIVE_CAMERA); |
|||
|
|||
// Wait until result is returned |
|||
synchronized (cameraPermissionLock) { |
|||
try { |
|||
cameraPermissionLock.wait(); |
|||
} catch (InterruptedException ignored) { |
|||
} |
|||
} |
|||
return cameraPermissionGranted; |
|||
} |
|||
|
|||
public static void CameraPermissionResult(boolean granted) { |
|||
cameraPermissionGranted = granted; |
|||
synchronized (cameraPermissionLock) { |
|||
cameraPermissionLock.notify(); |
|||
} |
|||
} |
|||
|
|||
private static final Object micPermissionLock = new Object(); |
|||
private static boolean micPermissionGranted = false; |
|||
public static final int REQUEST_CODE_NATIVE_MIC = 900; |
|||
|
|||
public static boolean RequestMicPermission() { |
|||
final EmulationActivity emulationActivity = sEmulationActivity.get(); |
|||
if (emulationActivity == null) { |
|||
Log.error("[NativeLibrary] EmulationActivity not present"); |
|||
return false; |
|||
} |
|||
if (ContextCompat.checkSelfPermission(emulationActivity, RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { |
|||
// Permission already granted |
|||
return true; |
|||
} |
|||
emulationActivity.requestPermissions(new String[]{RECORD_AUDIO}, REQUEST_CODE_NATIVE_MIC); |
|||
|
|||
// Wait until result is returned |
|||
synchronized (micPermissionLock) { |
|||
try { |
|||
micPermissionLock.wait(); |
|||
} catch (InterruptedException ignored) { |
|||
} |
|||
} |
|||
return micPermissionGranted; |
|||
} |
|||
|
|||
public static void MicPermissionResult(boolean granted) { |
|||
micPermissionGranted = granted; |
|||
synchronized (micPermissionLock) { |
|||
micPermissionLock.notify(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Logs the Citra version, Android version and, CPU. |
|||
*/ |
|||
public static native void LogDeviceInfo(); |
|||
|
|||
/** |
|||
* Submits inline keyboard text. Called on input for buttons that result text. |
|||
* @param text Text to submit to the inline software keyboard implementation. |
|||
*/ |
|||
public static native void SubmitInlineKeyboardText(String text); |
|||
|
|||
/** |
|||
* Submits inline keyboard input. Used to indicate keys pressed that are not text. |
|||
* @param key_code Android Key Code associated with the keyboard input. |
|||
*/ |
|||
public static native void SubmitInlineKeyboardInput(int key_code); |
|||
|
|||
/** |
|||
* Button type for use in onTouchEvent |
|||
*/ |
|||
public static final class ButtonType { |
|||
public static final int BUTTON_A = 0; |
|||
public static final int BUTTON_B = 1; |
|||
public static final int BUTTON_X = 2; |
|||
public static final int BUTTON_Y = 3; |
|||
public static final int STICK_L = 4; |
|||
public static final int STICK_R = 5; |
|||
public static final int TRIGGER_L = 6; |
|||
public static final int TRIGGER_R = 7; |
|||
public static final int TRIGGER_ZL = 8; |
|||
public static final int TRIGGER_ZR = 9; |
|||
public static final int BUTTON_PLUS = 10; |
|||
public static final int BUTTON_MINUS = 11; |
|||
public static final int DPAD_LEFT = 12; |
|||
public static final int DPAD_UP = 13; |
|||
public static final int DPAD_RIGHT = 14; |
|||
public static final int DPAD_DOWN = 15; |
|||
public static final int BUTTON_SL = 16; |
|||
public static final int BUTTON_SR = 17; |
|||
public static final int BUTTON_HOME = 18; |
|||
public static final int BUTTON_CAPTURE = 19; |
|||
} |
|||
|
|||
/** |
|||
* Stick type for use in onTouchEvent |
|||
*/ |
|||
public static final class StickType { |
|||
public static final int STICK_L = 0; |
|||
public static final int STICK_R = 1; |
|||
} |
|||
|
|||
/** |
|||
* Button states |
|||
*/ |
|||
public static final class ButtonState { |
|||
public static final int RELEASED = 0; |
|||
public static final int PRESSED = 1; |
|||
} |
|||
} |
|||
@ -0,0 +1,463 @@ |
|||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
package org.yuzu.yuzu_emu |
|||
|
|||
import android.app.Dialog |
|||
import android.content.DialogInterface |
|||
import android.os.Bundle |
|||
import android.text.Html |
|||
import android.text.method.LinkMovementMethod |
|||
import android.view.Surface |
|||
import android.view.View |
|||
import android.widget.TextView |
|||
import androidx.fragment.app.DialogFragment |
|||
import com.google.android.material.dialog.MaterialAlertDialogBuilder |
|||
import org.yuzu.yuzu_emu.YuzuApplication.Companion.appContext |
|||
import org.yuzu.yuzu_emu.activities.EmulationActivity |
|||
import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath |
|||
import org.yuzu.yuzu_emu.utils.FileUtil.getFileSize |
|||
import org.yuzu.yuzu_emu.utils.FileUtil.openContentUri |
|||
import org.yuzu.yuzu_emu.utils.Log.error |
|||
import org.yuzu.yuzu_emu.utils.Log.verbose |
|||
import org.yuzu.yuzu_emu.utils.Log.warning |
|||
import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable |
|||
import java.lang.ref.WeakReference |
|||
|
|||
/** |
|||
* Class which contains methods that interact |
|||
* with the native side of the Yuzu code. |
|||
*/ |
|||
object NativeLibrary { |
|||
/** |
|||
* Default controller id for each device |
|||
*/ |
|||
const val Player1Device = 0 |
|||
const val Player2Device = 1 |
|||
const val Player3Device = 2 |
|||
const val Player4Device = 3 |
|||
const val Player5Device = 4 |
|||
const val Player6Device = 5 |
|||
const val Player7Device = 6 |
|||
const val Player8Device = 7 |
|||
const val ConsoleDevice = 8 |
|||
|
|||
var sEmulationActivity = WeakReference<EmulationActivity?>(null) |
|||
|
|||
init { |
|||
try { |
|||
System.loadLibrary("yuzu-android") |
|||
} catch (ex: UnsatisfiedLinkError) { |
|||
error("[NativeLibrary] $ex") |
|||
} |
|||
} |
|||
|
|||
@JvmStatic |
|||
fun openContentUri(path: String?, openmode: String?): Int { |
|||
return if (isNativePath(path!!)) { |
|||
YuzuApplication.documentsTree!!.openContentUri(path, openmode) |
|||
} else openContentUri(appContext, path, openmode) |
|||
} |
|||
|
|||
@JvmStatic |
|||
fun getSize(path: String?): Long { |
|||
return if (isNativePath(path!!)) { |
|||
YuzuApplication.documentsTree!!.getFileSize(path) |
|||
} else getFileSize(appContext, path) |
|||
} |
|||
|
|||
/** |
|||
* Handles button press events for a gamepad. |
|||
* |
|||
* @param Device The input descriptor of the gamepad. |
|||
* @param Button Key code identifying which button was pressed. |
|||
* @param Action Mask identifying which action is happening (button pressed down, or button released). |
|||
* @return If we handled the button press. |
|||
*/ |
|||
external fun onGamePadButtonEvent(Device: Int, Button: Int, Action: Int): Boolean |
|||
|
|||
/** |
|||
* Handles joystick movement events. |
|||
* |
|||
* @param Device The device ID of the gamepad. |
|||
* @param Axis The axis ID |
|||
* @param x_axis The value of the x-axis represented by the given ID. |
|||
* @param y_axis The value of the y-axis represented by the given ID. |
|||
*/ |
|||
external fun onGamePadJoystickEvent( |
|||
Device: Int, |
|||
Axis: Int, |
|||
x_axis: Float, |
|||
y_axis: Float |
|||
): Boolean |
|||
|
|||
/** |
|||
* Handles motion events. |
|||
* |
|||
* @param delta_timestamp The finger id corresponding to this event |
|||
* @param gyro_x,gyro_y,gyro_z The value of the accelerometer sensor. |
|||
* @param accel_x,accel_y,accel_z The value of the y-axis |
|||
*/ |
|||
external fun onGamePadMotionEvent( |
|||
Device: Int, |
|||
delta_timestamp: Long, |
|||
gyro_x: Float, |
|||
gyro_y: Float, |
|||
gyro_z: Float, |
|||
accel_x: Float, |
|||
accel_y: Float, |
|||
accel_z: Float |
|||
): Boolean |
|||
|
|||
/** |
|||
* Signals and load a nfc tag |
|||
* |
|||
* @param data Byte array containing all the data from a nfc tag |
|||
*/ |
|||
external fun onReadNfcTag(data: ByteArray?): Boolean |
|||
|
|||
/** |
|||
* Removes current loaded nfc tag |
|||
*/ |
|||
external fun onRemoveNfcTag(): Boolean |
|||
|
|||
/** |
|||
* Handles touch press events. |
|||
* |
|||
* @param finger_id The finger id corresponding to this event |
|||
* @param x_axis The value of the x-axis. |
|||
* @param y_axis The value of the y-axis. |
|||
*/ |
|||
external fun onTouchPressed(finger_id: Int, x_axis: Float, y_axis: Float) |
|||
|
|||
/** |
|||
* Handles touch movement. |
|||
* |
|||
* @param x_axis The value of the instantaneous x-axis. |
|||
* @param y_axis The value of the instantaneous y-axis. |
|||
*/ |
|||
external fun onTouchMoved(finger_id: Int, x_axis: Float, y_axis: Float) |
|||
|
|||
/** |
|||
* Handles touch release events. |
|||
* |
|||
* @param finger_id The finger id corresponding to this event |
|||
*/ |
|||
external fun onTouchReleased(finger_id: Int) |
|||
|
|||
external fun reloadSettings() |
|||
|
|||
external fun getUserSetting(gameID: String?, Section: String?, Key: String?): String? |
|||
|
|||
external fun setUserSetting(gameID: String?, Section: String?, Key: String?, Value: String?) |
|||
|
|||
external fun initGameIni(gameID: String?) |
|||
|
|||
/** |
|||
* Gets the embedded icon within the given ROM. |
|||
* |
|||
* @param filename the file path to the ROM. |
|||
* @return a byte array containing the JPEG data for the icon. |
|||
*/ |
|||
external fun getIcon(filename: String): ByteArray |
|||
|
|||
/** |
|||
* Gets the embedded title of the given ISO/ROM. |
|||
* |
|||
* @param filename The file path to the ISO/ROM. |
|||
* @return the embedded title of the ISO/ROM. |
|||
*/ |
|||
external fun getTitle(filename: String): String |
|||
|
|||
external fun getDescription(filename: String): String |
|||
|
|||
external fun getGameId(filename: String): String |
|||
|
|||
external fun getRegions(filename: String): String |
|||
|
|||
external fun getCompany(filename: String): String |
|||
|
|||
external fun setAppDirectory(directory: String) |
|||
|
|||
external fun initializeGpuDriver( |
|||
hookLibDir: String?, |
|||
customDriverDir: String?, |
|||
customDriverName: String?, |
|||
fileRedirectDir: String? |
|||
) |
|||
|
|||
external fun reloadKeys(): Boolean |
|||
|
|||
external fun initializeEmulation() |
|||
|
|||
external fun defaultCPUCore(): Int |
|||
|
|||
/** |
|||
* Begins emulation. |
|||
*/ |
|||
external fun run(path: String?) |
|||
|
|||
/** |
|||
* Begins emulation from the specified savestate. |
|||
*/ |
|||
external fun run(path: String?, savestatePath: String?, deleteSavestate: Boolean) |
|||
|
|||
// Surface Handling |
|||
external fun surfaceChanged(surf: Surface?) |
|||
|
|||
external fun surfaceDestroyed() |
|||
|
|||
external fun doFrame() |
|||
|
|||
/** |
|||
* Unpauses emulation from a paused state. |
|||
*/ |
|||
external fun unPauseEmulation() |
|||
|
|||
/** |
|||
* Pauses emulation. |
|||
*/ |
|||
external fun pauseEmulation() |
|||
|
|||
/** |
|||
* Stops emulation. |
|||
*/ |
|||
external fun stopEmulation() |
|||
|
|||
/** |
|||
* Resets the in-memory ROM metadata cache. |
|||
*/ |
|||
external fun resetRomMetadata() |
|||
|
|||
/** |
|||
* Returns true if emulation is running (or is paused). |
|||
*/ |
|||
external fun isRunning(): Boolean |
|||
|
|||
/** |
|||
* Returns the performance stats for the current game |
|||
*/ |
|||
external fun getPerfStats(): DoubleArray |
|||
|
|||
/** |
|||
* Notifies the core emulation that the orientation has changed. |
|||
*/ |
|||
external fun notifyOrientationChange(layout_option: Int, rotation: Int) |
|||
|
|||
enum class CoreError { |
|||
ErrorSystemFiles, |
|||
ErrorSavestate, |
|||
ErrorUnknown |
|||
} |
|||
|
|||
private var coreErrorAlertResult = false |
|||
private val coreErrorAlertLock = Object() |
|||
|
|||
class CoreErrorDialogFragment : DialogFragment() { |
|||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
|||
val title = requireArguments().serializable<String>("title") |
|||
val message = requireArguments().serializable<String>("message") |
|||
|
|||
return MaterialAlertDialogBuilder(requireActivity()) |
|||
.setTitle(title) |
|||
.setMessage(message) |
|||
.setPositiveButton(R.string.continue_button, null) |
|||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int -> |
|||
coreErrorAlertResult = false |
|||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } |
|||
} |
|||
.create() |
|||
} |
|||
|
|||
override fun onDismiss(dialog: DialogInterface) { |
|||
coreErrorAlertResult = true |
|||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() } |
|||
} |
|||
|
|||
companion object { |
|||
fun newInstance(title: String?, message: String?): CoreErrorDialogFragment { |
|||
val frag = CoreErrorDialogFragment() |
|||
val args = Bundle() |
|||
args.putString("title", title) |
|||
args.putString("message", message) |
|||
frag.arguments = args |
|||
return frag |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun onCoreErrorImpl(title: String, message: String) { |
|||
val emulationActivity = sEmulationActivity.get() |
|||
if (emulationActivity == null) { |
|||
error("[NativeLibrary] EmulationActivity not present") |
|||
return |
|||
} |
|||
|
|||
val fragment = CoreErrorDialogFragment.newInstance(title, message) |
|||
fragment.show(emulationActivity.supportFragmentManager, "coreError") |
|||
} |
|||
|
|||
/** |
|||
* Handles a core error. |
|||
* |
|||
* @return true: continue; false: abort |
|||
*/ |
|||
fun onCoreError(error: CoreError?, details: String): Boolean { |
|||
val emulationActivity = sEmulationActivity.get() |
|||
if (emulationActivity == null) { |
|||
error("[NativeLibrary] EmulationActivity not present") |
|||
return false |
|||
} |
|||
|
|||
val title: String |
|||
val message: String |
|||
when (error) { |
|||
CoreError.ErrorSystemFiles -> { |
|||
title = emulationActivity.getString(R.string.system_archive_not_found) |
|||
message = emulationActivity.getString( |
|||
R.string.system_archive_not_found_message, |
|||
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) } |
|||
) |
|||
} |
|||
CoreError.ErrorSavestate -> { |
|||
title = emulationActivity.getString(R.string.save_load_error) |
|||
message = details |
|||
} |
|||
CoreError.ErrorUnknown -> { |
|||
title = emulationActivity.getString(R.string.fatal_error) |
|||
message = emulationActivity.getString(R.string.fatal_error_message) |
|||
} |
|||
else -> { |
|||
return true |
|||
} |
|||
} |
|||
|
|||
// Show the AlertDialog on the main thread. |
|||
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) }) |
|||
|
|||
// Wait for the lock to notify that it is complete. |
|||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() } |
|||
|
|||
return coreErrorAlertResult |
|||
} |
|||
|
|||
@JvmStatic |
|||
fun exitEmulationActivity(resultCode: Int) { |
|||
val Success = 0 |
|||
val ErrorNotInitialized = 1 |
|||
val ErrorGetLoader = 2 |
|||
val ErrorSystemFiles = 3 |
|||
val ErrorSharedFont = 4 |
|||
val ErrorVideoCore = 5 |
|||
val ErrorUnknown = 6 |
|||
val ErrorLoader = 7 |
|||
|
|||
val captionId: Int |
|||
var descriptionId: Int |
|||
when (resultCode) { |
|||
ErrorVideoCore -> { |
|||
captionId = R.string.loader_error_video_core |
|||
descriptionId = R.string.loader_error_video_core_description |
|||
} |
|||
else -> { |
|||
captionId = R.string.loader_error_encrypted |
|||
descriptionId = R.string.loader_error_encrypted_roms_description |
|||
if (!reloadKeys()) { |
|||
descriptionId = R.string.loader_error_encrypted_keys_description |
|||
} |
|||
} |
|||
} |
|||
|
|||
val emulationActivity = sEmulationActivity.get() |
|||
if (emulationActivity == null) { |
|||
warning("[NativeLibrary] EmulationActivity is null, can't exit.") |
|||
return |
|||
} |
|||
|
|||
val builder = MaterialAlertDialogBuilder(emulationActivity) |
|||
.setTitle(captionId) |
|||
.setMessage( |
|||
Html.fromHtml( |
|||
emulationActivity.getString(descriptionId), |
|||
Html.FROM_HTML_MODE_LEGACY |
|||
) |
|||
) |
|||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> emulationActivity.finish() } |
|||
.setOnDismissListener { emulationActivity.finish() } |
|||
emulationActivity.runOnUiThread { |
|||
val alert = builder.create() |
|||
alert.show() |
|||
(alert.findViewById<View>(android.R.id.message) as TextView).movementMethod = |
|||
LinkMovementMethod.getInstance() |
|||
} |
|||
} |
|||
|
|||
fun setEmulationActivity(emulationActivity: EmulationActivity?) { |
|||
verbose("[NativeLibrary] Registering EmulationActivity.") |
|||
sEmulationActivity = WeakReference(emulationActivity) |
|||
} |
|||
|
|||
fun clearEmulationActivity() { |
|||
verbose("[NativeLibrary] Unregistering EmulationActivity.") |
|||
sEmulationActivity.clear() |
|||
} |
|||
|
|||
/** |
|||
* Logs the Yuzu version, Android version and, CPU. |
|||
*/ |
|||
external fun logDeviceInfo() |
|||
|
|||
/** |
|||
* Submits inline keyboard text. Called on input for buttons that result text. |
|||
* @param text Text to submit to the inline software keyboard implementation. |
|||
*/ |
|||
external fun submitInlineKeyboardText(text: String?) |
|||
|
|||
/** |
|||
* Submits inline keyboard input. Used to indicate keys pressed that are not text. |
|||
* @param key_code Android Key Code associated with the keyboard input. |
|||
*/ |
|||
external fun submitInlineKeyboardInput(key_code: Int) |
|||
|
|||
/** |
|||
* Button type for use in onTouchEvent |
|||
*/ |
|||
object ButtonType { |
|||
const val BUTTON_A = 0 |
|||
const val BUTTON_B = 1 |
|||
const val BUTTON_X = 2 |
|||
const val BUTTON_Y = 3 |
|||
const val STICK_L = 4 |
|||
const val STICK_R = 5 |
|||
const val TRIGGER_L = 6 |
|||
const val TRIGGER_R = 7 |
|||
const val TRIGGER_ZL = 8 |
|||
const val TRIGGER_ZR = 9 |
|||
const val BUTTON_PLUS = 10 |
|||
const val BUTTON_MINUS = 11 |
|||
const val DPAD_LEFT = 12 |
|||
const val DPAD_UP = 13 |
|||
const val DPAD_RIGHT = 14 |
|||
const val DPAD_DOWN = 15 |
|||
const val BUTTON_SL = 16 |
|||
const val BUTTON_SR = 17 |
|||
const val BUTTON_HOME = 18 |
|||
const val BUTTON_CAPTURE = 19 |
|||
} |
|||
|
|||
/** |
|||
* Stick type for use in onTouchEvent |
|||
*/ |
|||
object StickType { |
|||
const val STICK_L = 0 |
|||
const val STICK_R = 1 |
|||
} |
|||
|
|||
/** |
|||
* Button states |
|||
*/ |
|||
object ButtonState { |
|||
const val RELEASED = 0 |
|||
const val PRESSED = 1 |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue