miroir de https://git.suyu.dev/suyu/suyu.git
463 lignes
14 KiB
Kotlin
463 lignes
14 KiB
Kotlin
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
package dev.suyu.suyu_emu
|
|
|
|
import android.content.DialogInterface
|
|
import android.net.Uri
|
|
import android.text.Html
|
|
import android.text.method.LinkMovementMethod
|
|
import android.view.Surface
|
|
import android.view.View
|
|
import android.widget.TextView
|
|
import androidx.annotation.Keep
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
import java.lang.ref.WeakReference
|
|
import dev.suyu.suyu_emu.activities.EmulationActivity
|
|
import dev.suyu.suyu_emu.fragments.CoreErrorDialogFragment
|
|
import dev.suyu.suyu_emu.utils.DocumentsTree
|
|
import dev.suyu.suyu_emu.utils.FileUtil
|
|
import dev.suyu.suyu_emu.utils.Log
|
|
import dev.suyu.suyu_emu.model.InstallResult
|
|
import dev.suyu.suyu_emu.model.Patch
|
|
import dev.suyu.suyu_emu.model.GameVerificationResult
|
|
|
|
/**
|
|
* Class which contains methods that interact
|
|
* with the native side of the Suyu code.
|
|
*/
|
|
object NativeLibrary {
|
|
@JvmField
|
|
var sEmulationActivity = WeakReference<EmulationActivity?>(null)
|
|
|
|
init {
|
|
try {
|
|
System.loadLibrary("suyu-android")
|
|
} catch (ex: UnsatisfiedLinkError) {
|
|
error("[NativeLibrary] $ex")
|
|
}
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun openContentUri(path: String?, openmode: String?): Int {
|
|
return if (DocumentsTree.isNativePath(path!!)) {
|
|
SuyuApplication.documentsTree!!.openContentUri(path, openmode)
|
|
} else {
|
|
FileUtil.openContentUri(path, openmode)
|
|
}
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun getSize(path: String?): Long {
|
|
return if (DocumentsTree.isNativePath(path!!)) {
|
|
SuyuApplication.documentsTree!!.getFileSize(path)
|
|
} else {
|
|
FileUtil.getFileSize(path)
|
|
}
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun exists(path: String?): Boolean {
|
|
return if (DocumentsTree.isNativePath(path!!)) {
|
|
SuyuApplication.documentsTree!!.exists(path)
|
|
} else {
|
|
FileUtil.exists(path, suppressLog = true)
|
|
}
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun isDirectory(path: String?): Boolean {
|
|
return if (DocumentsTree.isNativePath(path!!)) {
|
|
SuyuApplication.documentsTree!!.isDirectory(path)
|
|
} else {
|
|
FileUtil.isDirectory(path)
|
|
}
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun getParentDirectory(path: String): String =
|
|
if (DocumentsTree.isNativePath(path)) {
|
|
SuyuApplication.documentsTree!!.getParentDirectory(path)
|
|
} else {
|
|
path
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun getFilename(path: String): String =
|
|
if (DocumentsTree.isNativePath(path)) {
|
|
SuyuApplication.documentsTree!!.getFilename(path)
|
|
} else {
|
|
FileUtil.getFilename(Uri.parse(path))
|
|
}
|
|
|
|
external fun setAppDirectory(directory: String)
|
|
|
|
/**
|
|
* Installs a nsp or xci file to nand
|
|
* @param filename String representation of file uri
|
|
* @return int representation of [InstallResult]
|
|
*/
|
|
external fun installFileToNand(
|
|
filename: String,
|
|
callback: (max: Long, progress: Long) -> Boolean
|
|
): Int
|
|
|
|
external fun doesUpdateMatchProgram(programId: String, updatePath: String): Boolean
|
|
|
|
external fun initializeGpuDriver(
|
|
hookLibDir: String?,
|
|
customDriverDir: String?,
|
|
customDriverName: String?,
|
|
fileRedirectDir: String?
|
|
)
|
|
|
|
external fun reloadKeys(): Boolean
|
|
|
|
external fun initializeSystem(reload: Boolean)
|
|
|
|
/**
|
|
* Begins emulation.
|
|
*/
|
|
external fun run(path: String?, programIndex: Int, frontendInitiated: Boolean)
|
|
|
|
// Surface Handling
|
|
external fun surfaceChanged(surf: Surface?)
|
|
|
|
external fun surfaceDestroyed()
|
|
|
|
/**
|
|
* Unpauses emulation from a paused state.
|
|
*/
|
|
external fun unpauseEmulation()
|
|
|
|
/**
|
|
* Pauses emulation.
|
|
*/
|
|
external fun pauseEmulation()
|
|
|
|
/**
|
|
* Stops emulation.
|
|
*/
|
|
external fun stopEmulation()
|
|
|
|
/**
|
|
* Returns true if emulation is running (or is paused).
|
|
*/
|
|
external fun isRunning(): Boolean
|
|
|
|
/**
|
|
* Returns true if emulation is paused.
|
|
*/
|
|
external fun isPaused(): Boolean
|
|
|
|
/**
|
|
* Returns the performance stats for the current game
|
|
*/
|
|
external fun getPerfStats(): DoubleArray
|
|
|
|
/**
|
|
* Returns the current CPU backend.
|
|
*/
|
|
external fun getCpuBackend(): String
|
|
|
|
/**
|
|
* Returns the current GPU Driver.
|
|
*/
|
|
external fun getGpuDriver(): String
|
|
|
|
external fun applySettings()
|
|
|
|
external fun logSettings()
|
|
|
|
enum class CoreError {
|
|
ErrorSystemFiles,
|
|
ErrorSavestate,
|
|
ErrorUnknown
|
|
}
|
|
|
|
var coreErrorAlertResult = false
|
|
val coreErrorAlertLock = Object()
|
|
|
|
private fun onCoreErrorImpl(title: String, message: String) {
|
|
val emulationActivity = sEmulationActivity.get()
|
|
if (emulationActivity == null) {
|
|
Log.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) {
|
|
Log.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 { onCoreErrorImpl(title, message) }
|
|
|
|
// Wait for the lock to notify that it is complete.
|
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.wait() }
|
|
|
|
return coreErrorAlertResult
|
|
}
|
|
|
|
@Keep
|
|
@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) {
|
|
Log.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?) {
|
|
Log.debug("[NativeLibrary] Registering EmulationActivity.")
|
|
sEmulationActivity = WeakReference(emulationActivity)
|
|
}
|
|
|
|
fun clearEmulationActivity() {
|
|
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
|
|
sEmulationActivity.clear()
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun onEmulationStarted() {
|
|
sEmulationActivity.get()!!.onEmulationStarted()
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun onEmulationStopped(status: Int) {
|
|
sEmulationActivity.get()!!.onEmulationStopped(status)
|
|
}
|
|
|
|
@Keep
|
|
@JvmStatic
|
|
fun onProgramChanged(programIndex: Int) {
|
|
sEmulationActivity.get()!!.onProgramChanged(programIndex)
|
|
}
|
|
|
|
/**
|
|
* Logs the Suyu 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)
|
|
|
|
/**
|
|
* Creates a generic user directory if it doesn't exist already
|
|
*/
|
|
external fun initializeEmptyUserDirectory()
|
|
|
|
/**
|
|
* Gets the launch path for a given applet. It is the caller's responsibility to also
|
|
* set the system's current applet ID before trying to launch the nca given by this function.
|
|
*
|
|
* @param id The applet entry ID
|
|
* @return The applet's launch path
|
|
*/
|
|
external fun getAppletLaunchPath(id: Long): String
|
|
|
|
/**
|
|
* Sets the system's current applet ID before launching.
|
|
*
|
|
* @param appletId One of the ids in the Service::AM::Applets::AppletId enum
|
|
*/
|
|
external fun setCurrentAppletId(appletId: Int)
|
|
|
|
/**
|
|
* Sets the cabinet mode for launching the cabinet applet.
|
|
*
|
|
* @param cabinetMode One of the modes that corresponds to the enum in Service::NFP::CabinetMode
|
|
*/
|
|
external fun setCabinetMode(cabinetMode: Int)
|
|
|
|
/**
|
|
* Checks whether NAND contents are available and valid.
|
|
*
|
|
* @return 'true' if firmware is available
|
|
*/
|
|
external fun isFirmwareAvailable(): Boolean
|
|
|
|
/**
|
|
* Checks the PatchManager for any addons that are available
|
|
*
|
|
* @param path Path to game file. Can be a [Uri].
|
|
* @param programId String representation of a game's program ID
|
|
* @return Array of available patches
|
|
*/
|
|
external fun getPatchesForFile(path: String, programId: String): Array<Patch>?
|
|
|
|
/**
|
|
* Removes an update for a given [programId]
|
|
* @param programId String representation of a game's program ID
|
|
*/
|
|
external fun removeUpdate(programId: String)
|
|
|
|
/**
|
|
* Removes all DLC for a [programId]
|
|
* @param programId String representation of a game's program ID
|
|
*/
|
|
external fun removeDLC(programId: String)
|
|
|
|
/**
|
|
* Removes a mod installed for a given [programId]
|
|
* @param programId String representation of a game's program ID
|
|
* @param name The name of a mod as given by [getPatchesForFile]. This corresponds with the name
|
|
* of the mod's directory in a game's load folder.
|
|
*/
|
|
external fun removeMod(programId: String, name: String)
|
|
|
|
/**
|
|
* Verifies all installed content
|
|
* @param callback UI callback for verification progress. Return true in the callback to cancel.
|
|
* @return Array of content that failed verification. Successful if empty.
|
|
*/
|
|
external fun verifyInstalledContents(
|
|
callback: (max: Long, progress: Long) -> Boolean
|
|
): Array<String>
|
|
|
|
/**
|
|
* Verifies the contents of a game
|
|
* @param path String path to a game
|
|
* @param callback UI callback for verification progress. Return true in the callback to cancel.
|
|
* @return Int that is meant to be converted to a [GameVerificationResult]
|
|
*/
|
|
external fun verifyGameContents(
|
|
path: String,
|
|
callback: (max: Long, progress: Long) -> Boolean
|
|
): Int
|
|
|
|
/**
|
|
* Gets the save location for a specific game
|
|
*
|
|
* @param programId String representation of a game's program ID
|
|
* @return Save data path that may not exist yet
|
|
*/
|
|
external fun getSavePath(programId: String): String
|
|
|
|
/**
|
|
* Gets the root save directory for the default profile as either
|
|
* /user/save/account/<user id raw string> or /user/save/000...000/<user id>
|
|
*
|
|
* @param future If true, returns the /user/save/account/... directory
|
|
* @return Save data path that may not exist yet
|
|
*/
|
|
external fun getDefaultProfileSaveDataRoot(future: Boolean): String
|
|
|
|
/**
|
|
* Adds a file to the manual filesystem provider in our EmulationSession instance
|
|
* @param path Path to the file we're adding. Can be a string representation of a [Uri] or
|
|
* a normal path
|
|
*/
|
|
external fun addFileToFilesystemProvider(path: String)
|
|
|
|
/**
|
|
* Clears all files added to the manual filesystem provider in our EmulationSession instance
|
|
*/
|
|
external fun clearFilesystemProvider()
|
|
|
|
/**
|
|
* Checks if all necessary keys are present for decryption
|
|
*/
|
|
external fun areKeysPresent(): Boolean
|
|
}
|