From 34af99ad26cbddfdb88f326855a9c5b89722e281 Mon Sep 17 00:00:00 2001 From: Linkinworm Date: Fri, 17 Apr 2026 21:39:34 +0100 Subject: [PATCH 1/2] Enables, GBA on android. links to games, can be played on its own. --- .../dolphinemu/dolphinemu/NativeLibrary.kt | 31 +- .../activities/EmulationActivity.kt | 443 +++++++++++- .../dolphinemu/features/gba/GBAOverlayView.kt | 80 +++ .../features/gba/GbaRenderManager.kt | 70 ++ .../features/input/model/InputOverrider.kt | 4 + .../features/settings/model/BooleanSetting.kt | 21 +- .../features/settings/model/StringSetting.kt | 12 + .../features/settings/ui/SettingsAdapter.kt | 27 + .../settings/ui/SettingsFragmentPresenter.kt | 49 +- .../dolphinemu/overlay/InputOverlay.kt | 447 +++++++++++- .../dolphinemu/utils/FileBrowserHelper.java | 32 +- .../main/res/layout/fragment_emulation.xml | 4 +- .../res/menu/menu_overlay_controls_gc.xml | 11 + .../app/src/main/res/values/arrays.xml | 668 +++++++++--------- .../app/src/main/res/values/strings.xml | 2 + Source/Android/jni/AndroidCommon/IDCache.cpp | 7 +- Source/Android/jni/AndroidCommon/IDCache.h | 2 + Source/Android/jni/Input/InputOverrider.cpp | 14 + Source/Android/jni/MainAndroid.cpp | 56 ++ Source/Core/Core/HW/GBACore.cpp | 89 ++- Source/Core/Core/HW/GBACore.h | 9 + Source/Core/Core/HW/SI/SI.cpp | 52 ++ Source/Core/Core/HW/SI/SI.h | 10 + Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp | 9 + Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h | 5 + .../Touch/InputOverrider.cpp | 29 + .../Touch/InputOverrider.h | 2 + 27 files changed, 1795 insertions(+), 390 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt index 0edc0f91f9..3dfde3e4a3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt @@ -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() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt index 8a1d8addbd..43c6c178d2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -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() + + private val lastGbaTapTimes = mutableMapOf() + + 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, riivolution: Boolean, fromIntent: Boolean = false) { + fun launch( + activity: FragmentActivity, + filePaths: Array, + 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() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt new file mode 100644 index 0000000000..5bc85f7ce6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt @@ -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 + } +} + diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt new file mode 100644 index 0000000000..2a60f09363 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt @@ -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 = 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) { + handler.removeCallbacksAndMessages(null) + attached = true + activeViews = views + views.forEach { + it.renderManager = this + if (it.surfaceReady) requestRedraw(it.gbaSlot) + } + } + + fun updateViews(views: List) { + 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 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt index 8224434ee6..51137f0b71 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt @@ -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) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt index b6a03e6b61..28a9376959 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt @@ -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, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index 512a33ddbb..3b1f292df0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -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 = HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY)) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index ae236d3764..2fe0324aa2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -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 diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 4603411c52..80e9f81669 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -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, - controller: EmulatedController, - groupTypeFilter: Set? + sl: ArrayList, + controller: EmulatedController, + groupTypeFilter: Set? ) { - 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) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt index 3d991fed1e..752b0aedb1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt @@ -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 = HashSet() + private val gbaOverlayDpads: MutableSet = 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() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java index f52a70216f..f1e4ccffeb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/FileBrowserHelper.java @@ -29,8 +29,8 @@ import java.util.Set; public final class FileBrowserHelper { public static final HashSet 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 GAME_LIKE_EXTENSIONS = new HashSet<>(GAME_EXTENSIONS); @@ -40,19 +40,19 @@ public final class FileBrowserHelper } public static final HashSet 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 BIN_EXTENSION = new HashSet<>(Collections.singletonList( - "bin")); + "bin")); public static final HashSet RAW_EXTENSION = new HashSet<>(Collections.singletonList( - "raw")); + "raw")); public static final HashSet WAD_EXTENSION = new HashSet<>(Collections.singletonList( - "wad")); + "wad")); public static Intent createDirectoryPickerIntent(FragmentActivity activity, - HashSet extensions) + HashSet 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 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 diff --git a/Source/Android/app/src/main/res/layout/fragment_emulation.xml b/Source/Android/app/src/main/res/layout/fragment_emulation.xml index f51f25699a..c0f6f63c81 100644 --- a/Source/Android/app/src/main/res/layout/fragment_emulation.xml +++ b/Source/Android/app/src/main/res/layout/fragment_emulation.xml @@ -11,7 +11,9 @@ diff --git a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml index e4ef459487..7d2061fff3 100644 --- a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml +++ b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml @@ -30,4 +30,15 @@ + + + + + + + diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index d919edb1d2..74ee53cb09 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -2,78 +2,37 @@ - - NTSC-J - NTSC-U - PAL - NTSC-K - 0 1 2 3 - - - - @string/jit_recompiler_x86 - @string/cached_interpreter_slower - @string/interpreter_slowest - 1 5 0 - - @string/jit_recompiler_arm64 - @string/cached_interpreter_slower - @string/interpreter_slowest - + + 4 5 0 - - @string/cached_interpreter - @string/interpreter - 5 0 - - - - @string/dsp_hle - @string/dsp_lle_recompiler - @string/dsp_lle_interpreter - 0 1 2 - - @string/dsp_hle - @string/dsp_lle_interpreter - 0 2 - - - - @string/language_english - @string/language_german - @string/language_french - @string/language_spanish - @string/language_italian - @string/language_dutch - 0 1 @@ -82,14 +41,6 @@ 4 5 - - - - @string/device_nothing - @string/device_dummy - @string/device_memory_card - @string/device_gci_folder - 255 0 @@ -97,16 +48,7 @@ 8 - - - @string/device_nothing - @string/device_dummy - @string/broadband_adapter_xlink - @string/broadband_adapter_hle - @string/broadband_adapter_tapserver - @string/modem_adapter_tapserver - @string/sp1_am_baseboard - + 255 0 @@ -116,20 +58,6 @@ 13 6 - - - - @string/language_japanese - @string/language_english - @string/language_german - @string/language_french - @string/language_spanish - @string/language_italian - @string/language_dutch - @string/language_simplified_chinese - @string/language_traditional_chinese - @string/language_korean - 0 1 @@ -142,49 +70,23 @@ 8 9 - - - - @string/sound_mode_mono - @string/sound_mode_stereo - @string/sound_mode_surround - 0 1 2 - - - - @string/sensor_position_top - @string/sensor_position_bottom - 1 0 - - - @string/log_notice - @string/log_error - @string/log_warning - @string/log_info - + 1 2 3 4 - - @string/log_notice - @string/log_error - @string/log_warning - @string/log_info - @string/log_debug - 1 2 @@ -193,29 +95,7 @@ 5 - - - @string/backend_opengl - @string/backend_vulkan - @string/backend_software - @string/backend_null - - - OGL - Vulkan - Software Renderer - Null - - - - - @string/extension_none - @string/extension_nunchuk - @string/extension_classic - @string/extension_guitar - @string/extension_drums - @string/extension_turntable - + 0 1 @@ -224,40 +104,19 @@ 4 5 - - - - @string/accuracy_fast - @string/accuracy_medium - @string/accuracy_safe - 128 512 0 - - - @string/shader_compilation_specialized - @string/shader_compilation_exclusive_ubershaders - @string/shader_compilation_hybrid_ubershaders - @string/shader_compilation_skip_drawing - + 0 1 2 3 - - - - @string/shader_compilation_specialized_description - @string/shader_compilation_exclusive_ubershaders_description - @string/shader_compilation_hybrid_ubershaders_description - @string/shader_compilation_skip_drawing_description - 0 1 @@ -265,15 +124,7 @@ 3 - - - @string/resolution_one_native - @string/resolution_two_native - @string/resolution_three_native - @string/resolution_four_native - @string/resolution_five_native - @string/resolution_six_native - + 1 2 @@ -282,14 +133,6 @@ 5 6 - - - - @string/multiple_off - @string/multiple_two - @string/multiple_four - @string/multiple_eight - 1 2 @@ -297,14 +140,7 @@ 8 - - - @string/filtering_default - @string/multiple_two - @string/multiple_four - @string/multiple_eight - @string/multiple_sixteen - + 0 1 @@ -312,40 +148,19 @@ 3 4 - - - - @string/filtering_default - @string/filtering_nearest - @string/filtering_linear - 0 1 2 - - - @string/stereoscopy_off - @string/stereoscopy_side_by_side - @string/stereoscopy_top_and_bottom - @string/stereoscopy_anaglyph - + 0 1 2 3 - - - - @string/aspect_ratio_auto - @string/aspect_ratio_force_sixteen_by_nine - @string/aspect_ratio_force_four_by_three - @string/aspect_ratio_stretch - 0 1 @@ -353,6 +168,329 @@ 3 + + + 0 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + + 0 + 1 + 2 + + + 0 + 8 + 1 + -1 + + + 0 + 2 + 3 + 4 + + + + + 0 + 1 + 2 + 3 + 4 + + + -1 + 1 + 2 + + + + + 0 + 1 + 2 + + + 0 + 3 + 7 + 8 + + + + + 32768 + + + 2097152 + + + + + 32768 + 65536 + 131072 + 262144 + 524288 + 1048576 + 2097152 + + + 0 + + + + + 0 + 1 + 2 + 3 + 4 + + + 0 + 2 + 3 + 4 + 5 + 6 + + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + + + + + 0 + 1 + 2 + + + NTSC-J + NTSC-U + PAL + NTSC-K + + + + + @string/jit_recompiler_x86 + @string/cached_interpreter_slower + @string/interpreter_slowest + + + @string/jit_recompiler_arm64 + @string/cached_interpreter_slower + @string/interpreter_slowest + + + + + @string/cached_interpreter + @string/interpreter + + + @string/dsp_hle + @string/dsp_lle_recompiler + @string/dsp_lle_interpreter + + + + + @string/dsp_hle + @string/dsp_lle_interpreter + + + @string/language_english + @string/language_german + @string/language_french + @string/language_spanish + @string/language_italian + @string/language_dutch + + + + + @string/device_nothing + @string/device_dummy + @string/device_memory_card + @string/device_gci_folder + + + @string/device_nothing + @string/device_dummy + @string/broadband_adapter_xlink + @string/broadband_adapter_hle + @string/broadband_adapter_tapserver + @string/modem_adapter_tapserver + @string/sp1_am_baseboard + + + + @string/language_japanese + @string/language_english + @string/language_german + @string/language_french + @string/language_spanish + @string/language_italian + @string/language_dutch + @string/language_simplified_chinese + @string/language_traditional_chinese + @string/language_korean + + + + @string/sound_mode_mono + @string/sound_mode_stereo + @string/sound_mode_surround + + + @string/sensor_position_top + @string/sensor_position_bottom + + + + @string/log_notice + @string/log_error + @string/log_warning + @string/log_info + + + @string/log_notice + @string/log_error + @string/log_warning + @string/log_info + @string/log_debug + + + + @string/backend_opengl + @string/backend_vulkan + @string/backend_software + @string/backend_null + + + + OGL + Vulkan + Software Renderer + Null + + + + @string/extension_none + @string/extension_nunchuk + @string/extension_classic + @string/extension_guitar + @string/extension_drums + @string/extension_turntable + + + + @string/accuracy_fast + @string/accuracy_medium + @string/accuracy_safe + + + + @string/shader_compilation_specialized + @string/shader_compilation_exclusive_ubershaders + @string/shader_compilation_hybrid_ubershaders + @string/shader_compilation_skip_drawing + + + + @string/shader_compilation_specialized_description + @string/shader_compilation_exclusive_ubershaders_description + @string/shader_compilation_hybrid_ubershaders_description + @string/shader_compilation_skip_drawing_description + + + + @string/resolution_one_native + @string/resolution_two_native + @string/resolution_three_native + @string/resolution_four_native + @string/resolution_five_native + @string/resolution_six_native + + + + @string/multiple_off + @string/multiple_two + @string/multiple_four + @string/multiple_eight + + + + @string/filtering_default + @string/multiple_two + @string/multiple_four + @string/multiple_eight + @string/multiple_sixteen + + + + @string/filtering_default + @string/filtering_nearest + @string/filtering_linear + + + + @string/stereoscopy_off + @string/stereoscopy_side_by_side + @string/stereoscopy_top_and_bottom + @string/stereoscopy_anaglyph + + + + @string/aspect_ratio_auto + @string/aspect_ratio_force_sixteen_by_nine + @string/aspect_ratio_force_four_by_three + @string/aspect_ratio_stretch + @string/country_europe @string/country_japan @@ -370,6 +508,7 @@ @string/country_unknown + @string/gcpad_disabled @string/gcpad_emulated @@ -379,29 +518,13 @@ @string/gcpad_taru_konga @string/gcpad_am_baseboard @string/gcpad_gc_adapter + GBA - - 0 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - - @string/wiimote_disabled @string/wiimote_emulated @string/wiimote_real - - 0 - 1 - 2 - - A B @@ -415,7 +538,6 @@ @string/gamepad_main_stick @string/gamepad_c_stick - A B @@ -437,7 +559,6 @@ @string/gamepad_home @string/gamepad_d_pad - A B @@ -461,7 +582,6 @@ Z @string/gamepad_nunchuk_stick - A B @@ -504,7 +624,6 @@ ZL ZR - @string/ir_disabled @string/ir_follow @@ -516,7 +635,6 @@ @string/double_tap_b @string/double_tap_2 - @string/double_tap_a @string/double_tap_b @@ -530,26 +648,13 @@ @string/orientation_portrait @string/orientation_auto - - 0 - 8 - 1 - -1 - - - @string/theme_default @string/theme_material_default @string/theme_green @string/theme_pink - - 0 - 2 - 3 - 4 - + @string/theme_default @string/theme_material_you @@ -557,36 +662,17 @@ @string/theme_green @string/theme_pink - - 0 - 1 - 2 - 3 - 4 - - @string/theme_mode_follow_system @string/theme_mode_light @string/theme_mode_dark - - -1 - 1 - 2 - @string/sync_gpu_never @string/sync_gpu_idle @string/sync_gpu_always - - 0 - 1 - 2 - - @string/motion_device_with_pointer @string/motion_device_without_pointer @@ -599,27 +685,13 @@ WIA RVZ - - 0 - 3 - 7 - 8 - - @string/block_size_32_kib - - 32768 - @string/block_size_2_mib - - 2097152 - - @string/block_size_32_kib @string/block_size_64_kib @@ -629,23 +701,10 @@ @string/block_size_1_mib @string/block_size_2_mib - - 32768 - 65536 - 131072 - 262144 - 524288 - 1048576 - 2097152 - @string/compression_deflate - - 0 - - @string/compression_none @string/compression_purge @@ -653,13 +712,6 @@ @string/compression_lzma @string/compression_lzma2 - - 0 - 1 - 2 - 3 - 4 - @string/compression_none @@ -668,15 +720,6 @@ @string/compression_lzma2 @string/compression_zstandard - - 0 - 2 - 3 - 4 - 5 - 6 - - 1 2 @@ -688,17 +731,6 @@ 8 9 - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 1 @@ -724,39 +756,9 @@ 21 22 - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - - @string/ntscm_space @string/ntscj_space @string/pal_space - - 0 - 1 - 2 - diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 5c2e978d48..eb78c775ea 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -89,6 +89,8 @@ BIOS Game Boy Player ROM Saves + GBA ROM Path + Rom to load for this slot. Long-press to clear Wii Misc Settings SD Card Settings diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 18b486023a..c77e623199 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -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; diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index 0aaa9feec3..e6b4db6911 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -124,4 +124,6 @@ jclass GetAudioUtilsClass(); jmethodID GetAudioUtilsGetSampleRate(); jmethodID GetAudioUtilsGetFramesPerBuffer(); +JavaVM* GetJavaVM(); + } // namespace IDCache diff --git a/Source/Android/jni/Input/InputOverrider.cpp b/Source/Android/jni/Input/InputOverrider.cpp index 26b823e03d..c7c7b6f0d7 100644 --- a/Source/Android/jni/Input/InputOverrider.cpp +++ b/Source/Android/jni/Input/InputOverrider.cpp @@ -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( diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 7112833403..5f251b6223 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -69,6 +69,14 @@ #include "jni/AndroidCommon/IDCache.h" #include "jni/Host.h" +#include +#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 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(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)); +} } diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index 0e42a3ec66..190aa0e113 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -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 +namespace +{ +static std::atomic 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(&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(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(context); - if (auto host = core->m_host.lock()) - host->FrameEnded(core->m_video_buffer); + { + std::unique_lock 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); } diff --git a/Source/Core/Core/HW/GBACore.h b/Source/Core/Core/HW/GBACore.h index 3427f8eb0f..891866640b 100644 --- a/Source/Core/Core/HW/GBACore.h +++ b/Source/Core/Core/HW/GBACore.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -90,6 +91,10 @@ public: mAudioBuffer* GetAudioBuffer() { return m_core->getAudioBuffer(m_core); } std::span 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 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{}; diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index 11e0c6f2e7..f033aa8f9c 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -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 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(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(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(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(dev)->GetGBAGameCode(); +#endif + return ""; +} } // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI.h b/Source/Core/Core/HW/SI/SI.h index 5229bd045f..4a8b2895ae 100644 --- a/Source/Core/Core/HW/SI/SI.h +++ b/Source/Core/Core/HW/SI/SI.h @@ -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 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: diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp index 36ccc15bc0..81402033df 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp @@ -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 diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h index 89a8473b4d..01647bb468 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h @@ -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 GetCore() const { return m_core; } + // android gamecode match for future use. + std::string GetGBAGameTitle() const; + std::string GetGBAGameCode() const; private: enum class NextAction diff --git a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp index 9ddbaffcd6..a7458d4df7 100644 --- a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp @@ -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) { diff --git a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.h b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.h index 6217b44d4e..22fe7a8607 100644 --- a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.h +++ b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.h @@ -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); From 49f124e19dd9444b2a89ccbb860b37673a58dd7f Mon Sep 17 00:00:00 2001 From: Linkinworm Date: Fri, 24 Apr 2026 21:08:53 +0100 Subject: [PATCH 2/2] Android gba fixes, code clean up --- .../dolphinemu/dolphinemu/NativeLibrary.kt | 21 - .../activities/EmulationActivity.kt | 55 +- .../dolphinemu/features/gba/GbaLibrary.kt | 19 + .../{GBAOverlayView.kt => GbaOverlayView.kt} | 2 +- .../features/gba/GbaRenderManager.kt | 8 +- .../features/settings/model/BooleanSetting.kt | 22 +- .../features/settings/ui/SettingsAdapter.kt | 22 - .../dolphinemu/overlay/InputOverlay.kt | 265 +++----- .../res/menu/menu_overlay_controls_gc.xml | 1 - .../app/src/main/res/values/arrays.xml | 630 +++++++++--------- .../app/src/main/res/values/strings.xml | 3 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 24 +- Source/Android/jni/AndroidCommon/IDCache.h | 3 +- Source/Android/jni/Input/InputOverrider.cpp | 2 +- Source/Android/jni/MainAndroid.cpp | 25 +- Source/Core/Core/HW/GBACore.cpp | 92 +-- Source/Core/Core/HW/GBACore.h | 8 +- Source/Core/Core/HW/SI/SI.cpp | 36 - Source/Core/Core/HW/SI/SI.h | 3 - Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp | 9 - Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h | 3 - .../Touch/InputOverrider.cpp | 4 +- 22 files changed, 530 insertions(+), 727 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaLibrary.kt rename Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/{GBAOverlayView.kt => GbaOverlayView.kt} (97%) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt index 3dfde3e4a3..b8f2c60851 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.kt @@ -13,13 +13,11 @@ 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. @@ -452,25 +450,6 @@ 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() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt index 43c6c178d2..5aee5ebe9e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -41,7 +41,8 @@ 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.GbaLibrary +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 @@ -80,7 +81,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { private lateinit var settings: Settings - private val gbaViews = mutableListOf() + private val gbaViews = mutableListOf() private val lastGbaTapTimes = mutableMapOf() @@ -200,7 +201,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { super.onCreate(savedInstanceState) - //gba overlay setup, also clean up views from previous launch to prevent stacking + // gba overlay setup, also clean up views from previous launch to prevent stacking GbaRenderManager.detach() gbaViews.forEach { binding.root.removeView(it) } gbaViews.clear() @@ -236,8 +237,8 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { isGbaLocked = globalGbaPrefs.getBoolean("gba_locked", false) for (slot in 0 until 4) { - if (IntSetting.getSettingForSIDevice(slot).int != 13) continue - val view = GBAOverlayView(this) + if (IntSetting.getSettingForSIDevice(slot).int != InputOverlay.EMULATED_GBA_CONTROLLER) 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) @@ -386,7 +387,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { updateDisplaySettings() val activeSlots = (0 until 4).filter { - IntSetting.getSettingForSIDevice(it).int == 13 + IntSetting.getSettingForSIDevice(it).int == InputOverlay.EMULATED_GBA_CONTROLLER } gbaViews.forEachIndexed { index, view -> if (index < activeSlots.size) { @@ -630,11 +631,12 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { MENU_ACTION_EXIT -> emulationFragment!!.stopEmulation() MENU_ACTION_GBA_SNAP -> toggleGBASnap() MENU_ACTION_GBA_RESET -> { - isGbaLocked = false; resetGBAScreens(); + isGbaLocked = false; + resetGBAScreens(); binding.root.post { applyGbaLayout() } } - MENU_ACTION_GBA_RESET_CORE -> resetGBACore() + MENU_ACTION_GBA_RESET_CORE -> resetGbaCore() } } @@ -1115,20 +1117,22 @@ 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) + 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) } @@ -1170,9 +1174,9 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { this.themeId = themeId } - //gba touch for scale/drag listener while in unlocked gba mode. + // gba touch for scale/drag listener while in unlocked gba mode. private fun attachGbaTouchListener( - view: GBAOverlayView, + view: GbaOverlayView, slot: Int, slotPrefs: android.content.SharedPreferences ) { @@ -1231,9 +1235,6 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { // 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 -> { @@ -1405,10 +1406,10 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { } } - private fun resetGBACore() { + private fun resetGbaCore() { for (slot in 0 until 4) { - if (IntSetting.getSettingForSIDevice(slot).int == 13) - NativeLibrary.resetGBACore(slot) + if (IntSetting.getSettingForSIDevice(slot).int == InputOverlay.EMULATED_GBA_CONTROLLER) + GbaLibrary.resetGbaCore(slot) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaLibrary.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaLibrary.kt new file mode 100644 index 0000000000..fbf4a894d7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaLibrary.kt @@ -0,0 +1,19 @@ +package org.dolphinemu.dolphinemu.features.gba + +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.features.gba.GbaRenderManager + +object GbaLibrary{ + + @JvmStatic + external fun copyGbaFramebuffer(slot: Int, buffer: java.nio.ByteBuffer): Boolean + + @JvmStatic + external fun resetGbaCore(slot: Int) + + @Keep + @JvmStatic + fun onGbaFrame(slot: Int) { + GbaRenderManager.onFrame(slot) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaOverlayView.kt similarity index 97% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaOverlayView.kt index 5bc85f7ce6..aa57203ab3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GBAOverlayView.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaOverlayView.kt @@ -8,7 +8,7 @@ import android.graphics.* //Passive surfaceview displaying one gba screen //Rendering is driven by GbaRenderManager -class GBAOverlayView(context: Context) : SurfaceView(context), +class GbaOverlayView(context: Context) : SurfaceView(context), SurfaceHolder.Callback { var renderManager: GbaRenderManager? = null var gbaSlot: Int = 0 diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt index 2a60f09363..3198bd56b2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/gba/GbaRenderManager.kt @@ -14,7 +14,7 @@ object GbaRenderManager { private val handler = Handler(renderThread.looper) @Volatile - private var activeViews: List = emptyList() + private var activeViews: List = emptyList() @Volatile private var attached = false @@ -31,7 +31,7 @@ object GbaRenderManager { if (!view.holder.surface.isValid) return val buffer = buffers[slot] buffer.rewind() - if (NativeLibrary.copyGBAFramebuffer(slot, buffer)) { + if (GbaLibrary.copyGbaFramebuffer(slot, buffer)) { buffer.rewind() bitmaps[slot].copyPixelsFromBuffer(buffer) view.drawFrame(bitmaps[slot]) @@ -45,7 +45,7 @@ object GbaRenderManager { handler.post { renderFrame(slot, forceRedraw = true) } } - fun attach(views: List) { + fun attach(views: List) { handler.removeCallbacksAndMessages(null) attached = true activeViews = views @@ -55,7 +55,7 @@ object GbaRenderManager { } } - fun updateViews(views: List) { + fun updateViews(views: List) { activeViews = views views.forEach { it.renderManager = this } views.forEach { if (it.surfaceReady) requestRedraw(it.gbaSlot) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt index 28a9376959..cd0ea42d94 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt @@ -14,13 +14,9 @@ enum class BooleanSetting( MAIN_SKIP_IPL(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "SkipIPL", true), MAIN_DSP_HLE(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "DSPHLE", true), MAIN_FASTMEM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Fastmem", true), + MAIN_PAGE_TABLE_FASTMEM(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "PageTableFastmem", 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, @@ -36,12 +32,7 @@ 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, @@ -147,12 +138,7 @@ 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, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index 2fe0324aa2..6db93cad6f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -71,58 +71,47 @@ 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") } } @@ -458,17 +447,6 @@ 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() diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt index 752b0aedb1..93b1c3cc0a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt @@ -177,21 +177,98 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex val pointerIndex = if (firstPointer) 0 else event.actionIndex // Tracks if any button/joystick is pressed down var pressed = false +// Process GBA buttons first and claim them so GCPAD doesnt fire on the same touch. + val gbaClaimedPointers = mutableSetOf() - for (button in overlayButtons) { + if (gbaControllerIndex >= 0) { + pressed = processButtons( + gbaOverlayButtons, + gbaControllerIndex, + action, + event, + pointerIndex, + pressed, + gbaClaimedPointers + ) + pressed = processDpads( + gbaOverlayDpads, + gbaControllerIndex, + action, + event, + pointerIndex, + pressed, + gbaClaimedPointers + ) + } + + pressed = processButtons(overlayButtons, controllerIndex, action, event, pointerIndex, pressed, excludePointers = gbaClaimedPointers) + pressed = processDpads(overlayDpads, controllerIndex, action, event, pointerIndex, pressed, excludePointers = gbaClaimedPointers) + + for (joystick in overlayJoysticks) { + if (joystick.trackEvent(event)) { + if (joystick.trackId != -1) + pressed = true + } + + InputOverrider.setControlState( + controllerIndex, + joystick.xControl, + joystick.x.toDouble() + ) + InputOverrider.setControlState( + controllerIndex, + joystick.yControl, + -joystick.y.toDouble() + ) + } + + // No button/joystick pressed, safe to move pointer + if (!pressed && overlayPointer != null) { + overlayPointer!!.onTouch(event) + InputOverrider.setControlState( + controllerIndex, + ControlId.WIIMOTE_IR_X, + overlayPointer!!.x.toDouble() + ) + InputOverrider.setControlState( + controllerIndex, + ControlId.WIIMOTE_IR_Y, + -overlayPointer!!.y.toDouble() + ) + } + + invalidate() + + return true + } + + fun processButtons( + buttons: Set, + controllerIndex: Int, + action: Int, + event: MotionEvent, + pointerIndex: Int, + pressedIn: Boolean, + claimedPointers: MutableSet = mutableSetOf(), + excludePointers: Set = emptySet() + ): Boolean { + var pressed = pressedIn + for (button in buttons) { // Determine the button state to apply based on the MotionEvent action flag. when (action) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { + val pointerId = event.getPointerId(pointerIndex) // If a pointer enters the bounds of a button, press that button. - if (button.bounds.contains( + if (!excludePointers.contains(pointerId) && 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) + button.trackId = pointerId pressed = true + claimedPointers.add(pointerId) InputOverrider.setControlState( controllerIndex, button.control, @@ -233,14 +310,29 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex } } } + return pressed + } - for (dpad in overlayDpads) { + fun processDpads( + dpads: Set, + controllerIndex: Int, + action: Int, + event: MotionEvent, + pointerIndex: Int, + pressedIn: Boolean, + claimedPointers: MutableSet = mutableSetOf(), + excludePointers: Set = emptySet() + ): Boolean + { + var pressed = pressedIn + for (dpad in dpads) { // Determine the button state to apply based on the MotionEvent action flag. when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> { + val pointerId = event.getPointerId(pointerIndex) // If a pointer enters the bounds of a button, press that button. - if (dpad.bounds + if (!excludePointers.contains(pointerId) && dpad.bounds .contains( event.getX(pointerIndex).toInt(), event.getY(pointerIndex).toInt() @@ -248,6 +340,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex ) { dpad.trackId = event.getPointerId(pointerIndex) pressed = true + claimedPointers.add(pointerId) } } } @@ -310,155 +403,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex } } } - - for (joystick in overlayJoysticks) { - if (joystick.trackEvent(event)) { - if (joystick.trackId != -1) - pressed = true - } - - InputOverrider.setControlState( - controllerIndex, - joystick.xControl, - joystick.x.toDouble() - ) - InputOverrider.setControlState( - controllerIndex, - joystick.yControl, - -joystick.y.toDouble() - ) - } - - // No button/joystick pressed, safe to move pointer - if (!pressed && overlayPointer != null) { - overlayPointer!!.onTouch(event) - InputOverrider.setControlState( - controllerIndex, - ControlId.WIIMOTE_IR_X, - overlayPointer!!.x.toDouble() - ) - InputOverrider.setControlState( - controllerIndex, - ControlId.WIIMOTE_IR_Y, - -overlayPointer!!.y.toDouble() - ) - } - - //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 + return pressed } fun onTouchWhileEditing(event: MotionEvent): Boolean { @@ -1344,7 +1289,7 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex OVERLAY_NONE -> {} } - //add GBA controls on top of primary, GC controller always visible + // add GBA controls on top of primary, GC controller always visible gbaOverlayButtons.clear() gbaOverlayDpads.clear() gbaControllerIndex = -1 @@ -2712,6 +2657,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex .apply() } + fun isTouchTracked(): Boolean { + return overlayButtons.any {it.trackId !=-1} || + return overlayDpads.any {it.trackId !=-1} || + return overlayJoysticks.any {it.trackId !=-1} || + return gbaOverlayButtons.any {it.trackId !=-1} || + return gbaOverlayDpads.any {it.trackId !=-1} + } + companion object { const val OVERLAY_GAMECUBE = 0 @@ -2720,11 +2673,11 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex const val OVERLAY_WIIMOTE_NUNCHUK = 3 const val OVERLAY_WIIMOTE_CLASSIC = 4 const val OVERLAY_NONE = 5 + const val EMULATED_GBA_CONTROLLER = 13 private const val DISABLED_GAMECUBE_CONTROLLER = 0 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 diff --git a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml index 7d2061fff3..c21b8c0a03 100644 --- a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml +++ b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml @@ -35,7 +35,6 @@ android:checkable="true" android:title="Snap Gba Layout"/> - diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index 74ee53cb09..9cf4e4dedb 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -2,346 +2,70 @@ - - 0 - 1 - 2 - 3 - - - 1 - 5 - 0 - - - - - 4 - 5 - 0 - - - 5 - 0 - - - 0 - 1 - 2 - - - 0 - 2 - - - 0 - 1 - 2 - 3 - 4 - 5 - - - 255 - 0 - 1 - 8 - - - - - 255 - 0 - 10 - 12 - 11 - 13 - 6 - - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - - - 0 - 1 - 2 - - - 1 - 0 - - - - - 1 - 2 - 3 - 4 - - - 1 - 2 - 3 - 4 - 5 - - - - - 0 - 1 - 2 - 3 - 4 - 5 - - - 128 - 512 - 0 - - - - - 0 - 1 - 2 - 3 - - - 0 - 1 - 2 - 3 - - - - - 1 - 2 - 3 - 4 - 5 - 6 - - - 1 - 2 - 4 - 8 - - - - - 0 - 1 - 2 - 3 - 4 - - - 0 - 1 - 2 - - - - - 0 - 1 - 2 - 3 - - - 0 - 1 - 2 - 3 - - - - - 0 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - - - 0 - 1 - 2 - - - 0 - 8 - 1 - -1 - - - 0 - 2 - 3 - 4 - - - - - 0 - 1 - 2 - 3 - 4 - - - -1 - 1 - 2 - - - - - 0 - 1 - 2 - - - 0 - 3 - 7 - 8 - - - - - 32768 - - - 2097152 - - - - - 32768 - 65536 - 131072 - 262144 - 524288 - 1048576 - 2097152 - - - 0 - - - - - 0 - 1 - 2 - 3 - 4 - - - 0 - 2 - 3 - 4 - 5 - 6 - - - - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - - - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - - - - - 0 - 1 - 2 - NTSC-J NTSC-U PAL NTSC-K + + 0 + 1 + 2 + 3 + - + @string/jit_recompiler_x86 @string/cached_interpreter_slower @string/interpreter_slowest + + 1 + 5 + 0 + @string/jit_recompiler_arm64 @string/cached_interpreter_slower @string/interpreter_slowest - - + + 4 + 5 + 0 + @string/cached_interpreter @string/interpreter + + 5 + 0 + + + @string/dsp_hle @string/dsp_lle_recompiler @string/dsp_lle_interpreter - - + + 0 + 1 + 2 + @string/dsp_hle @string/dsp_lle_interpreter + + 0 + 2 + + + @string/language_english @string/language_german @@ -350,14 +74,30 @@ @string/language_italian @string/language_dutch + + 0 + 1 + 2 + 3 + 4 + 5 + - + @string/device_nothing @string/device_dummy @string/device_memory_card @string/device_gci_folder + + 255 + 0 + 1 + 8 + + + @string/device_nothing @string/device_dummy @@ -367,7 +107,17 @@ @string/modem_adapter_tapserver @string/sp1_am_baseboard + + 255 + 0 + 10 + 12 + 11 + 13 + 6 + + @string/language_japanese @string/language_english @@ -380,23 +130,54 @@ @string/language_traditional_chinese @string/language_korean + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + @string/sound_mode_mono @string/sound_mode_stereo @string/sound_mode_surround + + 0 + 1 + 2 + + + @string/sensor_position_top @string/sensor_position_bottom + + 1 + 0 + + @string/log_notice @string/log_error @string/log_warning @string/log_info + + 1 + 2 + 3 + 4 + @string/log_notice @string/log_error @@ -404,14 +185,21 @@ @string/log_info @string/log_debug + + 1 + 2 + 3 + 4 + 5 + + @string/backend_opengl @string/backend_vulkan @string/backend_software @string/backend_null - OGL Vulkan @@ -419,6 +207,7 @@ Null + @string/extension_none @string/extension_nunchuk @@ -427,27 +216,56 @@ @string/extension_drums @string/extension_turntable + + 0 + 1 + 2 + 3 + 4 + 5 + + @string/accuracy_fast @string/accuracy_medium @string/accuracy_safe + + 128 + 512 + 0 + + @string/shader_compilation_specialized @string/shader_compilation_exclusive_ubershaders @string/shader_compilation_hybrid_ubershaders @string/shader_compilation_skip_drawing + + 0 + 1 + 2 + 3 + + @string/shader_compilation_specialized_description @string/shader_compilation_exclusive_ubershaders_description @string/shader_compilation_hybrid_ubershaders_description @string/shader_compilation_skip_drawing_description + + 0 + 1 + 2 + 3 + + @string/resolution_one_native @string/resolution_two_native @@ -456,14 +274,30 @@ @string/resolution_five_native @string/resolution_six_native + + 1 + 2 + 3 + 4 + 5 + 6 + + @string/multiple_off @string/multiple_two @string/multiple_four @string/multiple_eight + + 1 + 2 + 4 + 8 + + @string/filtering_default @string/multiple_two @@ -471,26 +305,54 @@ @string/multiple_eight @string/multiple_sixteen + + 0 + 1 + 2 + 3 + 4 + + @string/filtering_default @string/filtering_nearest @string/filtering_linear + + 0 + 1 + 2 + + @string/stereoscopy_off @string/stereoscopy_side_by_side @string/stereoscopy_top_and_bottom @string/stereoscopy_anaglyph + + 0 + 1 + 2 + 3 + + @string/aspect_ratio_auto @string/aspect_ratio_force_sixteen_by_nine @string/aspect_ratio_force_four_by_three @string/aspect_ratio_stretch + + 0 + 1 + 2 + 3 + + @string/country_europe @string/country_japan @@ -508,7 +370,6 @@ @string/country_unknown - @string/gcpad_disabled @string/gcpad_emulated @@ -518,13 +379,31 @@ @string/gcpad_taru_konga @string/gcpad_am_baseboard @string/gcpad_gc_adapter - GBA + @string/gba_adapter + + 0 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + + @string/wiimote_disabled @string/wiimote_emulated @string/wiimote_real + + 0 + 1 + 2 + + A B @@ -538,6 +417,7 @@ @string/gamepad_main_stick @string/gamepad_c_stick + A B @@ -559,6 +439,7 @@ @string/gamepad_home @string/gamepad_d_pad + A B @@ -582,6 +463,7 @@ Z @string/gamepad_nunchuk_stick + A B @@ -624,6 +506,7 @@ ZL ZR + @string/ir_disabled @string/ir_follow @@ -635,6 +518,7 @@ @string/double_tap_b @string/double_tap_2 + @string/double_tap_a @string/double_tap_b @@ -648,13 +532,26 @@ @string/orientation_portrait @string/orientation_auto + + 0 + 8 + 1 + -1 + + + @string/theme_default @string/theme_material_default @string/theme_green @string/theme_pink - + + 0 + 2 + 3 + 4 + @string/theme_default @string/theme_material_you @@ -662,17 +559,36 @@ @string/theme_green @string/theme_pink + + 0 + 1 + 2 + 3 + 4 + + @string/theme_mode_follow_system @string/theme_mode_light @string/theme_mode_dark + + -1 + 1 + 2 + @string/sync_gpu_never @string/sync_gpu_idle @string/sync_gpu_always + + 0 + 1 + 2 + + @string/motion_device_with_pointer @string/motion_device_without_pointer @@ -685,13 +601,27 @@ WIA RVZ + + 0 + 3 + 7 + 8 + + @string/block_size_32_kib + + 32768 + @string/block_size_2_mib + + 2097152 + + @string/block_size_32_kib @string/block_size_64_kib @@ -701,10 +631,23 @@ @string/block_size_1_mib @string/block_size_2_mib + + 32768 + 65536 + 131072 + 262144 + 524288 + 1048576 + 2097152 + @string/compression_deflate + + 0 + + @string/compression_none @string/compression_purge @@ -712,6 +655,13 @@ @string/compression_lzma @string/compression_lzma2 + + 0 + 1 + 2 + 3 + 4 + @string/compression_none @@ -720,6 +670,15 @@ @string/compression_lzma2 @string/compression_zstandard + + 0 + 2 + 3 + 4 + 5 + 6 + + 1 2 @@ -731,6 +690,17 @@ 8 9 + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 1 @@ -756,9 +726,39 @@ 21 22 + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + + @string/ntscm_space @string/ntscj_space @string/pal_space + + 0 + 1 + 2 + diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index eb78c775ea..5239daa1ad 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -90,7 +90,7 @@ Game Boy Player ROM Saves GBA ROM Path - Rom to load for this slot. Long-press to clear + Rom to load for this slot. Wii Misc Settings SD Card Settings @@ -644,6 +644,7 @@ It can efficiently compress both junk data and encrypted Wii data. Enable the vibration function for this GameCube controller. Bongo Controller Enable this if you are using bongos on this port. + GBA (Integrated) Due to the Scoped Storage policy in Android 11 and newer, you can\'t change this path. Loading Settings… diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index c77e623199..ce8d5591fd 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -125,6 +125,9 @@ static jclass s_audio_utils_class; static jmethodID s_audio_utils_get_sample_rate; static jmethodID s_audio_utils_get_frames_per_buffer; +static jclass s_gba_library_class; +static jmethodID s_on_gba_frame; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -150,11 +153,6 @@ JNIEnv* GetEnvForThread() return owned.env; } -JavaVM* GetJavaVM() -{ - return s_java_vm; -} - jclass GetStringClass() { return s_string_class; @@ -579,6 +577,16 @@ jmethodID GetAudioUtilsGetFramesPerBuffer() { return s_audio_utils_get_frames_per_buffer; } + +jmethodID GetOnGbaFrame() +{ + return s_on_gba_frame; +} + +jclass GetGbaLibraryClass() +{ + return s_gba_library_class; +} } // namespace IDCache extern "C" { @@ -821,6 +829,11 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) env->GetStaticMethodID(audio_utils_class, "getFramesPerBuffer", "()I"); env->DeleteLocalRef(audio_utils_class); + const jclass gba_library_class = env->FindClass("org/dolphinemu/dolphinemu/features/gba/GbaLibrary"); + s_gba_library_class = reinterpret_cast(env->NewGlobalRef(gba_library_class)); + s_on_gba_frame = env->GetStaticMethodID(gba_library_class, "onGbaFrame", "(I)V"); + env->DeleteLocalRef(gba_library_class); + return JNI_VERSION; } @@ -858,5 +871,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_input_detector_class); env->DeleteGlobalRef(s_permission_handler_class); env->DeleteGlobalRef(s_audio_utils_class); + env->DeleteGlobalRef(s_gba_library_class); } } diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index e6b4db6911..ad2af99560 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -124,6 +124,7 @@ jclass GetAudioUtilsClass(); jmethodID GetAudioUtilsGetSampleRate(); jmethodID GetAudioUtilsGetFramesPerBuffer(); -JavaVM* GetJavaVM(); +jclass GetGbaLibraryClass(); +jmethodID GetOnGbaFrame(); } // namespace IDCache diff --git a/Source/Android/jni/Input/InputOverrider.cpp b/Source/Android/jni/Input/InputOverrider.cpp index c7c7b6f0d7..f77995c80e 100644 --- a/Source/Android/jni/Input/InputOverrider.cpp +++ b/Source/Android/jni/Input/InputOverrider.cpp @@ -35,7 +35,7 @@ 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) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index 5f251b6223..c0fd60aa0a 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -823,8 +823,8 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCurrentTitleDescriptionUnchecked return ToJString(env, description); } -// Android Gba Emulation. -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_copyGBAFramebuffer( + +JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_features_gba_GbaLibrary_copyGbaFramebuffer( JNIEnv* env, jclass, jint slot, jobject byte_buffer) { #ifdef HAS_LIBMGBA @@ -850,25 +850,14 @@ JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_copyGBAF #endif } -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_resetGBACore(JNIEnv*, jclass, +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_gba_GbaLibrary_resetGbaCore(JNIEnv*, + jclass, jint slot) { #ifdef HAS_LIBMGBA - Core::System::GetInstance().GetSerialInterface().ResetGBACore(slot); + auto core = Core::System::GetInstance().GetSerialInterface().GetGBACore(slot); + if (core) + core->Reset(); #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)); -} } diff --git a/Source/Core/Core/HW/GBACore.cpp b/Source/Core/Core/HW/GBACore.cpp index 190aa0e113..6fe4a50dd7 100644 --- a/Source/Core/Core/HW/GBACore.cpp +++ b/Source/Core/Core/HW/GBACore.cpp @@ -246,9 +246,6 @@ 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); if (!m_save_path.empty() && !LoadSave(m_save_path.c_str())) @@ -298,9 +295,6 @@ 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() @@ -380,18 +374,12 @@ 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); @@ -457,74 +445,25 @@ void Core::SetAudioBufferSize() m_core->setAudioBufferSize(m_core, AUDIO_BUFFER_SIZE); } -// Android GBAcore emu + static void PushFrameReady(int slot) + { #ifdef ANDROID -#include -namespace -{ -static std::atomic s_on_gba_frame{nullptr}; -struct GBAThreadJNI -{ - JNIEnv* env = nullptr; - bool attached = false; + JNIEnv* env = IDCache::GetEnvForThread(); + if (!env) + return; - GBAThreadJNI() - { - JavaVM* vm = IDCache::GetJavaVM(); - if (!vm) - return; - jint rc = vm->GetEnv(reinterpret_cast(&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) + env->CallStaticVoidMethod( + IDCache::GetGbaLibraryClass(), + IDCache::GetOnGbaFrame(), + static_cast(slot)); + + if (env->ExceptionCheck()) { - s_on_gba_frame.store(mid); + env->ExceptionDescribe(); + env->ExceptionClear(); } - else - { - env->ExceptionClear(); - } - } +#endif } - } -}; -} // 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(slot)); - - if (tls.env->ExceptionCheck()) - { - tls.env->ExceptionDescribe(); - tls.env->ExceptionClear(); - } -#endif -} void Core::AddCallbacks() { @@ -537,11 +476,10 @@ void Core::AddCallbacks() callbacks.videoFrameEnded = [](void* context) { auto core = static_cast(context); { - std::unique_lock 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); + PushFrameReady(core->m_device_number); }; m_core->addCoreCallbacks(m_core, &callbacks); } diff --git a/Source/Core/Core/HW/GBACore.h b/Source/Core/Core/HW/GBACore.h index 891866640b..8b2451b790 100644 --- a/Source/Core/Core/HW/GBACore.h +++ b/Source/Core/Core/HW/GBACore.h @@ -91,9 +91,7 @@ public: mAudioBuffer* GetAudioBuffer() { return m_core->getAudioBuffer(m_core); } std::span GetVideoBuffer() const { return m_video_buffer; } - // Android game code future use. - std::string GetGameCode() const { return m_game_code; } - // android video bufffer mutex. + // android video buffer mutex. std::shared_mutex& GetVideoBufferMutex() { return m_video_buffer_mutex; } mPlatform GetPlatform() const { return m_core->platform(m_core); } @@ -144,9 +142,7 @@ private: std::string m_save_path; std::array m_rom_hash{}; std::string m_game_title; - // android game code future use. - std::string m_game_code; - // guard for m_video_buffer vulkan + // guard for m_video_buffer vulkan, surface changes caused a hard crash to main menu. mutable std::shared_mutex m_video_buffer_mutex; mCore* m_core{}; diff --git a/Source/Core/Core/HW/SI/SI.cpp b/Source/Core/Core/HW/SI/SI.cpp index f033aa8f9c..7572535abf 100644 --- a/Source/Core/Core/HW/SI/SI.cpp +++ b/Source/Core/Core/HW/SI/SI.cpp @@ -614,40 +614,4 @@ std::shared_ptr SerialInterfaceManager::GetGBACore(int channel) c #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(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(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(dev)->GetGBAGameCode(); -#endif - return ""; -} - } // namespace SerialInterface diff --git a/Source/Core/Core/HW/SI/SI.h b/Source/Core/Core/HW/SI/SI.h index 4a8b2895ae..7f9b0f0741 100644 --- a/Source/Core/Core/HW/SI/SI.h +++ b/Source/Core/Core/HW/SI/SI.h @@ -74,9 +74,6 @@ public: u32 GetPollXLines(); std::shared_ptr 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; diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp index 81402033df..36ccc15bc0 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.cpp @@ -172,14 +172,5 @@ 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 diff --git a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h index 01647bb468..a62cc4839b 100644 --- a/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h +++ b/Source/Core/Core/HW/SI/SI_DeviceGBAEmu.h @@ -33,9 +33,6 @@ public: void OnEvent(u64 userdata, s64 cycles_late) override; // android gba core std::shared_ptr GetCore() const { return m_core; } - // android gamecode match for future use. - std::string GetGBAGameTitle() const; - std::string GetGBAGameCode() const; private: enum class NextAction diff --git a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp index a7458d4df7..b33eb6ff8c 100644 --- a/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Touch/InputOverrider.cpp @@ -146,7 +146,7 @@ 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}, @@ -235,7 +235,7 @@ 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()