Enables, GBA on android. links to games, can be played on its own.

This commit is contained in:
Linkinworm 2026-04-17 21:39:34 +01:00
parent c0e0b685f3
commit 34af99ad26
27 changed files with 1795 additions and 390 deletions

View File

@ -13,11 +13,13 @@ import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import org.dolphinemu.dolphinemu.dialogs.AlertMessage
import org.dolphinemu.dolphinemu.features.gba.GbaRenderManager
import org.dolphinemu.dolphinemu.utils.CompressCallback
import org.dolphinemu.dolphinemu.utils.Log
import java.lang.ref.WeakReference
import java.util.concurrent.Semaphore
/**
* Class which contains methods that interact
* with the native side of the Dolphin code.
@ -366,11 +368,11 @@ object NativeLibrary {
@JvmStatic
external fun StopEmulation()
/**
* Ensures that IsRunning will return true from now on until emulation exits.
* (If this is not called, IsRunning will start returning true at some point
* after calling Run.)
*/
/**
* Ensures that IsRunning will return true from now on until emulation exits.
* (If this is not called, IsRunning will start returning true at some point
* after calling Run.)
*/
@JvmStatic
external fun SetIsBooting()
@ -450,6 +452,25 @@ object NativeLibrary {
@JvmStatic
external fun GetGameAspectRatio(): Float
@JvmStatic
external fun copyGBAFramebuffer(slot: Int, buffer: java.nio.ByteBuffer):
Boolean
@JvmStatic
external fun resetGBACore(slot: Int)
@JvmStatic
external fun getGBAGameTitle(slot: Int): String
@JvmStatic
external fun getGBAGameCode(slot: Int): String
@Keep
@JvmStatic
fun onGBAFrame(slot: Int) {
GbaRenderManager.onFrame(slot)
}
@JvmStatic
fun IsEmulatingWii(): Boolean {
checkGameMetadataValid()

View File

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.activities
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.graphics.Rect
@ -14,13 +15,17 @@ import android.util.SparseIntArray
import android.view.KeyEvent
import android.view.MenuItem
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.doOnLayout
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
@ -36,12 +41,15 @@ import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ActivityEmulationBinding
import org.dolphinemu.dolphinemu.databinding.DialogInputAdjustBinding
import org.dolphinemu.dolphinemu.databinding.DialogNfcFiguresManagerBinding
import org.dolphinemu.dolphinemu.features.gba.GBAOverlayView
import org.dolphinemu.dolphinemu.features.gba.GbaRenderManager
import org.dolphinemu.dolphinemu.features.infinitybase.InfinityConfig
import org.dolphinemu.dolphinemu.features.infinitybase.model.Figure
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlot
import org.dolphinemu.dolphinemu.features.infinitybase.ui.FigureSlotAdapter
import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface
import org.dolphinemu.dolphinemu.features.input.model.DolphinSensorEventListener
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
import org.dolphinemu.dolphinemu.features.settings.model.Settings
@ -72,6 +80,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
private lateinit var settings: Settings
private val gbaViews = mutableListOf<GBAOverlayView>()
private val lastGbaTapTimes = mutableMapOf<Int, Long>()
private var isGbaLocked = false
override var themeId = 0
private var menuVisible = false
@ -186,6 +200,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
super.onCreate(savedInstanceState)
//gba overlay setup, also clean up views from previous launch to prevent stacking
GbaRenderManager.detach()
gbaViews.forEach { binding.root.removeView(it) }
gbaViews.clear()
lastGbaTapTimes.clear()
MainPresenter.skipRescanningLibrary()
if (savedInstanceState == null) {
@ -211,6 +231,38 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
binding = ActivityEmulationBinding.inflate(layoutInflater)
setContentView(binding.root)
//Read snap state before creating new views
val globalGbaPrefs = getSharedPreferences("gba_overlay", Context.MODE_PRIVATE)
isGbaLocked = globalGbaPrefs.getBoolean("gba_locked", false)
for (slot in 0 until 4) {
if (IntSetting.getSettingForSIDevice(slot).int != 13) continue
val view = GBAOverlayView(this)
view.gbaSlot = slot
val slotPrefs = getSharedPreferences("gba_overlay_${'$'}slot", Context.MODE_PRIVATE)
val sw = slotPrefs.getFloat("gba_width", 480f).coerceIn(120f, 960f)
val sh = slotPrefs.getFloat("gba_height", 320f).coerceIn(80f, 640f)
val screenW = resources.displayMetrics.widthPixels.toFloat()
val screenH = resources.displayMetrics.heightPixels.toFloat()
var sx = slotPrefs.getFloat("gba_x", 16f + slot * 20f)
var sy = slotPrefs.getFloat("gba_y", screenH - sh - 16f - slot * 20f)
if (sx < 0 || sx > screenW) sx = 16f + slot * 20f
if (sy < 0 || sy > screenH) sy = screenH - sh - 16f
val params = FrameLayout.LayoutParams(sw.toInt(), sh.toInt())
binding.root.addView(view, 0, params)
view.x = sx;
view.y = sy
view.visibility = android.view.View.VISIBLE
InputOverrider.registerGBA(slot)
attachGbaTouchListener(view, slot, slotPrefs)
gbaViews.add(view)
}
if (gbaViews.isNotEmpty() && NativeLibrary.IsGameMetadataValid()) {
GbaRenderManager.attach(gbaViews)
binding.root.doOnLayout { applyGbaLayout() }
}
setInsets()
// Find or create the EmulationFragment
@ -332,6 +384,25 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
emulationFragment?.refreshInputOverlay()
updateDisplaySettings()
val activeSlots = (0 until 4).filter {
IntSetting.getSettingForSIDevice(it).int == 13
}
gbaViews.forEachIndexed { index, view ->
if (index < activeSlots.size) {
view.gbaSlot = activeSlots[index]
view.visibility = android.view.View.VISIBLE
InputOverrider.registerGBA(activeSlots[index])
} else view.visibility = android.view.View.GONE
}
if (gbaViews.isNotEmpty()) {
if (GbaRenderManager.isAttached()) {
GbaRenderManager.updateViews(gbaViews)
} else {
GbaRenderManager.attach(gbaViews)
}
binding.root.post { applyGbaLayout() }
}
} catch (_: IllegalStateException) {
// Most likely the core delivered an onTitleChanged while emulation was shutting down.
// Let's just ignore it, since we're about to shut down anyway.
@ -340,9 +411,41 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
override fun onDestroy() {
super.onDestroy()
GbaRenderManager.detach()
for (slot in 0 until 4) {
InputOverrider.unregisterGBA(slot)
}
gbaViews.forEach { binding.root.removeView(it) }
gbaViews.clear()
settings.close()
}
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
super.onConfigurationChanged(newConfig)
if (gbaViews.isNotEmpty()) {
GbaRenderManager.updateViews(gbaViews)
binding.root.post { applyGbaLayout() }
} else {
//no gba - restore game to full screen to try stop portrait squish into landscape
binding.frameEmulationFragment.x = 0f
binding.frameEmulationFragment.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
binding.frameEmulationFragment.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.frameEmulationFragment.requestLayout()
}
}
private fun setGbaViewsTouchable(touchable: Boolean) {
gbaViews.forEach { view ->
view.isClickable = touchable
view.isFocusable = touchable
view.isFocusableInTouchMode = touchable
if (!touchable) {
view.setOnTouchListener(null)
}
}
}
override fun onBackPressed() {
if (!closeSubmenu()) {
toggleMenu()
@ -361,7 +464,8 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
WindowCompat.setDecorFitsSystemWindows(window, false)
WindowInsetsControllerCompat(window, window.decorView).let { controller ->
controller.hide(WindowInsetsCompat.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
}
@ -443,6 +547,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
menu.findItem(R.id.menu_emulation_ir_recenter).isChecked =
BooleanSetting.MAIN_IR_ALWAYS_RECENTER.boolean
}
menu.findItem(R.id.menu_emulation_gba_snap)?.isChecked = isGbaLocked
popup.setOnMenuItemClickListener { item: MenuItem -> onOptionsItemSelected(item) }
popup.show()
}
@ -471,6 +576,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
item.isChecked = !item.isChecked
toggleRecenter(item.isChecked)
}
MENU_ACTION_GBA_SNAP -> {
item.isChecked = !item.isChecked
toggleGBASnap()
}
}
}
@ -518,6 +628,13 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
MENU_ACTION_SKYLANDERS -> showSkylanderPortalSettings()
MENU_ACTION_INFINITY_BASE -> showInfinityBaseSettings()
MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation()
MENU_ACTION_GBA_SNAP -> toggleGBASnap()
MENU_ACTION_GBA_RESET -> {
isGbaLocked = false; resetGBAScreens();
binding.root.post { applyGbaLayout() }
}
MENU_ACTION_GBA_RESET_CORE -> resetGBACore()
}
}
@ -533,9 +650,21 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
private fun editControlsPlacement() {
if (emulationFragment!!.isConfiguringControls) {
emulationFragment?.stopConfiguringControls()
setGbaViewsTouchable(true)
if (!isGbaLocked) {
gbaViews.forEachIndexed { _, view ->
val slot = view.gbaSlot
val slotPrefs = getSharedPreferences(
"gba_overlay_${'$'}slot",
Context.MODE_PRIVATE
)
attachGbaTouchListener(view, slot, slotPrefs)
}
}
} else {
closeSubmenu()
closeMenu()
setGbaViewsTouchable(false)
emulationFragment?.startConfiguringControls()
}
}
@ -571,12 +700,14 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
emulationFragment?.refreshInputOverlay()
}
}
InputOverlay.OVERLAY_WIIMOTE_CLASSIC -> {
val wiiClassicLatchingButtons = BooleanArray(11)
val classicSettingBase = "MAIN_BUTTON_LATCHING_CLASSIC_"
for (i in wiiClassicLatchingButtons.indices) {
wiiClassicLatchingButtons[i] = BooleanSetting.valueOf(classicSettingBase + i).boolean
wiiClassicLatchingButtons[i] =
BooleanSetting.valueOf(classicSettingBase + i).boolean
}
builder.setMultiChoiceItems(
R.array.classicLatchableButtons, wiiClassicLatchingButtons
@ -586,6 +717,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
emulationFragment?.refreshInputOverlay()
}
}
InputOverlay.OVERLAY_WIIMOTE_NUNCHUK -> {
val nunchukLatchingButtons = BooleanArray(9)
val nunchukSettingBase = "MAIN_BUTTON_LATCHING_WII_"
@ -603,21 +735,27 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
builder.setMultiChoiceItems(
R.array.nunchukLatchableButtons, nunchukLatchingButtons
) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean ->
BooleanSetting.valueOf(nunchukSettingBase + translateToSettingsIndex(indexSelected))
BooleanSetting.valueOf(
nunchukSettingBase + translateToSettingsIndex(
indexSelected
)
)
.setBoolean(settings, isChecked)
emulationFragment?.refreshInputOverlay()
}
}
else -> {
val wiimoteLatchingButtons = BooleanArray(7)
val wiimoteSettingBase = "MAIN_BUTTON_LATCHING_WII_"
for (i in wiimoteLatchingButtons.indices) {
wiimoteLatchingButtons[i] = BooleanSetting.valueOf(wiimoteSettingBase + i).boolean
wiimoteLatchingButtons[i] =
BooleanSetting.valueOf(wiimoteSettingBase + i).boolean
}
builder.setMultiChoiceItems(
R.array.wiimoteLatchableButtons, wiimoteLatchingButtons
R.array.wiimoteLatchableButtons, wiimoteLatchingButtons
) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean ->
BooleanSetting.valueOf(wiimoteSettingBase + indexSelected)
.setBoolean(settings, isChecked)
@ -932,7 +1070,9 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
when (position) {
0 -> infinityFigures[position].label = getString(R.string.infinity_hexagon_label)
1 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_two_label)
2 -> infinityFigures[position].label = getString(R.string.infinity_power_hex_three_label)
2 -> infinityFigures[position].label =
getString(R.string.infinity_power_hex_three_label)
3 -> infinityFigures[position].label = getString(R.string.infinity_p1_label)
4 -> infinityFigures[position].label = getString(R.string.infinity_p1a1_label)
5 -> infinityFigures[position].label = getString(R.string.infinity_p1a2_label)
@ -975,6 +1115,20 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
if (anyMenuClosed)
return true
}
// gbas were not dragable this fixes it, but once touched they appear over the menus
if (!isGbaLocked && gbaViews.isNotEmpty()) {
val loc = IntArray(2)
for (gbaView in gbaViews) {
gbaView.getLocationOnScreen(loc)
val bounds = android.graphics.Rect(
loc[0], loc[1],
loc[0] + gbaView.width, loc[1] + gbaView.height
)
if (bounds.contains(event.rawX.toInt(), event.rawY.toInt())) {
return gbaView.dispatchTouchEvent(event)
}
}
}
return super.dispatchTouchEvent(event)
}
@ -1016,6 +1170,249 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
this.themeId = themeId
}
//gba touch for scale/drag listener while in unlocked gba mode.
private fun attachGbaTouchListener(
view: GBAOverlayView,
slot: Int,
slotPrefs: android.content.SharedPreferences
) {
var dragX = 0f
var dragY = 0f
// Initial dimensions for scaling math
val params = view.layoutParams as FrameLayout.LayoutParams
var cw = params.width.toFloat()
var ch = params.height.toFloat()
val scaleDetector = ScaleGestureDetector(
this,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(d: ScaleGestureDetector): Boolean {
if (isGbaLocked) return true
val sf = d.scaleFactor
val ow = cw
val oh = ch
// Scale width and maintain 3:2 aspect ratio
cw = (cw * sf).coerceIn(120f, 960f)
ch = cw * (2f / 3f)
// Center the scaling transformation
view.x += (ow - cw) / 2f
view.y += (oh - ch) / 2f
val p = view.layoutParams as FrameLayout.LayoutParams
p.width = cw.toInt()
p.height = ch.toInt()
view.layoutParams = p
slotPrefs.edit()
.putFloat("gba_width", cw)
.putFloat("gba_height", ch)
.putFloat("gba_x", view.x)
.putFloat("gba_y", view.y)
.apply()
return true
}
})
view.setOnTouchListener { v, event ->
// Prevent interaction if Snap Mode (Locked) is active
if (isGbaLocked) return@setOnTouchListener false
// Let the scale detector handle pinch gestures first
scaleDetector.onTouchEvent(event)
// If the user is currently pinching/scaling, stop the dragging logic
if (scaleDetector.isInProgress) return@setOnTouchListener true
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
// Store the offset so the window doesn't "jump" to the finger center
dragX = event.rawX - v.x
dragY = event.rawY - v.y
// Bring the window to the top layer
v.bringToFront()
}
MotionEvent.ACTION_MOVE -> {
// Update the view's position as the finger moves
v.x = event.rawX - dragX
v.y = event.rawY - dragY
}
MotionEvent.ACTION_UP -> {
// Double tap detection for visibility toggle
val now = System.currentTimeMillis()
val last = lastGbaTapTimes[slot] ?: 0L
if (now - last < 300) {
view.onDoubleTap()
}
lastGbaTapTimes[slot] = now
slotPrefs.edit()
.putFloat("gba_x", v.x)
.putFloat("gba_y", v.y)
.apply()
}
}
true
}
}
//Android Gba layout
private fun applyGbaLayout() {
if (gbaViews.isEmpty())
return
val tw = binding.root.width
val th = binding.root.height
val count = gbaViews.size
val isLandscape =
resources.configuration.orientation == android.content.res.Configuration.ORIENTATION_LANDSCAPE
binding.frameEmulationFragment.x = 0f
binding.frameEmulationFragment.layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT
binding.frameEmulationFragment.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
binding.frameEmulationFragment.requestLayout()
if (isGbaLocked) {
if (isLandscape) {
val slotH = th / count
val maxW = (tw * 0.45f).toInt()
// Set point for landscape height
val maxH = 300
gbaViews.forEachIndexed { i, v ->
v.setOnTouchListener(null)
val p = v.layoutParams as FrameLayout.LayoutParams
// Calculate height first, then width based on 3:2 ratio
var targetH = slotH.coerceAtMost(maxH)
var targetW = (targetH * 3f / 2f).toInt()
// If it's too wide for the sidebar, scale down based on width
if (targetW > maxW) {
targetW = maxW
targetH = (targetW * 2f / 3f).toInt()
}
p.width = targetW
p.height = targetH
v.layoutParams = p
// Re-snapping: Force X to 0 and calculate centered Y within the slot
v.x = 0f
v.y = (i * slotH).toFloat() + (slotH - targetH) / 2f
v.visibility = android.view.View.VISIBLE
}
} else {
// Portrait Logic
val gih = (tw * 3f / 4f).toInt()
val topBar = (th - gih) / 2
val gbaY = topBar + gih
val availH = th - gbaY
// Set point for portrait height
val maxH = 400
when (count) {
1 -> {
val targetH = (tw * 2f / 3f).toInt().coerceAtMost(availH).coerceAtMost(maxH)
val targetW = (targetH * 3f / 2f).toInt()
with(gbaViews[0]) {
setOnTouchListener(null)
val p = layoutParams as FrameLayout.LayoutParams
p.width = targetW; p.height = targetH; layoutParams = p
x = (tw - targetW) / 2f
y = gbaY.toFloat() + (availH - targetH) / 2f
visibility = android.view.View.VISIBLE
}
}
else -> {
// Multi-screen grid (2, 3, or 4)
val cols = if (count <= 2) count else 2
val rows = if (count <= 2) 1 else 2
val slotW = tw / cols
val slotH = availH / rows
val targetH =
(slotW * 2f / 3f).toInt().coerceAtMost(slotH).coerceAtMost(maxH)
val targetW = (targetH * 3f / 2f).toInt()
gbaViews.forEachIndexed { i, v ->
v.setOnTouchListener(null)
val p = v.layoutParams as FrameLayout.LayoutParams
p.width = targetW; p.height = targetH; v.layoutParams = p
val col = i % cols
val row = i / cols
v.x = (col * slotW).toFloat() + (slotW - targetW) / 2f
v.y = gbaY.toFloat() + (row * slotH).toFloat() + (slotH - targetH) / 2f
v.visibility = android.view.View.VISIBLE
}
}
}
}
} else {
gbaViews.forEachIndexed { i, view ->
val slot = view.gbaSlot
val sp2 = getSharedPreferences("gba_overlay_${slot}", Context.MODE_PRIVATE)
val sw = sp2.getFloat("gba_width", 480f).coerceIn(120f, 960f)
val sh = sp2.getFloat("gba_height", 320f).coerceIn(80f, 640f)
val screenW = resources.displayMetrics.widthPixels.toFloat()
val screenH = resources.displayMetrics.heightPixels.toFloat()
var sx = sp2.getFloat("gba_x", 16f + i * 20f)
var sy = sp2.getFloat("gba_y", screenH - sh - 16f - i * 20f)
if (sx < 0 || sx > screenW) sx = 16f + i * 20f
if (sy < 0 || sy > screenH) sy = screenH - sh - 16f
val p = view.layoutParams as FrameLayout.LayoutParams
p.width = sw.toInt(); p.height = sh.toInt(); view.layoutParams = p
view.x = sx; view.y = sy
attachGbaTouchListener(view, slot, sp2)
}
}
getSharedPreferences("gba_overlay", Context.MODE_PRIVATE).edit()
.putBoolean("gba_locked", isGbaLocked).apply()
}
private fun toggleGBASnap() {
isGbaLocked = !isGbaLocked
if (!isGbaLocked) {
NativeLibrary.SetObscuredPixelsLeft(0)
}
binding.root.post { applyGbaLayout() }
}
private fun resetGBAScreens() {
if (gbaViews.isEmpty()) return
runOnUiThread {
val screenH = resources.displayMetrics.heightPixels.toFloat()
gbaViews.forEachIndexed { i, view ->
val slot = view.gbaSlot
val dx = 16f + i * 20f
val dy = screenH - 320f - 16f - i * 20f
getSharedPreferences("gba_overlay_${slot}", Context.MODE_PRIVATE).edit()
.putFloat("gba_x", dx).putFloat("gba_y", dy).putFloat("gba_width", 480f)
.putFloat("gba_height", 320f).apply()
val p = view.layoutParams as? FrameLayout.LayoutParams ?: return@forEachIndexed
p.width = 480; p.height = 320; view.layoutParams = p
view.x = dx; view.y = dy
}
}
}
private fun resetGBACore() {
for (slot in 0 until 4) {
if (IntSetting.getSettingForSIDevice(slot).int == 13)
NativeLibrary.resetGBACore(slot)
}
}
companion object {
private const val BACKSTACK_NAME_MENU = "menu"
private const val BACKSTACK_NAME_SUBMENU = "submenu"
@ -1077,6 +1474,9 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
const val MENU_ACTION_SKYLANDERS = 36
const val MENU_ACTION_INFINITY_BASE = 37
const val MENU_ACTION_LATCHING_CONTROLS = 38
const val MENU_ACTION_GBA_SNAP = 39
const val MENU_ACTION_GBA_RESET = 40
const val MENU_ACTION_GBA_RESET_CORE = 41
init {
buttonsActionsMap.apply {
@ -1090,19 +1490,38 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
append(R.id.menu_emulation_ir_recenter, MENU_SET_IR_RECENTER)
append(R.id.menu_emulation_set_ir_mode, MENU_SET_IR_MODE)
append(R.id.menu_emulation_choose_doubletap, MENU_ACTION_CHOOSE_DOUBLETAP)
append(R.id.menu_emulation_gba_snap, MENU_ACTION_GBA_SNAP)
append(R.id.menu_emulation_gba_reset, MENU_ACTION_GBA_RESET)
append(R.id.menu_emulation_gba_reset_core, MENU_ACTION_GBA_RESET_CORE)
}
}
@JvmStatic
fun launch(activity: FragmentActivity, filePaths: Array<String>, riivolution: Boolean, fromIntent: Boolean = false) {
fun launch(
activity: FragmentActivity,
filePaths: Array<String>,
riivolution: Boolean,
fromIntent: Boolean = false
) {
if (ignoreLaunchRequests)
return
performLaunchChecks(activity, fromIntent) { launchWithoutChecks(activity, filePaths, riivolution) }
performLaunchChecks(activity, fromIntent) {
launchWithoutChecks(
activity,
filePaths,
riivolution
)
}
}
@JvmStatic
fun launch(activity: FragmentActivity, filePath: String, riivolution: Boolean, fromIntent: Boolean = false) =
fun launch(
activity: FragmentActivity,
filePath: String,
riivolution: Boolean,
fromIntent: Boolean = false
) =
launch(activity, arrayOf(filePath), riivolution, fromIntent)
private fun launchWithoutChecks(
@ -1117,7 +1536,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
activity.startActivity(launcher)
}
private fun performLaunchChecks(activity: FragmentActivity, fromIntent: Boolean, continueCallback: Runnable) {
private fun performLaunchChecks(
activity: FragmentActivity,
fromIntent: Boolean,
continueCallback: Runnable
) {
AfterDirectoryInitializationRunner().runWithLifecycle(activity) {
if (fromIntent) {
activity.finish()

View File

@ -0,0 +1,80 @@
package org.dolphinemu.dolphinemu.features.gba
import android.content.Context
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.graphics.*
//Passive surfaceview displaying one gba screen
//Rendering is driven by GbaRenderManager
class GBAOverlayView(context: Context) : SurfaceView(context),
SurfaceHolder.Callback {
var renderManager: GbaRenderManager? = null
var gbaSlot: Int = 0
var isScreenVisible = true
var needsBorderRedraw = false
var surfaceReady = false
private val paint = Paint().apply { isFilterBitmap = true }
private val destRect = Rect()
private val borderPaint = Paint().apply {
color = Color.argb(120, 255, 255, 255); style = Paint.Style.STROKE
strokeWidth = 2f; isAntiAlias = true
}
private val borderFillPaint = Paint().apply {
color = Color.argb(0, 0, 0, 0); style = Paint.Style.FILL
}
private val borderTextPaint = Paint().apply {
color = Color.argb(120, 255, 255, 255); textSize = 20f
isAntiAlias = true; typeface = Typeface.DEFAULT_BOLD
textAlign = Paint.Align.CENTER
}
init {
holder.setFormat(PixelFormat.TRANSLUCENT)
setZOrderMediaOverlay(true)
holder.addCallback(this)
}
fun drawFrame(bitmap: Bitmap) {
if (!holder.surface.isValid) return
if (!isScreenVisible) {
if (needsBorderRedraw) {
needsBorderRedraw = false
val canvas = holder.lockCanvas() ?: return
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
val rect = RectF(2f, 2f, width - 2f, height - 2f)
canvas.drawRoundRect(rect, 12f, 12f, borderFillPaint)
canvas.drawRoundRect(rect, 12f, 12f, borderPaint)
canvas.drawText("GBA", width / 2f, height / 2f + 8f, borderTextPaint)
holder.unlockCanvasAndPost(canvas)
}
return
}
val canvas = holder.lockCanvas() ?: return
destRect.set(0, 0, width, height)
canvas.drawColor(Color.BLACK)
canvas.drawBitmap(bitmap, null, destRect, paint)
holder.unlockCanvasAndPost(canvas)
}
fun onDoubleTap() {
isScreenVisible = !isScreenVisible
if (!isScreenVisible) needsBorderRedraw = true
}
override fun surfaceCreated(h: SurfaceHolder) {
surfaceReady = false
}
override fun surfaceChanged(h: SurfaceHolder, f: Int, w: Int, h2: Int) {
surfaceReady = true
post { renderManager?.requestRedraw(gbaSlot) }
}
override fun surfaceDestroyed(h: SurfaceHolder) {
surfaceReady = false
}
}

View File

@ -0,0 +1,70 @@
package org.dolphinemu.dolphinemu.features.gba
import android.graphics.Bitmap
import android.os.Handler
import android.os.HandlerThread
import org.dolphinemu.dolphinemu.NativeLibrary
import java.nio.ByteBuffer
object GbaRenderManager {
private val buffers = Array(4) { ByteBuffer.allocateDirect(240 * 160 * 4) }
private val bitmaps = Array(4) { Bitmap.createBitmap(240, 160, Bitmap.Config.ARGB_8888) }
private val renderThread = HandlerThread("GBA_RENDER").apply { start() }
private val handler = Handler(renderThread.looper)
@Volatile
private var activeViews: List<GBAOverlayView> = emptyList()
@Volatile
private var attached = false
fun isAttached() = attached
fun onFrame(slot: Int) {
if (!attached || slot < 0 || slot >= 4) return
handler.post { renderFrame(slot, forceRedraw = false) }
}
private fun renderFrame(slot: Int, forceRedraw: Boolean = false) {
val view = activeViews.firstOrNull { it.gbaSlot == slot } ?: return
if (!view.holder.surface.isValid) return
val buffer = buffers[slot]
buffer.rewind()
if (NativeLibrary.copyGBAFramebuffer(slot, buffer)) {
buffer.rewind()
bitmaps[slot].copyPixelsFromBuffer(buffer)
view.drawFrame(bitmaps[slot])
} else if (forceRedraw) {
view.drawFrame(bitmaps[slot])
}
}
fun requestRedraw(slot: Int) {
if (!attached || slot < 0 || slot >= 4) return
handler.post { renderFrame(slot, forceRedraw = true) }
}
fun attach(views: List<GBAOverlayView>) {
handler.removeCallbacksAndMessages(null)
attached = true
activeViews = views
views.forEach {
it.renderManager = this
if (it.surfaceReady) requestRedraw(it.gbaSlot)
}
}
fun updateViews(views: List<GBAOverlayView>) {
activeViews = views
views.forEach { it.renderManager = this }
views.forEach { if (it.surfaceReady) requestRedraw(it.gbaSlot) }
}
fun detach() {
handler.removeCallbacksAndMessages(null)
//renderThread.quitSafely()
activeViews = emptyList()
attached = false
}
}

View File

@ -11,6 +11,10 @@ object InputOverrider {
external fun unregisterWii(controllerIndex: Int)
external fun registerGBA(controllerIndex: Int)
external fun unregisterGBA(controllerIndex: Int)
external fun setControlState(controllerIndex: Int, control: Int, state: Double)
external fun clearControlState(controllerIndex: Int, control: Int)

View File

@ -15,7 +15,12 @@ enum class BooleanSetting(
MAIN_DSP_HLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "DSPHLE", true),
MAIN_FASTMEM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Fastmem", true),
MAIN_FASTMEM_ARENA(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "FastmemArena", true),
MAIN_LARGE_ENTRY_POINTS_MAP(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "LargeEntryPointsMap", true),
MAIN_LARGE_ENTRY_POINTS_MAP(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,
"LargeEntryPointsMap",
true
),
MAIN_CPU_THREAD(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "CPUThread", true),
MAIN_SYNC_ON_SKIP_IDLE(
Settings.FILE_DOLPHIN,
@ -31,7 +36,12 @@ enum class BooleanSetting(
false
),
MAIN_AUDIO_FILL_GAPS(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "AudioFillGaps", true),
MAIN_AUDIO_PRESERVE_PITCH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "AudioPreservePitch", false),
MAIN_AUDIO_PRESERVE_PITCH(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,
"AudioPreservePitch",
false
),
MAIN_BBA_XLINK_CHAT_OSD(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,
@ -137,7 +147,12 @@ enum class BooleanSetting(
"EnableSaveStates",
false
),
MAIN_WII_WIILINK_ENABLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "EnableWiiLink", false),
MAIN_WII_WIILINK_ENABLE(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,
"EnableWiiLink",
false
),
MAIN_DSP_JIT(Settings.FILE_DOLPHIN, Settings.SECTION_INI_DSP, "EnableJIT", true),
MAIN_TIME_TRACKING(
Settings.FILE_DOLPHIN,

View File

@ -72,6 +72,10 @@ enum class StringSetting(
MAIN_GBA_BIOS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "BIOS", ""),
MAIN_GB_PLAYER_ROM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "GBPlayerRom", ""),
MAIN_GBA_SAVES_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "SavesPath", ""),
MAIN_GBA_ROM_PATH_1(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom1", ""),
MAIN_GBA_ROM_PATH_2(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom2", ""),
MAIN_GBA_ROM_PATH_3(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom3", ""),
MAIN_GBA_ROM_PATH_4(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GBA, "Rom4", ""),
MAIN_TRIFORCE_IP_REDIRECTIONS(
Settings.FILE_DOLPHIN,
Settings.SECTION_INI_CORE,
@ -137,6 +141,14 @@ enum class StringSetting(
MAIN_GFX_BACKEND
)
fun getGBARomPath(slot: Int): StringSetting = when (slot) {
0 -> MAIN_GBA_ROM_PATH_1
1 -> MAIN_GBA_ROM_PATH_2
2 -> MAIN_GBA_ROM_PATH_3
3 -> MAIN_GBA_ROM_PATH_4
else -> MAIN_GBA_ROM_PATH_1
}
private val NOT_RUNTIME_EDITABLE: Set<StringSetting> =
HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY))
}

View File

@ -71,47 +71,58 @@ class SettingsAdapter(
ListItemHeaderBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_SWITCH -> SwitchSettingViewHolder(
ListItemSettingSwitchBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_STRING_SINGLE_CHOICE,
SettingsItem.TYPE_SINGLE_CHOICE_DYNAMIC_DESCRIPTIONS,
SettingsItem.TYPE_SINGLE_CHOICE -> SingleChoiceViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_SLIDER -> SliderViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false),
this,
context
)
SettingsItem.TYPE_SUBMENU -> SubmenuViewHolder(
ListItemSubmenuBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_INPUT_MAPPING_CONTROL -> InputMappingControlSettingViewHolder(
ListItemMappingBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_FILE_PICKER,
SettingsItem.TYPE_DIRECTORY_PICKER -> FilePickerViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false),
this
)
SettingsItem.TYPE_RUN_RUNNABLE -> RunRunnableViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false),
this, context
)
SettingsItem.TYPE_STRING -> InputStringSettingViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false), this
)
SettingsItem.TYPE_HYPERLINK_HEADER -> HeaderHyperLinkViewHolder(
ListItemHeaderBinding.inflate(inflater, parent, false), this
)
SettingsItem.TYPE_DATETIME_CHOICE -> DateTimeSettingViewHolder(
ListItemSettingBinding.inflate(inflater, parent, false), this
)
else -> throw IllegalArgumentException("Invalid view type: $viewType")
}
}
@ -251,6 +262,7 @@ class SettingsAdapter(
slider.valueTo = item.max
slider.stepSize = item.stepSize
}
is IntSliderSetting -> {
slider.valueFrom = item.min.toFloat()
slider.valueTo = item.max.toFloat()
@ -446,6 +458,17 @@ class SettingsAdapter(
fun onFilePickerConfirmation(selectedFile: String) {
val filePicker = clickedItem as FilePicker
if (selectedFile.startsWith("content://")) {
try {
val uri = android.net.Uri.parse(selectedFile)
fragmentActivity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
} catch (e: SecurityException) {
}
}
if (filePicker.getSelectedValue() != selectedFile) {
notifyItemChanged(clickedPosition)
fragmentView.onSettingChanged()
@ -476,6 +499,7 @@ class SettingsAdapter(
closeDialog()
}
is SingleChoiceSettingDynamicDescriptions -> {
val scSetting = clickedItem as SingleChoiceSettingDynamicDescriptions
@ -486,6 +510,7 @@ class SettingsAdapter(
closeDialog()
}
is StringSingleChoiceSetting -> {
val scSetting = clickedItem as StringSingleChoiceSetting
@ -496,6 +521,7 @@ class SettingsAdapter(
closeDialog()
}
is IntSliderSetting -> {
val sliderSetting = clickedItem as IntSliderSetting
if (sliderSetting.selectedValue != seekbarProgress.toInt()) {
@ -504,6 +530,7 @@ class SettingsAdapter(
sliderSetting.setSelectedValue(settings!!, seekbarProgress.toInt())
closeDialog()
}
is FloatSliderSetting -> {
val sliderSetting = clickedItem as FloatSliderSetting

View File

@ -502,16 +502,16 @@ class SettingsFragmentPresenter(
override val isOverridden: Boolean
get() = BooleanSetting.MAIN_DSP_HLE.isOverridden ||
BooleanSetting.MAIN_DSP_JIT.isOverridden
BooleanSetting.MAIN_DSP_JIT.isOverridden
override val isRuntimeEditable: Boolean
get() = BooleanSetting.MAIN_DSP_HLE.isRuntimeEditable &&
BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
BooleanSetting.MAIN_DSP_JIT.isRuntimeEditable
override fun delete(settings: Settings): Boolean {
// Not short circuiting
return BooleanSetting.MAIN_DSP_HLE.delete(settings) and
BooleanSetting.MAIN_DSP_JIT.delete(settings)
BooleanSetting.MAIN_DSP_JIT.delete(settings)
}
}
@ -1002,8 +1002,8 @@ class SettingsFragmentPresenter(
0,
false
) {
fragmentView.showDialogFragment(LoginDialog(this))
loadSettingsList()
fragmentView.showDialogFragment(LoginDialog(this))
loadSettingsList()
})
} else {
sl.add(
@ -1015,8 +1015,8 @@ class SettingsFragmentPresenter(
0,
false
) {
logout()
loadSettingsList()
logout()
loadSettingsList()
})
}
sl.add(
@ -1113,16 +1113,16 @@ class SettingsFragmentPresenter(
override val isOverridden: Boolean
get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isOverridden ||
BooleanSetting.MAIN_SYNC_GPU.isOverridden
BooleanSetting.MAIN_SYNC_GPU.isOverridden
override val isRuntimeEditable: Boolean
get() = BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.isRuntimeEditable &&
BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
BooleanSetting.MAIN_SYNC_GPU.isRuntimeEditable
override fun delete(settings: Settings): Boolean {
// Not short circuiting
return BooleanSetting.MAIN_SYNC_ON_SKIP_IDLE.delete(settings) and
BooleanSetting.MAIN_SYNC_GPU.delete(settings)
BooleanSetting.MAIN_SYNC_GPU.delete(settings)
}
}
@ -2239,7 +2239,7 @@ class SettingsFragmentPresenter(
BooleanSetting.MAIN_DEBUG_JIT_ENABLE_PROFILING,
R.string.debug_jit_enable_block_profiling,
0
)
)
)
sl.add(
RunRunnable(
@ -2405,6 +2405,7 @@ class SettingsFragmentPresenter(
addControllerMappingSettings(sl, gcPad, null)
}
}
7 -> {
// Emulated keyboard controller
val gcKeyboard = EmulatedController.getGcKeyboard(gcPadNumber)
@ -2417,6 +2418,7 @@ class SettingsFragmentPresenter(
addControllerMappingSettings(sl, gcKeyboard, null)
}
}
12 -> {
// Adapter
sl.add(
@ -2436,6 +2438,21 @@ class SettingsFragmentPresenter(
)
)
}
13 -> {
//GBA emulator
sl.add(HeaderSetting(context, R.string.gba_settings, 0))
sl.add(
FilePicker(
context,
StringSetting.getGBARomPath(gcPadNumber),
R.string.gba_rom_path,
R.string.gba_rom_path_description,
fragmentView.activityResultLaunchers.requestGbaRomFile,
null
)
)
}
}
}
@ -2635,11 +2652,11 @@ class SettingsFragmentPresenter(
* @param groupTypeFilter If this is non-null, only groups whose types match this are considered.
*/
private fun addControllerMappingSettings(
sl: ArrayList<SettingsItem>,
controller: EmulatedController,
groupTypeFilter: Set<Int>?
sl: ArrayList<SettingsItem>,
controller: EmulatedController,
groupTypeFilter: Set<Int>?
) {
addContainerMappingSettings(sl, controller, controller, groupTypeFilter)
addContainerMappingSettings(sl, controller, controller, groupTypeFilter)
}
/**
@ -2734,7 +2751,7 @@ class SettingsFragmentPresenter(
val defaultDevice = controller.getDefaultDevice()
hasOldControllerSettings = defaultDevice.startsWith("Android/") &&
defaultDevice.endsWith("/Touchscreen")
defaultDevice.endsWith("/Touchscreen")
fragmentView.setOldControllerSettingsWarningVisibility(hasOldControllerSettings)
}

View File

@ -51,11 +51,17 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
private var isFirstRun = true
private val gcPadRegistered = BooleanArray(4)
private val wiimoteRegistered = BooleanArray(4)
private val gbaRegistered = BooleanArray(4)
private val gbaOverlayButtons: MutableSet<InputOverlayDrawableButton> = HashSet()
private val gbaOverlayDpads: MutableSet<InputOverlayDrawableDpad> = HashSet()
private var gbaControllerIndex = -1
var editMode = false
private var controllerType = -1
private var controllerIndex = 0
private var buttonBeingConfigured: InputOverlayDrawableButton? = null
private var dpadBeingConfigured: InputOverlayDrawableDpad? = null
private var gbaButtonBeingConfigured: InputOverlayDrawableButton? = null
private var gbaDpadBeingConfigured: InputOverlayDrawableDpad? = null
private var joystickBeingConfigured: InputOverlayDrawableJoystick? = null
private val preferences: SharedPreferences
@ -129,6 +135,35 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
for (joystick in overlayJoysticks) {
joystick.draw(canvas)
}
for (button in gbaOverlayButtons) {
button.draw(canvas)
drawGBABadge(canvas, button.bounds)
}
for (dpad in gbaOverlayDpads) {
dpad.draw(canvas)
drawGBABadge(canvas, dpad.bounds)
}
}
//draws gba badge on controlls for gba controller, to not get confused with the GC pad buttons
private fun drawGBABadge(canvas: Canvas, bounds: android.graphics.Rect) {
val bp = android.graphics.Paint().apply {
isAntiAlias = true; color = android.graphics.Color.argb(200, 98, 0, 238)
style = android.graphics.Paint.Style.FILL
}
val tp = android.graphics.Paint().apply {
isAntiAlias = true; color = android.graphics.Color.WHITE; textSize = 18f
typeface = android.graphics.Typeface.DEFAULT_BOLD
textAlign = android.graphics.Paint.Align.CENTER
}
val r = android.graphics.RectF(
bounds.left.toFloat(), bounds.top.toFloat(),
bounds.left + 36f, bounds.top + 18f
)
canvas.drawRoundRect(r, 6f, 6f, bp)
canvas.drawText("GBA", r.left + 18f, r.top + 14f, tp)
}
override fun onTouch(v: View, event: MotionEvent): Boolean {
@ -138,7 +173,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
val action = event.actionMasked
val firstPointer = action != MotionEvent.ACTION_POINTER_DOWN &&
action != MotionEvent.ACTION_POINTER_UP
action != MotionEvent.ACTION_POINTER_UP
val pointerIndex = if (firstPointer) 0 else event.actionIndex
// Tracks if any button/joystick is pressed down
var pressed = false
@ -157,7 +192,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
button.setPressedState(if (button.latching) !button.getPressedState() else true)
button.trackId = event.getPointerId(pointerIndex)
pressed = true
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
InputOverrider.setControlState(
controllerIndex,
button.control,
if (button.getPressedState()) 1.0 else 0.0
)
val analogControl = getAnalogControlForTrigger(button.control)
if (analogControl >= 0)
@ -175,7 +214,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
if (button.trackId == event.getPointerId(pointerIndex)) {
if (!button.latching)
button.setPressedState(false)
InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0)
InputOverrider.setControlState(
controllerIndex,
button.control,
if (button.getPressedState()) 1.0 else 0.0
)
val analogControl = getAnalogControlForTrigger(button.control)
if (analogControl >= 0)
@ -301,6 +344,118 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
)
}
//gba touch handling
if (gbaControllerIndex >= 0) {
for (button in gbaOverlayButtons) {
when (action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
if (button.bounds.contains(
event.getX(pointerIndex).toInt(),
event.getY(pointerIndex).toInt()
)
) {
button.setPressedState(if (button.latching) !button.getPressedState() else true)
button.trackId = event.getPointerId(pointerIndex)
pressed = true
InputOverrider.setControlState(
gbaControllerIndex,
button.control,
if (button.getPressedState()) 1.0 else 0.0
)
val analog = getAnalogControlForTrigger(button.control)
if (analog >= 0)
InputOverrider.setControlState(gbaControllerIndex, analog, 1.0)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
if (button.trackId == event.getPointerId(pointerIndex)) {
if (!button.latching)
button.setPressedState(false)
InputOverrider.setControlState(
gbaControllerIndex,
button.control,
if (button.getPressedState()) 1.0 else 0.0
)
val analog = getAnalogControlForTrigger(button.control)
if (analog >= 0)
InputOverrider.setControlState(gbaControllerIndex, analog, 0.0)
button.trackId = -1
}
}
}
}
for (dpad in gbaOverlayDpads) {
when (action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
if (dpad.bounds.contains(
event.getX(pointerIndex).toInt(),
event.getY(pointerIndex).toInt()
)
) {
dpad.trackId = event.getPointerId(pointerIndex)
pressed = true
}
}
MotionEvent.ACTION_MOVE -> {
if (dpad.trackId == event.getPointerId(pointerIndex)) {
val up = event.getY(pointerIndex) < dpad.bounds.centerY()
val down = event.getY(pointerIndex) > dpad.bounds.centerY()
val left = event.getX(pointerIndex) < dpad.bounds.centerX()
val right = event.getX(pointerIndex) > dpad.bounds.centerX()
setDpadState(dpad, up, down, left, right)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_UP, if (up) 1.0 else 0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_DOWN, if (down) 1.0 else 0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_LEFT, if (left) 1.0 else 0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_RIGHT, if (right) 1.0 else 0.0
)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
if (dpad.trackId == event.getPointerId(pointerIndex)) {
dpad.trackId = -1
dpad.setState(InputOverlayDrawableDpad.STATE_DEFAULT)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_UP,
0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_DOWN,
0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_LEFT,
0.0
)
InputOverrider.setControlState(
gbaControllerIndex,
ControlId.GCPAD_DPAD_RIGHT,
0.0
)
}
}
}
}
}
invalidate()
return true
@ -425,6 +580,76 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
}
}
}
for (button in gbaOverlayButtons) {
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (gbaButtonBeingConfigured == null &&
button.bounds.contains(fingerPositionX, fingerPositionY)
) {
gbaButtonBeingConfigured = button
gbaButtonBeingConfigured?.onConfigureTouch(event)
}
}
MotionEvent.ACTION_MOVE -> {
if (gbaButtonBeingConfigured != null) {
gbaButtonBeingConfigured?.onConfigureTouch(event)
invalidate()
return true
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
if (gbaButtonBeingConfigured == button) {
saveControlPosition(
gbaButtonBeingConfigured!!.legacyId,
gbaButtonBeingConfigured!!.bounds.left,
gbaButtonBeingConfigured!!.bounds.top,
orientation
)
gbaButtonBeingConfigured = null
}
}
}
}
for (dpad in gbaOverlayDpads) {
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (gbaDpadBeingConfigured == null && dpad.bounds.contains(
fingerPositionX,
fingerPositionY
)
) {
gbaDpadBeingConfigured = dpad
gbaDpadBeingConfigured?.onConfigureTouch(event)
}
}
MotionEvent.ACTION_MOVE -> {
if (gbaDpadBeingConfigured != null) {
gbaDpadBeingConfigured?.onConfigureTouch(event)
invalidate()
return true
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP -> {
if (gbaDpadBeingConfigured == dpad) {
saveControlPosition(
gbaDpadBeingConfigured!!.legacyId,
gbaDpadBeingConfigured!!.bounds.left,
gbaDpadBeingConfigured!!.bounds.top,
orientation
)
gbaDpadBeingConfigured = null
}
}
}
}
return true
}
@ -443,8 +668,16 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
InputOverrider.unregisterWii(i)
}
for (i in gbaRegistered.indices) {
if (gbaRegistered[i]) InputOverrider.unregisterGBA(i)
}
Arrays.fill(gcPadRegistered, false)
Arrays.fill(wiimoteRegistered, false)
Arrays.fill(gbaRegistered, false)
gbaOverlayButtons.clear()
gbaOverlayDpads.clear()
gbaControllerIndex = -1
}
private fun getAnalogControlForTrigger(control: Int): Int = when (control) {
@ -640,6 +873,70 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
}
}
private fun addGBAOverlayControls(orientation: String) {
gbaOverlayButtons.add(
initializeOverlayButton(
context, R.drawable.gcpad_a, R.drawable.gcpad_a_pressed,
ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_A_BUTTON, orientation, false
)
)
gbaOverlayButtons.add(
initializeOverlayButton(
context, R.drawable.gcpad_b, R.drawable.gcpad_b_pressed,
ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_B_BUTTON, orientation, false
)
)
gbaOverlayButtons.add(
initializeOverlayButton(
context,
R.drawable.gcpad_start,
R.drawable.gcpad_start_pressed,
ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_START_BUTTON,
orientation,
false
)
)
gbaOverlayButtons.add(
initializeOverlayButton(
context, R.drawable.gcpad_z, R.drawable.gcpad_z_pressed,
ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_Z_BUTTON, orientation, false
)
)
gbaOverlayButtons.add(
initializeOverlayButton(
context, R.drawable.gcpad_l, R.drawable.gcpad_l_pressed,
ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_L_DIGITAL, orientation, false
)
)
gbaOverlayButtons.add(
initializeOverlayButton(
context, R.drawable.gcpad_r, R.drawable.gcpad_r_pressed,
ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_R_DIGITAL, orientation, false
)
)
gbaOverlayDpads.add(
initializeOverlayDpad(
context, R.drawable.gcwii_dpad, R.drawable.gcwii_dpad_pressed_one_direction,
R.drawable.gcwii_dpad_pressed_two_directions,
ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET,
ControlId.GCPAD_DPAD_UP, ControlId.GCPAD_DPAD_DOWN,
ControlId.GCPAD_DPAD_LEFT, ControlId.GCPAD_DPAD_RIGHT, orientation
)
)
}
private fun addWiimoteOverlayControls(orientation: String) {
if (BooleanSetting.MAIN_BUTTON_TOGGLE_WII_0.boolean) {
overlayButtons.add(
@ -1047,6 +1344,23 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
OVERLAY_NONE -> {}
}
//add GBA controls on top of primary, GC controller always visible
gbaOverlayButtons.clear()
gbaOverlayDpads.clear()
gbaControllerIndex = -1
for (i in 0 until 4) {
if (getSettingForSIDevice(i).int == EMULATED_GBA_CONTROLLER) {
if (gbaControllerIndex < 0) gbaControllerIndex = i
if (!gbaRegistered[i]) {
InputOverrider.registerGBA(i)
gbaRegistered[i] = true
}
}
}
if (gbaControllerIndex >= 0) {
addGBAOverlayControls(orientation)
}
}
isFirstRun = false
@ -1085,6 +1399,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
wiiOnlyPortraitDefaultOverlay()
}
}
if (isLandscape) gbaDefaultOverlay() else gbaPortraitDefaultOverlay()
refreshControls()
}
@ -1401,6 +1716,22 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
) {
wiiClassicPortraitDefaultOverlay()
}
// GBA controls android
if (preferences.getFloat(
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET)
.toString() + "-X", 0f
) == 0f
) {
gbaDefaultOverlay()
}
if (preferences.getFloat(
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET)
.toString() + "-Portrait" + "-X", 0f
) == 0f
) {
gbaPortraitDefaultOverlay()
}
}
if (!preferences.getBoolean("OverlayInitV3", false)) {
@ -2276,6 +2607,112 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
.apply()
}
private fun gbaDefaultOverlay() {
val dm = resources.displayMetrics
var maxX = dm.heightPixels.toFloat()
var maxY = dm.widthPixels.toFloat()
if (maxY > maxX) {
val tmp = maxX;
maxX = maxY;
maxY = tmp
}
preferences.edit()
.putFloat((ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.82f * maxX)
.putFloat((ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.60f * maxY)
.putFloat((ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.73f * maxX)
.putFloat((ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.70f * maxY)
.putFloat((ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.08f * maxX)
.putFloat((ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.25f * maxY)
.putFloat((ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.78f * maxX)
.putFloat((ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.25f * maxY)
.putFloat(
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + "-X",
0.60f * maxX
)
.putFloat(
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + "-Y",
0.80f * maxY
)
.putFloat((ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.45f * maxX)
.putFloat((ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.80f * maxY)
.putFloat((ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + "-X", 0.12f * maxX)
.putFloat((ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + "-Y", 0.55f * maxY)
.apply()
}
private fun gbaPortraitDefaultOverlay() {
val dm = resources.displayMetrics
var maxX = dm.heightPixels.toFloat()
var maxY = dm.widthPixels.toFloat()
if (maxY < maxX) {
val tmp = maxX;
maxX = maxY;
maxY = tmp
}
val portrait = "-Portrait"
preferences.edit()
.putFloat(
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.82f * maxX
)
.putFloat(
(ButtonType.BUTTON_A + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.72f * maxY
)
.putFloat(
(ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.68f * maxX
)
.putFloat(
(ButtonType.BUTTON_B + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.80f * maxY
)
.putFloat(
(ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.04f * maxX
)
.putFloat(
(ButtonType.TRIGGER_L + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.55f * maxY
)
.putFloat(
(ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.78f * maxX
)
.putFloat(
(ButtonType.TRIGGER_R + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.55f * maxY
)
.putFloat(
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.62f * maxX
)
.putFloat(
(ButtonType.BUTTON_START + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.90f * maxY
)
.putFloat(
(ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.42f * maxX
)
.putFloat(
(ButtonType.BUTTON_Z + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.90f * maxY
)
.putFloat(
(ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-X",
0.10f * maxX
)
.putFloat(
(ButtonType.BUTTON_UP + GBA_BUTTON_ID_OFFSET).toString() + portrait + "-Y",
0.72f * maxY
)
.apply()
}
companion object {
const val OVERLAY_GAMECUBE = 0
const val OVERLAY_WIIMOTE = 1
@ -2287,6 +2724,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
private const val EMULATED_GAMECUBE_CONTROLLER = 6
private const val EMULATED_AM_BASEBOARD = 11
private const val GAMECUBE_ADAPTER = 12
private const val EMULATED_GBA_CONTROLLER = 13
//avoid ID collision with GC buttons
private const val GBA_BUTTON_ID_OFFSET = 1000
// Buttons that have special positions in Wiimote only
private val WIIMOTE_H_BUTTONS = ArrayList<Int>()

View File

@ -29,8 +29,8 @@ import java.util.Set;
public final class FileBrowserHelper
{
public static final HashSet<String> GAME_EXTENSIONS = new HashSet<>(Arrays.asList(
"gcm", "tgc", "bin", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "nfs", "wad", "dol",
"elf", "json"));
"gcm", "tgc", "bin", "iso", "ciso", "gcz", "wbfs", "wia", "rvz", "nfs", "wad", "dol",
"elf", "json"));
public static final HashSet<String> GAME_LIKE_EXTENSIONS = new HashSet<>(GAME_EXTENSIONS);
@ -40,19 +40,19 @@ public final class FileBrowserHelper
}
public static final HashSet<String> GBA_ROM_EXTENSIONS = new HashSet<>(Arrays.asList(
"gba", "gbc", "gb", "agb", "mb", "rom", "bin"));
"gba", "gbc", "gb", "agb", "mb", "rom", "bin"));
public static final HashSet<String> BIN_EXTENSION = new HashSet<>(Collections.singletonList(
"bin"));
"bin"));
public static final HashSet<String> RAW_EXTENSION = new HashSet<>(Collections.singletonList(
"raw"));
"raw"));
public static final HashSet<String> WAD_EXTENSION = new HashSet<>(Collections.singletonList(
"wad"));
"wad"));
public static Intent createDirectoryPickerIntent(FragmentActivity activity,
HashSet<String> extensions)
HashSet<String> extensions)
{
Intent i = new Intent(activity, CustomFilePickerActivity.class);
@ -60,7 +60,7 @@ public final class FileBrowserHelper
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH,
Environment.getExternalStorageDirectory().getPath());
Environment.getExternalStorageDirectory().getPath());
i.putExtra(CustomFilePickerActivity.EXTRA_EXTENSIONS, extensions);
return i;
@ -98,7 +98,7 @@ public final class FileBrowserHelper
}
public static void runAfterExtensionCheck(Context context, Uri uri, Set<String> validExtensions,
Runnable runnable)
Runnable runnable)
{
String extension = null;
@ -123,18 +123,18 @@ public final class FileBrowserHelper
else
{
int messageId = validExtensions.size() == 1 ?
R.string.wrong_file_extension_single : R.string.wrong_file_extension_multiple;
R.string.wrong_file_extension_single : R.string.wrong_file_extension_multiple;
message = context.getString(messageId, extension,
setToSortedDelimitedString(validExtensions));
setToSortedDelimitedString(validExtensions));
}
new MaterialAlertDialogBuilder(context)
.setMessage(message)
.setPositiveButton(R.string.yes, (dialogInterface, i) -> runnable.run())
.setNegativeButton(R.string.no, null)
.setCancelable(false)
.show();
.setMessage(message)
.setPositiveButton(R.string.yes, (dialogInterface, i) -> runnable.run())
.setNegativeButton(R.string.no, null)
.setCancelable(false)
.show();
}
@Nullable

View File

@ -11,7 +11,9 @@
<SurfaceView
android:id="@+id/surface_emulation"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="0dp"
android:focusable="false"
android:focusableInTouchMode="false" />

View File

@ -30,4 +30,15 @@
<item
android:id="@+id/menu_emulation_reset_overlay"
android:title="@string/emulation_touch_overlay_reset"/>
<item android:id="@+id/menu_emulation_gba_snap"
android:checkable="true"
android:title="Snap Gba Layout"/>
<item android:id="@+id/menu_emulation_gba_reset"
android:title="Reset Gba Screens"/>
<item android:id="@+id/menu_emulation_gba_reset_core"
android:title="Reset Gba"/>
</menu>

View File

@ -2,78 +2,37 @@
<!-- All lists for ListPreference keys/values are placed here -->
<resources>
<string-array name="regionEntries">
<item>NTSC-J</item>
<item>NTSC-U</item>
<item>PAL</item>
<item>NTSC-K</item>
</string-array>
<integer-array name="regionValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<!-- UI CPU Core selection -->
<string-array name="emuCoresEntriesX86_64">
<item>@string/jit_recompiler_x86</item>
<item>@string/cached_interpreter_slower</item>
<item>@string/interpreter_slowest</item>
</string-array>
<integer-array name="emuCoresValuesX86_64">
<item>1</item>
<item>5</item>
<item>0</item>
</integer-array>
<string-array name="emuCoresEntriesARM64">
<item>@string/jit_recompiler_arm64</item>
<item>@string/cached_interpreter_slower</item>
<item>@string/interpreter_slowest</item>
</string-array>
<!-- UI CPU Core selection -->
<integer-array name="emuCoresValuesARM64">
<item>4</item>
<item>5</item>
<item>0</item>
</integer-array>
<string-array name="emuCoresEntriesGeneric">
<item>@string/cached_interpreter</item>
<item>@string/interpreter</item>
</string-array>
<integer-array name="emuCoresValuesGeneric">
<item>5</item>
<item>0</item>
</integer-array>
<!-- DSP Emulation Engine -->
<string-array name="dspEngineEntriesX86_64">
<item>@string/dsp_hle</item>
<item>@string/dsp_lle_recompiler</item>
<item>@string/dsp_lle_interpreter</item>
</string-array>
<integer-array name="dspEngineValuesX86_64">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="dspEngineEntriesGeneric">
<item>@string/dsp_hle</item>
<item>@string/dsp_lle_interpreter</item>
</string-array>
<integer-array name="dspEngineValuesGeneric">
<item>0</item>
<item>2</item>
</integer-array>
<!-- GameCube System Languages -->
<string-array name="gameCubeSystemLanguageEntries">
<item>@string/language_english</item>
<item>@string/language_german</item>
<item>@string/language_french</item>
<item>@string/language_spanish</item>
<item>@string/language_italian</item>
<item>@string/language_dutch</item>
</string-array>
<integer-array name="gameCubeSystemLanguageValues">
<item>0</item>
<item>1</item>
@ -82,14 +41,6 @@
<item>4</item>
<item>5</item>
</integer-array>
<!-- Slot A & B Device selection -->
<string-array name="slotDeviceEntries">
<item>@string/device_nothing</item>
<item>@string/device_dummy</item>
<item>@string/device_memory_card</item>
<item>@string/device_gci_folder</item>
</string-array>
<integer-array name="slotDeviceValues">
<item>255</item>
<item>0</item>
@ -97,16 +48,7 @@
<item>8</item>
</integer-array>
<!-- Slot SP1 Device selection -->
<string-array name="serialPort1DeviceEntries">
<item>@string/device_nothing</item>
<item>@string/device_dummy</item>
<item>@string/broadband_adapter_xlink</item>
<item>@string/broadband_adapter_hle</item>
<item>@string/broadband_adapter_tapserver</item>
<item>@string/modem_adapter_tapserver</item>
<item>@string/sp1_am_baseboard</item>
</string-array>
<!-- DSP Emulation Engine -->
<integer-array name="serialPort1DeviceValues">
<item>255</item>
<item>0</item>
@ -116,20 +58,6 @@
<item>13</item>
<item>6</item>
</integer-array>
<!-- Wii System Languages -->
<string-array name="wiiSystemLanguageEntries">
<item>@string/language_japanese</item>
<item>@string/language_english</item>
<item>@string/language_german</item>
<item>@string/language_french</item>
<item>@string/language_spanish</item>
<item>@string/language_italian</item>
<item>@string/language_dutch</item>
<item>@string/language_simplified_chinese</item>
<item>@string/language_traditional_chinese</item>
<item>@string/language_korean</item>
</string-array>
<integer-array name="wiiSystemLanguageValues">
<item>0</item>
<item>1</item>
@ -142,49 +70,23 @@
<item>8</item>
<item>9</item>
</integer-array>
<!-- Sound Mode -->
<string-array name="soundModeEntries">
<item>@string/sound_mode_mono</item>
<item>@string/sound_mode_stereo</item>
<item>@string/sound_mode_surround</item>
</string-array>
<integer-array name="soundModeValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<!-- Sensor Bar Position -->
<string-array name="sensorBarPositionEntries">
<item>@string/sensor_position_top</item>
<item>@string/sensor_position_bottom</item>
</string-array>
<integer-array name="sensorBarPositionValues">
<item>1</item>
<item>0</item>
</integer-array>
<!-- Log Verbosity selection based on LogLevel in Common/Logging/Log.h -->
<string-array name="logVerbosityEntriesMaxLevelInfo">
<item>@string/log_notice</item>
<item>@string/log_error</item>
<item>@string/log_warning</item>
<item>@string/log_info</item>
</string-array>
<!-- GameCube System Languages -->
<integer-array name="logVerbosityValuesMaxLevelInfo">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="logVerbosityEntriesMaxLevelDebug">
<item>@string/log_notice</item>
<item>@string/log_error</item>
<item>@string/log_warning</item>
<item>@string/log_info</item>
<item>@string/log_debug</item>
</string-array>
<integer-array name="logVerbosityValuesMaxLevelDebug">
<item>1</item>
<item>2</item>
@ -193,29 +95,7 @@
<item>5</item>
</integer-array>
<!-- Video backend selection -->
<string-array name="videoBackendEntries">
<item>@string/backend_opengl</item>
<item>@string/backend_vulkan</item>
<item>@string/backend_software</item>
<item>@string/backend_null</item>
</string-array>
<string-array name="videoBackendValues">
<item>OGL</item>
<item>Vulkan</item>
<item>Software Renderer</item>
<item>Null</item>
</string-array>
<!-- Wii Remote extensions -->
<string-array name="wiimoteExtensionsEntries">
<item>@string/extension_none</item>
<item>@string/extension_nunchuk</item>
<item>@string/extension_classic</item>
<item>@string/extension_guitar</item>
<item>@string/extension_drums</item>
<item>@string/extension_turntable</item>
</string-array>
<!-- Slot A & B Device selection -->
<integer-array name="wiimoteExtensionsValues">
<item>0</item>
<item>1</item>
@ -224,40 +104,19 @@
<item>4</item>
<item>5</item>
</integer-array>
<!-- Texture Cache Accuracy Preference -->
<string-array name="textureCacheAccuracyEntries">
<item>@string/accuracy_fast</item>
<item>@string/accuracy_medium</item>
<item>@string/accuracy_safe</item>
</string-array>
<integer-array name="textureCacheAccuracyValues">
<item>128</item>
<item>512</item>
<item>0</item>
</integer-array>
<!-- Shader Compilation Mode Preference -->
<string-array name="shaderCompilationModeEntries">
<item>@string/shader_compilation_specialized</item>
<item>@string/shader_compilation_exclusive_ubershaders</item>
<item>@string/shader_compilation_hybrid_ubershaders</item>
<item>@string/shader_compilation_skip_drawing</item>
</string-array>
<!-- Slot SP1 Device selection -->
<integer-array name="shaderCompilationModeValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<!-- Shader Compilation Mode Dynamic Descriptions -->
<string-array name="shaderCompilationDescriptionEntries">
<item>@string/shader_compilation_specialized_description</item>
<item>@string/shader_compilation_exclusive_ubershaders_description</item>
<item>@string/shader_compilation_hybrid_ubershaders_description</item>
<item>@string/shader_compilation_skip_drawing_description</item>
</string-array>
<integer-array name="shaderCompilationDescriptionValues">
<item>0</item>
<item>1</item>
@ -265,15 +124,7 @@
<item>3</item>
</integer-array>
<!-- Internal Resolution Preference -->
<string-array name="internalResolutionEntries">
<item>@string/resolution_one_native</item>
<item>@string/resolution_two_native</item>
<item>@string/resolution_three_native</item>
<item>@string/resolution_four_native</item>
<item>@string/resolution_five_native</item>
<item>@string/resolution_six_native</item>
</string-array>
<!-- Wii System Languages -->
<integer-array name="internalResolutionValues">
<item>1</item>
<item>2</item>
@ -282,14 +133,6 @@
<item>5</item>
<item>6</item>
</integer-array>
<!-- FSAA Preference -->
<string-array name="FSAAEntries" translatable="false">
<item>@string/multiple_off</item>
<item>@string/multiple_two</item>
<item>@string/multiple_four</item>
<item>@string/multiple_eight</item>
</string-array>
<integer-array name="FSAAValues" translatable="false">
<item>1</item>
<item>2</item>
@ -297,14 +140,7 @@
<item>8</item>
</integer-array>
<!-- Anisotropic Filtering Preference -->
<string-array name="anisotropicFilteringEntries">
<item>@string/filtering_default</item>
<item>@string/multiple_two</item>
<item>@string/multiple_four</item>
<item>@string/multiple_eight</item>
<item>@string/multiple_sixteen</item>
</string-array>
<!-- Sound Mode -->
<integer-array name="anisotropicFilteringValues">
<item>0</item>
<item>1</item>
@ -312,40 +148,19 @@
<item>3</item>
<item>4</item>
</integer-array>
<!-- Texture Filtering Preference -->
<string-array name="textureFilteringEntries">
<item>@string/filtering_default</item>
<item>@string/filtering_nearest</item>
<item>@string/filtering_linear</item>
</string-array>
<integer-array name="textureFilteringValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<!-- Stereoscopy Preference -->
<string-array name="stereoscopyEntries">
<item>@string/stereoscopy_off</item>
<item>@string/stereoscopy_side_by_side</item>
<item>@string/stereoscopy_top_and_bottom</item>
<item>@string/stereoscopy_anaglyph</item>
</string-array>
<!-- Sensor Bar Position -->
<integer-array name="stereoscopyValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<!-- Aspect Ratio Preference -->
<string-array name="aspectRatioEntries">
<item>@string/aspect_ratio_auto</item>
<item>@string/aspect_ratio_force_sixteen_by_nine</item>
<item>@string/aspect_ratio_force_four_by_three</item>
<item>@string/aspect_ratio_stretch</item>
</string-array>
<integer-array name="aspectRatioValues">
<item>0</item>
<item>1</item>
@ -353,6 +168,329 @@
<item>3</item>
</integer-array>
<!-- Log Verbosity selection based on LogLevel in Common/Logging/Log.h -->
<integer-array name="gcpadTypeValues">
<item>0</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
</integer-array>
<integer-array name="wiimoteTypeValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<integer-array name="orientationValues">
<item>0</item>
<item>8</item>
<item>1</item>
<item>-1</item>
</integer-array>
<integer-array name="themeValues">
<item>0</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<!-- Video backend selection -->
<integer-array name="themeValuesA12">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<integer-array name="themeModeValues">
<item>-1</item>
<item>1</item>
<item>2</item>
</integer-array>
<!-- Wii Remote extensions -->
<integer-array name="synchronizeGpuThreadValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<integer-array name="convertFormatValues">
<item>0</item>
<item>3</item>
<item>7</item>
<item>8</item>
</integer-array>
<!-- Texture Cache Accuracy Preference -->
<integer-array name="convertBlockSizeGczValues">
<item>32768</item>
</integer-array>
<integer-array name="convertBlockSizeWiaValues">
<item>2097152</item>
</integer-array>
<!-- Shader Compilation Mode Preference -->
<integer-array name="convertBlockSizeRvzValues">
<item>32768</item>
<item>65536</item>
<item>131072</item>
<item>262144</item>
<item>524288</item>
<item>1048576</item>
<item>2097152</item>
</integer-array>
<integer-array name="convertCompressionGczValues">
<item>0</item>
</integer-array>
<!-- Shader Compilation Mode Dynamic Descriptions -->
<integer-array name="convertCompressionWiaValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<integer-array name="convertCompressionRvzValues">
<item>0</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</integer-array>
<!-- Internal Resolution Preference -->
<integer-array name="convertCompressionLevelValues">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
</integer-array>
<integer-array name="convertCompressionLevelZstdValues">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
<item>17</item>
<item>18</item>
<item>19</item>
<item>20</item>
<item>21</item>
<item>22</item>
</integer-array>
<!-- FSAA Preference -->
<integer-array name="colorSpaceValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="regionEntries">
<item>NTSC-J</item>
<item>NTSC-U</item>
<item>PAL</item>
<item>NTSC-K</item>
</string-array>
<!-- Anisotropic Filtering Preference -->
<string-array name="emuCoresEntriesX86_64">
<item>@string/jit_recompiler_x86</item>
<item>@string/cached_interpreter_slower</item>
<item>@string/interpreter_slowest</item>
</string-array>
<string-array name="emuCoresEntriesARM64">
<item>@string/jit_recompiler_arm64</item>
<item>@string/cached_interpreter_slower</item>
<item>@string/interpreter_slowest</item>
</string-array>
<!-- Texture Filtering Preference -->
<string-array name="emuCoresEntriesGeneric">
<item>@string/cached_interpreter</item>
<item>@string/interpreter</item>
</string-array>
<string-array name="dspEngineEntriesX86_64">
<item>@string/dsp_hle</item>
<item>@string/dsp_lle_recompiler</item>
<item>@string/dsp_lle_interpreter</item>
</string-array>
<!-- Stereoscopy Preference -->
<string-array name="dspEngineEntriesGeneric">
<item>@string/dsp_hle</item>
<item>@string/dsp_lle_interpreter</item>
</string-array>
<string-array name="gameCubeSystemLanguageEntries">
<item>@string/language_english</item>
<item>@string/language_german</item>
<item>@string/language_french</item>
<item>@string/language_spanish</item>
<item>@string/language_italian</item>
<item>@string/language_dutch</item>
</string-array>
<!-- Aspect Ratio Preference -->
<string-array name="slotDeviceEntries">
<item>@string/device_nothing</item>
<item>@string/device_dummy</item>
<item>@string/device_memory_card</item>
<item>@string/device_gci_folder</item>
</string-array>
<string-array name="serialPort1DeviceEntries">
<item>@string/device_nothing</item>
<item>@string/device_dummy</item>
<item>@string/broadband_adapter_xlink</item>
<item>@string/broadband_adapter_hle</item>
<item>@string/broadband_adapter_tapserver</item>
<item>@string/modem_adapter_tapserver</item>
<item>@string/sp1_am_baseboard</item>
</string-array>
<string-array name="wiiSystemLanguageEntries">
<item>@string/language_japanese</item>
<item>@string/language_english</item>
<item>@string/language_german</item>
<item>@string/language_french</item>
<item>@string/language_spanish</item>
<item>@string/language_italian</item>
<item>@string/language_dutch</item>
<item>@string/language_simplified_chinese</item>
<item>@string/language_traditional_chinese</item>
<item>@string/language_korean</item>
</string-array>
<string-array name="soundModeEntries">
<item>@string/sound_mode_mono</item>
<item>@string/sound_mode_stereo</item>
<item>@string/sound_mode_surround</item>
</string-array>
<string-array name="sensorBarPositionEntries">
<item>@string/sensor_position_top</item>
<item>@string/sensor_position_bottom</item>
</string-array>
<string-array name="logVerbosityEntriesMaxLevelInfo">
<item>@string/log_notice</item>
<item>@string/log_error</item>
<item>@string/log_warning</item>
<item>@string/log_info</item>
</string-array>
<string-array name="logVerbosityEntriesMaxLevelDebug">
<item>@string/log_notice</item>
<item>@string/log_error</item>
<item>@string/log_warning</item>
<item>@string/log_info</item>
<item>@string/log_debug</item>
</string-array>
<string-array name="videoBackendEntries">
<item>@string/backend_opengl</item>
<item>@string/backend_vulkan</item>
<item>@string/backend_software</item>
<item>@string/backend_null</item>
</string-array>
<string-array name="videoBackendValues">
<item>OGL</item>
<item>Vulkan</item>
<item>Software Renderer</item>
<item>Null</item>
</string-array>
<string-array name="wiimoteExtensionsEntries">
<item>@string/extension_none</item>
<item>@string/extension_nunchuk</item>
<item>@string/extension_classic</item>
<item>@string/extension_guitar</item>
<item>@string/extension_drums</item>
<item>@string/extension_turntable</item>
</string-array>
<string-array name="textureCacheAccuracyEntries">
<item>@string/accuracy_fast</item>
<item>@string/accuracy_medium</item>
<item>@string/accuracy_safe</item>
</string-array>
<string-array name="shaderCompilationModeEntries">
<item>@string/shader_compilation_specialized</item>
<item>@string/shader_compilation_exclusive_ubershaders</item>
<item>@string/shader_compilation_hybrid_ubershaders</item>
<item>@string/shader_compilation_skip_drawing</item>
</string-array>
<string-array name="shaderCompilationDescriptionEntries">
<item>@string/shader_compilation_specialized_description</item>
<item>@string/shader_compilation_exclusive_ubershaders_description</item>
<item>@string/shader_compilation_hybrid_ubershaders_description</item>
<item>@string/shader_compilation_skip_drawing_description</item>
</string-array>
<string-array name="internalResolutionEntries">
<item>@string/resolution_one_native</item>
<item>@string/resolution_two_native</item>
<item>@string/resolution_three_native</item>
<item>@string/resolution_four_native</item>
<item>@string/resolution_five_native</item>
<item>@string/resolution_six_native</item>
</string-array>
<string-array name="FSAAEntries" translatable="false">
<item>@string/multiple_off</item>
<item>@string/multiple_two</item>
<item>@string/multiple_four</item>
<item>@string/multiple_eight</item>
</string-array>
<string-array name="anisotropicFilteringEntries">
<item>@string/filtering_default</item>
<item>@string/multiple_two</item>
<item>@string/multiple_four</item>
<item>@string/multiple_eight</item>
<item>@string/multiple_sixteen</item>
</string-array>
<string-array name="textureFilteringEntries">
<item>@string/filtering_default</item>
<item>@string/filtering_nearest</item>
<item>@string/filtering_linear</item>
</string-array>
<string-array name="stereoscopyEntries">
<item>@string/stereoscopy_off</item>
<item>@string/stereoscopy_side_by_side</item>
<item>@string/stereoscopy_top_and_bottom</item>
<item>@string/stereoscopy_anaglyph</item>
</string-array>
<string-array name="aspectRatioEntries">
<item>@string/aspect_ratio_auto</item>
<item>@string/aspect_ratio_force_sixteen_by_nine</item>
<item>@string/aspect_ratio_force_four_by_three</item>
<item>@string/aspect_ratio_stretch</item>
</string-array>
<string-array name="countryNames">
<item>@string/country_europe</item>
<item>@string/country_japan</item>
@ -370,6 +508,7 @@
<item>@string/country_unknown</item>
</string-array>
<!-- Monet must always have a value exclusive to >= API 31 -->
<string-array name="gcpadTypeEntries">
<item>@string/gcpad_disabled</item>
<item>@string/gcpad_emulated</item>
@ -379,29 +518,13 @@
<item>@string/gcpad_taru_konga</item>
<item>@string/gcpad_am_baseboard</item>
<item>@string/gcpad_gc_adapter</item>
<item>GBA</item>
</string-array>
<integer-array name="gcpadTypeValues">
<item>0</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
</integer-array>
<string-array name="wiimoteTypeEntries">
<item>@string/wiimote_disabled</item>
<item>@string/wiimote_emulated</item>
<item>@string/wiimote_real</item>
</string-array>
<integer-array name="wiimoteTypeValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="gcpadButtons">
<item>A</item>
<item>B</item>
@ -415,7 +538,6 @@
<item>@string/gamepad_main_stick</item>
<item>@string/gamepad_c_stick</item>
</string-array>
<string-array name="gcpadLatchableButtons">
<item>A</item>
<item>B</item>
@ -437,7 +559,6 @@
<item>@string/gamepad_home</item>
<item>@string/gamepad_d_pad</item>
</string-array>
<string-array name="wiimoteLatchableButtons">
<item>A</item>
<item>B</item>
@ -461,7 +582,6 @@
<item>Z</item>
<item>@string/gamepad_nunchuk_stick</item>
</string-array>
<string-array name="nunchukLatchableButtons">
<item>A</item>
<item>B</item>
@ -504,7 +624,6 @@
<item>ZL</item>
<item>ZR</item>
</string-array>
<string-array name="irModeEntries">
<item>@string/ir_disabled</item>
<item>@string/ir_follow</item>
@ -516,7 +635,6 @@
<item>@string/double_tap_b</item>
<item>@string/double_tap_2</item>
</string-array>
<string-array name="doubleTapWithClassic">
<item>@string/double_tap_a</item>
<item>@string/double_tap_b</item>
@ -530,26 +648,13 @@
<item>@string/orientation_portrait</item>
<item>@string/orientation_auto</item>
</string-array>
<integer-array name="orientationValues">
<item>0</item>
<item>8</item>
<item>1</item>
<item>-1</item>
</integer-array>
<!-- Monet must always have a value exclusive to >= API 31 -->
<string-array name="themeEntries">
<item>@string/theme_default</item>
<item>@string/theme_material_default</item>
<item>@string/theme_green</item>
<item>@string/theme_pink</item>
</string-array>
<integer-array name="themeValues">
<item>0</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="themeEntriesA12">
<item>@string/theme_default</item>
<item>@string/theme_material_you</item>
@ -557,36 +662,17 @@
<item>@string/theme_green</item>
<item>@string/theme_pink</item>
</string-array>
<integer-array name="themeValuesA12">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="themeModeEntries">
<item>@string/theme_mode_follow_system</item>
<item>@string/theme_mode_light</item>
<item>@string/theme_mode_dark</item>
</string-array>
<integer-array name="themeModeValues">
<item>-1</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="synchronizeGpuThreadEntries">
<item>@string/sync_gpu_never</item>
<item>@string/sync_gpu_idle</item>
<item>@string/sync_gpu_always</item>
</string-array>
<integer-array name="synchronizeGpuThreadValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
<string-array name="motionControlsEntries">
<item>@string/motion_device_with_pointer</item>
<item>@string/motion_device_without_pointer</item>
@ -599,27 +685,13 @@
<item>WIA</item>
<item>RVZ</item>
</string-array>
<integer-array name="convertFormatValues">
<item>0</item>
<item>3</item>
<item>7</item>
<item>8</item>
</integer-array>
<string-array name="convertBlockSizeGczEntries">
<item>@string/block_size_32_kib</item>
</string-array>
<integer-array name="convertBlockSizeGczValues">
<item>32768</item>
</integer-array>
<string-array name="convertBlockSizeWiaEntries">
<item>@string/block_size_2_mib</item>
</string-array>
<integer-array name="convertBlockSizeWiaValues">
<item>2097152</item>
</integer-array>
<string-array name="convertBlockSizeRvzEntries">
<item>@string/block_size_32_kib</item>
<item>@string/block_size_64_kib</item>
@ -629,23 +701,10 @@
<item>@string/block_size_1_mib</item>
<item>@string/block_size_2_mib</item>
</string-array>
<integer-array name="convertBlockSizeRvzValues">
<item>32768</item>
<item>65536</item>
<item>131072</item>
<item>262144</item>
<item>524288</item>
<item>1048576</item>
<item>2097152</item>
</integer-array>
<string-array name="convertCompressionGczEntries">
<item>@string/compression_deflate</item>
</string-array>
<integer-array name="convertCompressionGczValues">
<item>0</item>
</integer-array>
<string-array name="convertCompressionWiaEntries">
<item>@string/compression_none</item>
<item>@string/compression_purge</item>
@ -653,13 +712,6 @@
<item>@string/compression_lzma</item>
<item>@string/compression_lzma2</item>
</string-array>
<integer-array name="convertCompressionWiaValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</integer-array>
<string-array name="convertCompressionRvzEntries">
<item>@string/compression_none</item>
@ -668,15 +720,6 @@
<item>@string/compression_lzma2</item>
<item>@string/compression_zstandard</item>
</string-array>
<integer-array name="convertCompressionRvzValues">
<item>0</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
</integer-array>
<string-array name="convertCompressionLevelEntries">
<item>1</item>
<item>2</item>
@ -688,17 +731,6 @@
<item>8</item>
<item>9</item>
</string-array>
<integer-array name="convertCompressionLevelValues">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
</integer-array>
<string-array name="convertCompressionLevelZstdEntries">
<item>1</item>
@ -724,39 +756,9 @@
<item>21</item>
<item>22</item>
</string-array>
<integer-array name="convertCompressionLevelZstdValues">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
<item>16</item>
<item>17</item>
<item>18</item>
<item>19</item>
<item>20</item>
<item>21</item>
<item>22</item>
</integer-array>
<string-array name="colorSpaceEntries">
<item>@string/ntscm_space</item>
<item>@string/ntscj_space</item>
<item>@string/pal_space</item>
</string-array>
<integer-array name="colorSpaceValues">
<item>0</item>
<item>1</item>
<item>2</item>
</integer-array>
</resources>

View File

@ -89,6 +89,8 @@
<string name="gba_bios_path">BIOS</string>
<string name="gb_player_rom">Game Boy Player ROM</string>
<string name="gba_saves_path">Saves</string>
<string name="gba_rom_path">GBA ROM Path</string>
<string name="gba_rom_path_description">Rom to load for this slot. Long-press to clear</string>
<string name="wii_submenu">Wii</string>
<string name="wii_misc_settings">Misc Settings</string>
<string name="wii_sd_card_settings">SD Card Settings</string>

View File

@ -150,6 +150,11 @@ JNIEnv* GetEnvForThread()
return owned.env;
}
JavaVM* GetJavaVM()
{
return s_java_vm;
}
jclass GetStringClass()
{
return s_string_class;
@ -574,12 +579,12 @@ jmethodID GetAudioUtilsGetFramesPerBuffer()
{
return s_audio_utils_get_frames_per_buffer;
}
} // namespace IDCache
extern "C" {
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
s_java_vm = vm;

View File

@ -124,4 +124,6 @@ jclass GetAudioUtilsClass();
jmethodID GetAudioUtilsGetSampleRate();
jmethodID GetAudioUtilsGetFramesPerBuffer();
JavaVM* GetJavaVM();
} // namespace IDCache

View File

@ -35,6 +35,20 @@ Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterWii
{
ciface::Touch::UnregisterWiiInputOverrider(controller_index);
}
// Android GBA emu.
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_registerGBA(JNIEnv*, jclass,
int controller_index)
{
ciface::Touch::RegisterGBAInputOverrider(controller_index);
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterGBA(
JNIEnv*, jclass, int controller_index)
{
ciface::Touch::UnregisterGBAInputOverrider(controller_index);
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_setControlState(

View File

@ -69,6 +69,14 @@
#include "jni/AndroidCommon/IDCache.h"
#include "jni/Host.h"
#include <shared_mutex>
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#include "Core/HW/SI/SI.h"
#include "Core/HW/SI/SI_Device.h"
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
#endif
namespace
{
constexpr char DOLPHIN_TAG[] = "DolphinEmuNative";
@ -815,4 +823,52 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCurrentTitleDescriptionUnchecked
return ToJString(env, description);
}
// Android Gba Emulation.
JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_copyGBAFramebuffer(
JNIEnv* env, jclass, jint slot, jobject byte_buffer)
{
#ifdef HAS_LIBMGBA
if (slot < 0 || slot >= 4)
return JNI_FALSE;
auto core = Core::System::GetInstance().GetSerialInterface().GetGBACore(slot);
if (!core)
return JNI_FALSE;
std::shared_lock<std::shared_mutex> lock(core->GetVideoBufferMutex());
const auto buffer = core->GetVideoBuffer();
if (buffer.empty())
return JNI_FALSE;
void* dst = env->GetDirectBufferAddress(byte_buffer);
if (!dst)
return JNI_FALSE;
jlong capacity = env->GetDirectBufferCapacity(byte_buffer);
if (static_cast<size_t>(capacity) < buffer.size() * sizeof(u32))
return JNI_FALSE;
memcpy(dst, buffer.data(), buffer.size() * sizeof(u32));
return JNI_TRUE;
#else
return JNI_FALSE;
#endif
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_resetGBACore(JNIEnv*, jclass,
jint slot)
{
#ifdef HAS_LIBMGBA
Core::System::GetInstance().GetSerialInterface().ResetGBACore(slot);
#endif
}
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_getGBAGameTitle(JNIEnv* env,
jclass,
jint slot)
{
return ToJString(env, Core::System::GetInstance().GetSerialInterface().GetGBAGameTitle(slot));
}
JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_getGBAGameCode(JNIEnv* env,
jclass,
jint slot)
{
return ToJString(env, Core::System::GetInstance().GetSerialInterface().GetGBAGameCode(slot));
}
}

View File

@ -38,6 +38,7 @@
#ifdef ANDROID
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#endif
namespace HW::GBA
@ -245,6 +246,8 @@ bool Core::Start(u64 gc_ticks)
mGameInfo info;
m_core->getGameInfo(m_core, &info);
m_game_title = info.title;
// android region code furture use.
m_game_code = std::string(info.code, 4);
m_save_path = NetPlay::IsNetPlayRunning() ? NetPlay::GetGBASavePath(m_device_number) :
GetSavePath(m_rom_path, m_device_number);
@ -295,6 +298,9 @@ void Core::Stop()
m_save_path = {};
m_rom_hash = {};
m_game_title = {};
// android future use, game code match.
m_game_title = {};
m_game_code = {};
}
void Core::Reset()
@ -374,12 +380,18 @@ bool Core::LoadBIOS(const char* bios_path)
bool Core::LoadSave(const char* save_path)
{
VFile* vf = VFileOpen(save_path, O_CREAT | O_RDWR);
std::string path_str = save_path;
if (!vf)
{
PanicAlertFmtT("Error: GBA{0} failed to open the save in {1}", m_device_number + 1, save_path);
return false;
}
#ifdef ANDROID
if (path_str.starts_with("content://"))
vf = VFileFromFD(OpenAndroidContent(save_path, "rw"));
#endif
if (!m_core->loadSave(m_core, vf))
{
PanicAlertFmtT("Error: GBA{0} failed to load the save in {1}", m_device_number + 1, save_path);
@ -445,6 +457,75 @@ void Core::SetAudioBufferSize()
m_core->setAudioBufferSize(m_core, AUDIO_BUFFER_SIZE);
}
// Android GBAcore emu
#ifdef ANDROID
#include <atomic>
namespace
{
static std::atomic<jmethodID> s_on_gba_frame{nullptr};
struct GBAThreadJNI
{
JNIEnv* env = nullptr;
bool attached = false;
GBAThreadJNI()
{
JavaVM* vm = IDCache::GetJavaVM();
if (!vm)
return;
jint rc = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
if (rc == JNI_EDETACHED)
{
if (vm->AttachCurrentThread(&env, nullptr) != JNI_OK)
env = nullptr;
else
attached = true;
}
else if (rc != JNI_OK)
{
env = nullptr;
return;
}
if (s_on_gba_frame.load() == nullptr)
{
jclass cls = IDCache::GetNativeLibraryClass();
if (cls)
{
jmethodID mid = env->GetStaticMethodID(cls, "onGBAFrame", "(I)V");
if (mid)
{
s_on_gba_frame.store(mid);
}
else
{
env->ExceptionClear();
}
}
}
}
};
} // namespace
#endif
static void GBACore_PushFrameReady(int slot)
{
#ifdef ANDROID
thread_local GBAThreadJNI tls;
jmethodID methodId = s_on_gba_frame.load();
if (!tls.env || !methodId)
return;
tls.env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), s_on_gba_frame,
static_cast<jint>(slot));
if (tls.env->ExceptionCheck())
{
tls.env->ExceptionDescribe();
tls.env->ExceptionClear();
}
#endif
}
void Core::AddCallbacks()
{
mCoreCallbacks callbacks{};
@ -455,8 +536,12 @@ void Core::AddCallbacks()
};
callbacks.videoFrameEnded = [](void* context) {
auto core = static_cast<Core*>(context);
if (auto host = core->m_host.lock())
host->FrameEnded(core->m_video_buffer);
{
std::unique_lock<std::shared_mutex> lock(core->m_video_buffer_mutex);
if (auto host = core->m_host.lock())
host->FrameEnded(core->m_video_buffer);
}
GBACore_PushFrameReady(core->m_device_number);
};
m_core->addCoreCallbacks(m_core, &callbacks);
}

View File

@ -7,6 +7,7 @@
#include <array>
#include <memory>
#include <shared_mutex>
#include <span>
#include <string>
#include <string_view>
@ -90,6 +91,10 @@ public:
mAudioBuffer* GetAudioBuffer() { return m_core->getAudioBuffer(m_core); }
std::span<const u32> GetVideoBuffer() const { return m_video_buffer; }
// Android game code future use.
std::string GetGameCode() const { return m_game_code; }
// android video bufffer mutex.
std::shared_mutex& GetVideoBufferMutex() { return m_video_buffer_mutex; }
mPlatform GetPlatform() const { return m_core->platform(m_core); }
u32 GetAudioSampleRate() const { return m_core->audioSampleRate(m_core); }
@ -139,6 +144,10 @@ private:
std::string m_save_path;
std::array<u8, 20> m_rom_hash{};
std::string m_game_title;
// android game code future use.
std::string m_game_code;
// guard for m_video_buffer vulkan
mutable std::shared_mutex m_video_buffer_mutex;
mCore* m_core{};
mCoreSync m_core_sync{};

View File

@ -31,6 +31,11 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#ifdef HAS_LIBMGBA
#include "Core/HW/GBACore.h"
#include "Core/HW/SI/SI_DeviceGBAEmu.h"
#endif
namespace SerialInterface
{
// SI Internal Hardware Addresses
@ -597,5 +602,52 @@ u32 SerialInterfaceManager::GetPollXLines()
{
return m_poll.X;
}
// Android GBA emulation.
std::shared_ptr<HW::GBA::Core> SerialInterfaceManager::GetGBACore(int channel) const
{
#ifdef HAS_LIBMGBA
if (channel < 0 || channel >= MAX_SI_CHANNELS)
return nullptr;
auto* dev = m_channel[channel].device.get();
if (dev && dev->GetDeviceType() == SIDEVICE_GC_GBA_EMULATED)
return static_cast<CSIDevice_GBAEmu*>(dev)->GetCore();
#endif
return nullptr;
}
void SerialInterfaceManager::ResetGBACore(int channel)
{
#ifdef HAS_LIBMGBA
if (channel < 0 || channel >= MAX_SI_CHANNELS)
return;
auto* dev = m_channel[channel].device.get();
if (dev && dev->GetDeviceType() == SIDEVICE_GC_GBA_EMULATED)
static_cast<CSIDevice_GBAEmu*>(dev)->GetCore()->Reset();
#endif
}
std::string SerialInterfaceManager::GetGBAGameTitle(int channel) const
{
#ifdef HAS_LIBMGBA
if (channel < 0 || channel >= MAX_SI_CHANNELS)
return "";
auto* dev = m_channel[channel].device.get();
if (dev && dev->GetDeviceType() == SIDEVICE_GC_GBA_EMULATED)
return static_cast<CSIDevice_GBAEmu*>(dev)->GetGBAGameTitle();
#endif
return "";
}
std::string SerialInterfaceManager::GetGBAGameCode(int channel) const
{
#ifdef HAS_LIBMGBA
if (channel < 0 || channel >= MAX_SI_CHANNELS)
return "";
auto* dev = m_channel[channel].device.get();
if (dev && dev->GetDeviceType() == SIDEVICE_GC_GBA_EMULATED)
return static_cast<CSIDevice_GBAEmu*>(dev)->GetGBAGameCode();
#endif
return "";
}
} // namespace SerialInterface

View File

@ -24,6 +24,11 @@ namespace MMIO
{
class Mapping;
}
// Android GBA
namespace HW::GBA
{
class Core;
}
namespace SerialInterface
{
@ -68,6 +73,11 @@ public:
u32 GetPollXLines();
std::shared_ptr<HW::GBA::Core> GetGBACore(int channel) const;
void ResetGBACore(int channel);
std::string GetGBAGameTitle(int channel) const;
std::string GetGBAGameCode(int channel) const;
static constexpr u32 BUFFER_SIZE = 128;
private:

View File

@ -172,5 +172,14 @@ void CSIDevice_GBAEmu::OnEvent(u64 userdata, s64 cycles_late)
const auto num_cycles = userdata + GetSyncInterval(m_system.GetSystemTimers());
m_system.GetSerialInterface().ScheduleEvent(m_device_number, num_cycles);
}
// android gameID furture use
std::string CSIDevice_GBAEmu::GetGBAGameTitle() const
{
return m_core ? m_core->GetCoreInfo().game_title : "";
}
std::string CSIDevice_GBAEmu::GetGBAGameCode() const
{
return m_core ? m_core->GetGameCode() : "";
}
} // namespace SerialInterface
#endif // HAS_LIBMGBA

View File

@ -31,6 +31,11 @@ public:
void SendCommand(u32 command, u8 poll) override;
void DoState(PointerWrap& p) override;
void OnEvent(u64 userdata, s64 cycles_late) override;
// android gba core
std::shared_ptr<HW::GBA::Core> GetCore() const { return m_core; }
// android gamecode match for future use.
std::string GetGBAGameTitle() const;
std::string GetGBAGameCode() const;
private:
enum class NextAction

View File

@ -13,6 +13,8 @@
#include "Common/Assert.h"
#include "Core/HW/GBAPad.h"
#include "Core/HW/GBAPadEmu.h"
#include "Core/HW/GCPad.h"
#include "Core/HW/GCPadEmu.h"
#include "Core/HW/Wiimote.h"
@ -144,6 +146,19 @@ const ControlsMap s_classic_controls_map = {{
{{WiimoteEmu::Classic::RIGHT_STICK_GROUP, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE},
ControlID::CLASSIC_RIGHT_STICK_Y},
}};
// android GBA controls map
static const ControlsMap s_gbapad_controls_map = {{
{{GBAPad::BUTTONS_GROUP, GBAPad::A_BUTTON}, ControlID::GCPAD_A_BUTTON},
{{GBAPad::BUTTONS_GROUP, GBAPad::B_BUTTON}, ControlID::GCPAD_B_BUTTON},
{{GBAPad::BUTTONS_GROUP, GBAPad::L_BUTTON}, ControlID::GCPAD_L_DIGITAL},
{{GBAPad::BUTTONS_GROUP, GBAPad::R_BUTTON}, ControlID::GCPAD_R_DIGITAL},
{{GBAPad::BUTTONS_GROUP, GBAPad::START_BUTTON}, ControlID::GCPAD_START_BUTTON},
{{GBAPad::BUTTONS_GROUP, GBAPad::SELECT_BUTTON}, ControlID::GCPAD_Z_BUTTON},
{{GBAPad::DPAD_GROUP, DIRECTION_UP}, ControlID::GCPAD_DPAD_UP},
{{GBAPad::DPAD_GROUP, DIRECTION_DOWN}, ControlID::GCPAD_DPAD_DOWN},
{{GBAPad::DPAD_GROUP, DIRECTION_LEFT}, ControlID::GCPAD_DPAD_LEFT},
{{GBAPad::DPAD_GROUP, DIRECTION_RIGHT}, ControlID::GCPAD_DPAD_RIGHT},
}};
ControllerEmu::InputOverrideFunction GetInputOverrideFunction(const ControlsMap& controls_map,
size_t i)
@ -220,6 +235,20 @@ void UnregisterWiiInputOverrider(int controller_index)
for (size_t i = ControlID::FIRST_WII_CONTROL; i <= ControlID::LAST_WII_CONTROL; ++i)
s_state_arrays[controller_index][i].overriding = false;
}
// android gba overrider
void RegisterGBAInputOverrider(int controller_index)
{
Pad::GetGBAConfig()
->GetController(controller_index)
->SetInputOverrideFunction(GetInputOverrideFunction(s_gbapad_controls_map, controller_index));
}
void UnregisterGBAInputOverrider(int controller_index)
{
Pad::GetGBAConfig()->GetController(controller_index)->ClearInputOverrideFunction();
for (size_t i = ControlID::FIRST_GC_CONTROL; i <= ControlID::LAST_GC_CONTROL; ++i)
s_state_arrays[controller_index][i].overriding = false;
}
void SetControlState(int controller_index, ControlID control, double state)
{

View File

@ -80,6 +80,8 @@ void RegisterGameCubeInputOverrider(int controller_index);
void RegisterWiiInputOverrider(int controller_index);
void UnregisterGameCubeInputOverrider(int controller_index);
void UnregisterWiiInputOverrider(int controller_index);
void RegisterGBAInputOverrider(int controller_index);
void UnregisterGBAInputOverrider(int controller_index);
void SetControlState(int controller_index, ControlID control, double state);
void ClearControlState(int controller_index, ControlID control);