Merge feature/genisis: Genesis emulation with bundled Genesis Plus GX core
This commit is contained in:
BIN
app/src/main/assets/cores/genesis_plus_gx_libretro_android.so
Executable file
BIN
app/src/main/assets/cores/genesis_plus_gx_libretro_android.so
Executable file
Binary file not shown.
@@ -43,6 +43,7 @@ enum class ConsoleType(
|
||||
displayName = "Sega Genesis",
|
||||
manufacturer = "Sega",
|
||||
fileExtensions = listOf("gen", "md", "smd", "bin"),
|
||||
isImplemented = true,
|
||||
fallbackAspectRatio = 4f / 3f,
|
||||
libretroThumbnailRepo = "Sega_-_Mega_Drive_-_Genesis"
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.lazy.emulate.emulation
|
||||
|
||||
import android.content.Context
|
||||
import com.lazy.emulate.emulation.cores.GenesisCore
|
||||
import com.lazy.emulate.emulation.cores.NesCore
|
||||
import com.lazy.emulate.emulation.cores.PlaceholderCore
|
||||
import com.lazy.emulate.emulation.cores.Ps1Core
|
||||
@@ -18,7 +19,7 @@ object EmulationEngine {
|
||||
ConsoleType.NES -> NesCore(context, buttonMappingManager)
|
||||
ConsoleType.SNES -> SnesCore(context, buttonMappingManager)
|
||||
ConsoleType.N64 -> PlaceholderCore(ConsoleType.N64)
|
||||
ConsoleType.GENESIS -> PlaceholderCore(ConsoleType.GENESIS)
|
||||
ConsoleType.GENESIS -> GenesisCore(context, buttonMappingManager)
|
||||
ConsoleType.PS2 -> PlaceholderCore(ConsoleType.PS2)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
package com.lazy.emulate.emulation.cores
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import com.lazy.emulate.emulation.ConsoleType
|
||||
import com.lazy.emulate.emulation.DisplayMode
|
||||
import com.lazy.emulate.emulation.EmulatorCore
|
||||
import com.lazy.emulate.emulation.NativeLibretro
|
||||
import com.lazy.emulate.emulation.SaveSlotInfo
|
||||
import com.lazy.emulate.input.ButtonMappingManager
|
||||
import com.lazy.emulate.input.GamepadButton
|
||||
import java.io.File
|
||||
|
||||
class GenesisCore(
|
||||
private val context: Context,
|
||||
private val buttonMappingManager: ButtonMappingManager? = null
|
||||
) : EmulatorCore {
|
||||
|
||||
override val consoleType = ConsoleType.GENESIS
|
||||
|
||||
override var isRunning: Boolean = false
|
||||
private set
|
||||
|
||||
private var buttonState: Int = 0
|
||||
|
||||
private val coresDir = File(context.filesDir, "cores")
|
||||
private val systemDir = File(context.filesDir, "system")
|
||||
private val baseSavesDir = File(context.filesDir, "saves")
|
||||
private lateinit var savesDir: File
|
||||
|
||||
override fun loadRom(path: String): Boolean {
|
||||
coresDir.mkdirs()
|
||||
systemDir.mkdirs()
|
||||
|
||||
val romName = File(path).nameWithoutExtension
|
||||
savesDir = File(baseSavesDir, romName)
|
||||
savesDir.mkdirs()
|
||||
|
||||
NativeLibretro.nativeSetSystemDir(systemDir.absolutePath)
|
||||
NativeLibretro.nativeSetSaveDir(savesDir.absolutePath)
|
||||
|
||||
val corePath = findCorePath()
|
||||
if (corePath == null) {
|
||||
Log.e(TAG, "Genesis core .so not found. Place genesis_plus_gx_libretro_android.so in ${coresDir.absolutePath}")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!NativeLibretro.nativeLoadCore(corePath)) {
|
||||
Log.e(TAG, "Failed to load core: $corePath")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!NativeLibretro.nativeLoadGame(path)) {
|
||||
Log.e(TAG, "Failed to load game: $path")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.i(TAG, "Game loaded successfully: $path")
|
||||
return true
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
isRunning = true
|
||||
NativeLibretro.nativeStart()
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
isRunning = false
|
||||
NativeLibretro.nativePause()
|
||||
}
|
||||
|
||||
override fun resume() {
|
||||
isRunning = true
|
||||
NativeLibretro.nativeResume()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
isRunning = false
|
||||
NativeLibretro.nativeStop()
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
NativeLibretro.nativeReset()
|
||||
}
|
||||
|
||||
override fun setSurface(surface: Surface) {
|
||||
NativeLibretro.nativeSetSurface(surface)
|
||||
}
|
||||
|
||||
override fun onButtonPressed(button: GamepadButton) {
|
||||
val bit = mapButtonToRetroId(button)
|
||||
if (bit >= 0) {
|
||||
buttonState = buttonState or (1 shl bit)
|
||||
NativeLibretro.nativeSetButtonState(buttonState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onButtonReleased(button: GamepadButton) {
|
||||
val bit = mapButtonToRetroId(button)
|
||||
if (bit >= 0) {
|
||||
buttonState = buttonState and (1 shl bit).inv()
|
||||
NativeLibretro.nativeSetButtonState(buttonState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAnalogStick(x: Float, y: Float, stickIndex: Int) {
|
||||
// Genesis has no analog sticks
|
||||
}
|
||||
|
||||
override fun saveState(slot: Int): Boolean {
|
||||
val path = File(savesDir, "state_$slot.sav").absolutePath
|
||||
return NativeLibretro.nativeSaveState(path)
|
||||
}
|
||||
|
||||
override fun loadState(slot: Int): Boolean {
|
||||
val path = File(savesDir, "state_$slot.sav").absolutePath
|
||||
return NativeLibretro.nativeLoadState(path)
|
||||
}
|
||||
|
||||
override fun deleteState(slot: Int): Boolean {
|
||||
val state = File(savesDir, "state_$slot.sav")
|
||||
val screenshot = File(savesDir, "state_$slot.jpg")
|
||||
val deleted = state.exists() && state.delete()
|
||||
if (screenshot.exists()) screenshot.delete()
|
||||
return deleted
|
||||
}
|
||||
|
||||
override fun getSaveSlotInfo(slot: Int): SaveSlotInfo {
|
||||
val file = File(savesDir, "state_$slot.sav")
|
||||
val screenshot = File(savesDir, "state_$slot.jpg")
|
||||
return SaveSlotInfo(
|
||||
slot = slot,
|
||||
exists = file.exists(),
|
||||
lastModified = if (file.exists()) file.lastModified() else 0L,
|
||||
screenshotPath = if (screenshot.exists()) screenshot.absolutePath else null
|
||||
)
|
||||
}
|
||||
|
||||
override fun getScreenshotPath(slot: Int): String =
|
||||
File(savesDir, "state_$slot.jpg").absolutePath
|
||||
|
||||
override fun setAudioEnabled(enabled: Boolean) {
|
||||
NativeLibretro.nativeSetAudioEnabled(enabled)
|
||||
}
|
||||
|
||||
override fun setFrameSkip(skip: Int) {
|
||||
NativeLibretro.nativeSetFrameSkip(skip)
|
||||
}
|
||||
|
||||
override fun setDisplayMode(mode: DisplayMode) {
|
||||
NativeLibretro.nativeSetDisplayMode(mode.nativeValue)
|
||||
}
|
||||
|
||||
private fun findCorePath(): String? {
|
||||
val names = listOf(
|
||||
"genesis_plus_gx_libretro_android.so",
|
||||
"genesis_plus_gx_libretro.so",
|
||||
"picodrive_libretro_android.so",
|
||||
"picodrive_libretro.so",
|
||||
)
|
||||
// Check internal cores dir first
|
||||
for (name in names) {
|
||||
val file = File(coresDir, name)
|
||||
if (file.exists()) return file.absolutePath
|
||||
}
|
||||
// Extract bundled core from assets
|
||||
for (name in names) {
|
||||
try {
|
||||
context.assets.open("cores/$name").use { input ->
|
||||
val dest = File(coresDir, name)
|
||||
dest.outputStream().use { output -> input.copyTo(output) }
|
||||
dest.setExecutable(true)
|
||||
Log.i(TAG, "Extracted bundled core: $name")
|
||||
return dest.absolutePath
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Asset doesn't exist, try next
|
||||
}
|
||||
}
|
||||
// Fallback: auto-import from /sdcard/Games/cores/
|
||||
val sdcardCores = File("/sdcard/Games/cores")
|
||||
if (sdcardCores.exists()) {
|
||||
for (name in names) {
|
||||
val sdFile = File(sdcardCores, name)
|
||||
if (sdFile.exists()) {
|
||||
val dest = File(coresDir, name)
|
||||
try {
|
||||
sdFile.copyTo(dest, overwrite = true)
|
||||
dest.setExecutable(true)
|
||||
Log.i(TAG, "Imported core from sdcard: $name")
|
||||
return dest.absolutePath
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to import core: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun mapButtonToRetroId(button: GamepadButton): Int {
|
||||
if (buttonMappingManager != null) {
|
||||
return buttonMappingManager.getRetroId(consoleType, button)
|
||||
}
|
||||
return defaultMapButtonToRetroId(button)
|
||||
}
|
||||
|
||||
private fun defaultMapButtonToRetroId(button: GamepadButton): Int {
|
||||
// Genesis Plus GX default retropad -> Genesis mapping:
|
||||
// RETRO_Y (1) -> Genesis A
|
||||
// RETRO_B (0) -> Genesis B
|
||||
// RETRO_A (8) -> Genesis C
|
||||
// RETRO_L (10) -> Genesis X (6-button)
|
||||
// RETRO_X (9) -> Genesis Y (6-button)
|
||||
// RETRO_R (11) -> Genesis Z (6-button)
|
||||
// RETRO_SELECT (2) -> Mode
|
||||
// RETRO_START (3) -> Start
|
||||
return when (button) {
|
||||
GamepadButton.FACE_BOTTOM -> 1 // Genesis A
|
||||
GamepadButton.FACE_RIGHT -> 0 // Genesis B
|
||||
GamepadButton.FACE_LEFT -> 8 // Genesis C
|
||||
GamepadButton.FACE_TOP -> 9 // Genesis Y (6-button)
|
||||
GamepadButton.L1 -> 10 // Genesis X (6-button)
|
||||
GamepadButton.R1 -> 11 // Genesis Z (6-button)
|
||||
GamepadButton.C_BUTTON -> 8 // alt binding for C
|
||||
GamepadButton.Z_BUTTON -> 11 // alt binding for Z
|
||||
GamepadButton.DPAD_UP -> 4
|
||||
GamepadButton.DPAD_DOWN -> 5
|
||||
GamepadButton.DPAD_LEFT -> 6
|
||||
GamepadButton.DPAD_RIGHT -> 7
|
||||
GamepadButton.MODE -> 2
|
||||
GamepadButton.SELECT -> 2
|
||||
GamepadButton.START -> 3
|
||||
else -> -1
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GenesisCore"
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,23 @@ class ButtonMappingManager(private val preferencesManager: PreferencesManager) {
|
||||
GamepadButton.DPAD_RIGHT to 7,
|
||||
GamepadButton.SELECT to 2,
|
||||
GamepadButton.START to 3
|
||||
),
|
||||
ConsoleType.GENESIS to mapOf(
|
||||
// Genesis Plus GX default retropad -> Genesis mapping
|
||||
GamepadButton.FACE_BOTTOM to 1, // Genesis A (RETRO_Y)
|
||||
GamepadButton.FACE_RIGHT to 0, // Genesis B (RETRO_B)
|
||||
GamepadButton.FACE_LEFT to 8, // Genesis C (RETRO_A)
|
||||
GamepadButton.FACE_TOP to 9, // Genesis Y (6-button)
|
||||
GamepadButton.L1 to 10, // Genesis X (6-button)
|
||||
GamepadButton.R1 to 11, // Genesis Z (6-button)
|
||||
GamepadButton.C_BUTTON to 8, // alt C
|
||||
GamepadButton.Z_BUTTON to 11, // alt Z
|
||||
GamepadButton.DPAD_UP to 4,
|
||||
GamepadButton.DPAD_DOWN to 5,
|
||||
GamepadButton.DPAD_LEFT to 6,
|
||||
GamepadButton.DPAD_RIGHT to 7,
|
||||
GamepadButton.MODE to 2, // Genesis Mode
|
||||
GamepadButton.START to 3
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user