mirror of
https://github.com/rafaelvcaetano/melonDS-android.git
synced 2024-12-02 10:36:38 +00:00
Add support for save states
This commit is contained in:
parent
cf96f724ba
commit
dcb6040aad
@ -91,6 +91,20 @@ Java_me_magnum_melonds_MelonEmulator_resumeEmulation( JNIEnv* env, jclass type)
|
||||
MelonDSAndroid::resume();
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_magnum_melonds_MelonEmulator_saveState( JNIEnv* env, jclass type, jstring path)
|
||||
{
|
||||
const char* saveStatePath = path == nullptr ? nullptr : env->GetStringUTFChars(path, JNI_FALSE);
|
||||
return MelonDSAndroid::saveState(saveStatePath);
|
||||
}
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_me_magnum_melonds_MelonEmulator_loadState( JNIEnv* env, jclass type, jstring path)
|
||||
{
|
||||
const char* saveStatePath = path == nullptr ? nullptr : env->GetStringUTFChars(path, JNI_FALSE);
|
||||
return MelonDSAndroid::loadState(saveStatePath);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_me_magnum_melonds_MelonEmulator_stopEmulation( JNIEnv* env, jclass type)
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ import me.magnum.melonds.impl.FileSystemRomsRepository
|
||||
import me.magnum.melonds.impl.SharedPreferencesSettingsRepository
|
||||
import me.magnum.melonds.repositories.RomsRepository
|
||||
import me.magnum.melonds.repositories.SettingsRepository
|
||||
import me.magnum.melonds.ui.emulator.EmulatorViewModel
|
||||
import me.magnum.melonds.ui.inputsetup.InputSetupViewModel
|
||||
import me.magnum.melonds.ui.romlist.RomListViewModel
|
||||
|
||||
@ -36,6 +37,8 @@ class MelonDSApplication : Application() {
|
||||
return RomListViewModel(ServiceLocator[RomsRepository::class], ServiceLocator[SettingsRepository::class]) as T
|
||||
if (modelClass == InputSetupViewModel::class.java)
|
||||
return InputSetupViewModel(ServiceLocator[SettingsRepository::class]) as T
|
||||
if (modelClass == EmulatorViewModel::class.java)
|
||||
return EmulatorViewModel(ServiceLocator[SettingsRepository::class]) as T
|
||||
|
||||
throw RuntimeException("ViewModel of type " + modelClass.name + " is not supported")
|
||||
}
|
||||
|
@ -43,6 +43,12 @@ object MelonEmulator {
|
||||
@JvmStatic
|
||||
external fun stopEmulation()
|
||||
|
||||
@JvmStatic
|
||||
external fun saveState(path: String): Boolean
|
||||
|
||||
@JvmStatic
|
||||
external fun loadState(path: String): Boolean
|
||||
|
||||
@JvmStatic
|
||||
external fun onScreenTouch(x: Int, y: Int)
|
||||
|
||||
|
@ -9,10 +9,7 @@ import androidx.core.content.edit
|
||||
import com.google.gson.Gson
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import me.magnum.melonds.model.ControllerConfiguration
|
||||
import me.magnum.melonds.model.SortingMode
|
||||
import me.magnum.melonds.model.SortingOrder
|
||||
import me.magnum.melonds.model.VideoFiltering
|
||||
import me.magnum.melonds.model.*
|
||||
import me.magnum.melonds.repositories.SettingsRepository
|
||||
import me.magnum.melonds.ui.Theme
|
||||
import me.magnum.melonds.utils.PreferenceDirectoryUtils
|
||||
@ -90,6 +87,22 @@ class SharedPreferencesSettingsRepository(private val context: Context, private
|
||||
return PreferenceDirectoryUtils.getSingleDirectoryFromPreference(dirPreference)
|
||||
}
|
||||
|
||||
override fun getSaveStateDirectory(rom: Rom): String {
|
||||
val locationPreference = preferences.getString("save_state_location", "save_dir")!!
|
||||
val saveStateLocation = SaveStateLocation.valueOf(locationPreference.toUpperCase(Locale.ROOT))
|
||||
|
||||
return when (saveStateLocation) {
|
||||
SaveStateLocation.SAVE_DIR -> {
|
||||
if (saveNextToRomFile())
|
||||
File(rom.path).parentFile!!.absolutePath
|
||||
else
|
||||
getSaveFileDirectory() ?: File(rom.path).parentFile!!.absolutePath
|
||||
}
|
||||
SaveStateLocation.ROM_DIR -> File(rom.path).parentFile!!.absolutePath
|
||||
SaveStateLocation.INTERNAL_DIR -> File(context.getExternalFilesDir(null), "savestates").absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
override fun getControllerConfiguration(): ControllerConfiguration {
|
||||
if (controllerConfiguration == null) {
|
||||
try {
|
||||
|
@ -0,0 +1,7 @@
|
||||
package me.magnum.melonds.model
|
||||
|
||||
enum class SaveStateLocation {
|
||||
SAVE_DIR,
|
||||
ROM_DIR,
|
||||
INTERNAL_DIR
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package me.magnum.melonds.model
|
||||
|
||||
import java.util.*
|
||||
|
||||
data class SaveStateSlot(val slot: Int, val exists: Boolean, val path: String, val lastUsedDate: Date?)
|
@ -1,10 +1,7 @@
|
||||
package me.magnum.melonds.repositories
|
||||
|
||||
import io.reactivex.Observable
|
||||
import me.magnum.melonds.model.ControllerConfiguration
|
||||
import me.magnum.melonds.model.SortingMode
|
||||
import me.magnum.melonds.model.SortingOrder
|
||||
import me.magnum.melonds.model.VideoFiltering
|
||||
import me.magnum.melonds.model.*
|
||||
import me.magnum.melonds.ui.Theme
|
||||
|
||||
interface SettingsRepository {
|
||||
@ -21,6 +18,7 @@ interface SettingsRepository {
|
||||
fun getRomSortingOrder(): SortingOrder
|
||||
fun saveNextToRomFile(): Boolean
|
||||
fun getSaveFileDirectory(): String?
|
||||
fun getSaveStateDirectory(rom: Rom): String
|
||||
|
||||
fun getControllerConfiguration(): ControllerConfiguration
|
||||
fun showSoftInput(): Boolean
|
||||
|
@ -8,9 +8,12 @@ import android.view.View
|
||||
import android.view.Window
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
@ -21,10 +24,7 @@ import me.magnum.melonds.MelonEmulator
|
||||
import me.magnum.melonds.MelonEmulator.LoadResult
|
||||
import me.magnum.melonds.R
|
||||
import me.magnum.melonds.ServiceLocator
|
||||
import me.magnum.melonds.model.Input
|
||||
import me.magnum.melonds.model.RendererConfiguration
|
||||
import me.magnum.melonds.model.Rom
|
||||
import me.magnum.melonds.model.RomConfig
|
||||
import me.magnum.melonds.model.*
|
||||
import me.magnum.melonds.parcelables.RomParcelable
|
||||
import me.magnum.melonds.repositories.RomsRepository
|
||||
import me.magnum.melonds.repositories.SettingsRepository
|
||||
@ -33,6 +33,7 @@ import me.magnum.melonds.ui.emulator.DSRenderer.RendererListener
|
||||
import me.magnum.melonds.ui.input.*
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class EmulatorActivity : AppCompatActivity(), RendererListener {
|
||||
companion object {
|
||||
@ -49,9 +50,13 @@ class EmulatorActivity : AppCompatActivity(), RendererListener {
|
||||
|
||||
private enum class PauseMenuOptions(val textResource: Int) {
|
||||
SETTINGS(R.string.settings),
|
||||
SAVE_STATE(R.string.save_state),
|
||||
LOAD_STATE(R.string.load_state),
|
||||
EXIT(R.string.exit);
|
||||
}
|
||||
|
||||
private val viewModel: EmulatorViewModel by viewModels { ServiceLocator[ViewModelProvider.Factory::class] }
|
||||
private lateinit var loadedRom: Rom
|
||||
private lateinit var dsRenderer: DSRenderer
|
||||
private lateinit var romsRepository: RomsRepository
|
||||
private lateinit var settingsRepository: SettingsRepository
|
||||
@ -160,6 +165,7 @@ class EmulatorActivity : AppCompatActivity(), RendererListener {
|
||||
}
|
||||
|
||||
romLoader.flatMap {
|
||||
loadedRom = it
|
||||
Single.create<LoadResult> { emitter ->
|
||||
MelonEmulator.setupEmulator(getConfigDirPath(), assets)
|
||||
|
||||
@ -256,6 +262,22 @@ class EmulatorActivity : AppCompatActivity(), RendererListener {
|
||||
val settingsIntent = Intent(this@EmulatorActivity, SettingsActivity::class.java)
|
||||
startActivityForResult(settingsIntent, REQUEST_SETTINGS)
|
||||
}
|
||||
PauseMenuOptions.SAVE_STATE -> pickSaveStateSlot {
|
||||
if (!MelonEmulator.saveState(it.path))
|
||||
Toast.makeText(this@EmulatorActivity, getString(R.string.failed_save_state), Toast.LENGTH_SHORT).show()
|
||||
|
||||
MelonEmulator.resumeEmulation()
|
||||
}
|
||||
PauseMenuOptions.LOAD_STATE -> pickSaveStateSlot {
|
||||
if (!it.exists) {
|
||||
Toast.makeText(this@EmulatorActivity, getString(R.string.cant_load_empty_slot), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (!MelonEmulator.loadState(it.path))
|
||||
Toast.makeText(this@EmulatorActivity, getString(R.string.failed_load_state), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
MelonEmulator.resumeEmulation()
|
||||
}
|
||||
PauseMenuOptions.EXIT -> finish()
|
||||
}
|
||||
}
|
||||
@ -328,6 +350,23 @@ class EmulatorActivity : AppCompatActivity(), RendererListener {
|
||||
runOnUiThread { textFps.text = getString(R.string.info_fps, fps) }
|
||||
}
|
||||
|
||||
private fun pickSaveStateSlot(onSlotPicked: (SaveStateSlot) -> Unit) {
|
||||
val dateFormatter = SimpleDateFormat("EEE, dd MMMM yyyy kk:mm:ss", ConfigurationCompat.getLocales(resources.configuration)[0])
|
||||
val slots = viewModel.getRomSaveStateSlots(loadedRom)
|
||||
val options = slots.map { "${it.slot}. ${if (it.exists) dateFormatter.format(it.lastUsedDate!!) else getString(R.string.empty_slot)}" }.toTypedArray()
|
||||
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(getString(R.string.save_slot))
|
||||
.setItems(options) { _, which ->
|
||||
onSlotPicked(slots[which])
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}
|
||||
.setOnCancelListener { MelonEmulator.resumeEmulation() }
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
surfaceMain.onPause()
|
||||
|
@ -0,0 +1,33 @@
|
||||
package me.magnum.melonds.ui.emulator
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import me.magnum.melonds.model.Rom
|
||||
import me.magnum.melonds.model.SaveStateSlot
|
||||
import me.magnum.melonds.repositories.SettingsRepository
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class EmulatorViewModel(private val settingsRepository: SettingsRepository) : ViewModel() {
|
||||
fun getRomSaveStateSlots(rom: Rom): List<SaveStateSlot> {
|
||||
val saveStatePath = settingsRepository.getSaveStateDirectory(rom)
|
||||
val saveStateDirectory = File(saveStatePath)
|
||||
if (!saveStateDirectory.isDirectory) {
|
||||
// If the directory cannot be created, there's no point in returning slots
|
||||
if (!saveStateDirectory.mkdirs())
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val romFileName = File(rom.path).nameWithoutExtension
|
||||
|
||||
val saveStateSlots = mutableListOf<SaveStateSlot>()
|
||||
for (i in 1..8) {
|
||||
val saveStateFile = File(saveStateDirectory, "$romFileName.ml$i")
|
||||
if (saveStateFile.isFile)
|
||||
saveStateSlots.add(SaveStateSlot(i, true, saveStateFile.absolutePath, Date(saveStateFile.lastModified())))
|
||||
else
|
||||
saveStateSlots.add(SaveStateSlot(i, false, saveStateFile.absolutePath, null))
|
||||
}
|
||||
|
||||
return saveStateSlots
|
||||
}
|
||||
}
|
@ -10,4 +10,10 @@
|
||||
<item>dark</item>
|
||||
<item>system</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="save_state_location_values">
|
||||
<item>save_dir</item>
|
||||
<item>rom_dir</item>
|
||||
<item>internal_dir</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -3,6 +3,7 @@
|
||||
|
||||
<string name="ok">OK</string>
|
||||
<string name="no">No</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="not_set">Not set</string>
|
||||
|
||||
<string name="error_load_rom">Could not load ROM</string>
|
||||
@ -15,6 +16,9 @@
|
||||
<string name="hint_search_roms">Search ROMs…</string>
|
||||
<string name="info_fps">FPS: %1$d</string>
|
||||
<string name="info_loading">LOADING…</string>
|
||||
<string name="failed_save_state">Failed to save state</string>
|
||||
<string name="failed_load_state">Failed to load state</string>
|
||||
<string name="cant_load_empty_slot">Can\'t load an empty save state slot</string>
|
||||
|
||||
<string name="action_sort_alphabetically">Alphabetically</string>
|
||||
<string name="action_sort_recently_played">Recently played</string>
|
||||
@ -34,6 +38,10 @@
|
||||
<string name="exit">Exit</string>
|
||||
|
||||
<string name="settings">Settings</string>
|
||||
<string name="save_state">Save state</string>
|
||||
<string name="load_state">Load state</string>
|
||||
<string name="save_slot">Save slot</string>
|
||||
<string name="empty_slot"><Empty></string>
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
<string name="category_general">General</string>
|
||||
<string name="category_system">System</string>
|
||||
@ -46,6 +54,7 @@
|
||||
<string name="category_save_files">Save Files</string>
|
||||
<string name="save_next_rom">Save next to ROM file</string>
|
||||
<string name="save_file_directory">Save file directory</string>
|
||||
<string name="save_state_location">Save state location</string>
|
||||
<string name="input">Input</string>
|
||||
<string name="key_mapping">Key mapping</string>
|
||||
<string name="show_soft_input">Show soft input</string>
|
||||
@ -84,4 +93,10 @@
|
||||
<item>None</item>
|
||||
<item>Linear</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="save_state_locations">
|
||||
<item>Save file directory</item>
|
||||
<item>ROM directory</item>
|
||||
<item>Internal directory</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -71,6 +71,14 @@
|
||||
app:root_dir="/sdcard"
|
||||
app:selection_mode="single_mode"
|
||||
app:selection_type="dir_select" />
|
||||
|
||||
<ListPreference
|
||||
android:key="save_state_location"
|
||||
android:title="@string/save_state_location"
|
||||
android:summary="%s"
|
||||
android:entries="@array/save_state_locations"
|
||||
android:entryValues="@array/save_state_location_values"
|
||||
android:defaultValue="save_dir" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 87d8b6d47a59e8b4ddb00753fbf0e78abea44692
|
||||
Subproject commit c70176f1e3c297d35b20b0fc1dd4ee09503e44e3
|
Loading…
Reference in New Issue
Block a user