more settings options

This commit is contained in:
izzy2lost
2026-03-13 10:00:39 -04:00
parent 20d467c137
commit 51e16997ff
7 changed files with 478 additions and 10 deletions
@@ -23,6 +23,7 @@ static bool xemu_snapshots_dirty = true;
static GLuint g_snapshot_display_tex = 0;
static bool g_snapshot_display_flip = false;
static SDL_atomic_t g_snapshot_pending = { 0 };
static SDL_atomic_t g_reboot_pending = { 0 };
#define SNAPSHOT_PREVIEW_WIDTH 320
#define SNAPSHOT_PREVIEW_HEIGHT 240
@@ -391,6 +392,11 @@ static struct {
void xemu_android_process_snapshot_request(void)
{
if (SDL_AtomicCAS(&g_reboot_pending, 1, 0)) {
qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
return;
}
if (SDL_AtomicGet(&g_snapshot_pending) == 0) {
return;
}
@@ -469,3 +475,12 @@ Java_com_izzy2lost_x1box_MainActivity_nativeLoadSnapshot(
(void)obj;
return dispatch_snapshot(env, name, SNAP_LOAD);
}
JNIEXPORT void JNICALL
Java_com_izzy2lost_x1box_MainActivity_nativeRebootSystem(
JNIEnv *env, jobject obj)
{
(void)env;
(void)obj;
SDL_AtomicSet(&g_reboot_pending, 1);
}
@@ -434,6 +434,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
private external fun nativeSaveSnapshot(name: String): Boolean
private external fun nativeLoadSnapshot(name: String): Boolean
private external fun nativeRebootSystem()
private fun slotName(slot: Int) = "android_slot_$slot"
@@ -699,6 +700,18 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
showSnapshotSlotDialog(save = false)
}
private fun showRebootSystemConfirmation() {
MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
.setTitle(R.string.in_game_menu_reboot_title)
.setMessage(R.string.in_game_menu_reboot_message)
.setPositiveButton(R.string.in_game_menu_reboot_action) { _, _ ->
onScreenController?.resetAllInputs()
nativeRebootSystem()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun showInGameMenu() {
swipeUpGestureRecognizer.reset()
if (inGameMenuDialog?.isShowing == true) {
@@ -714,6 +727,7 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
},
getString(R.string.in_game_menu_save_state),
getString(R.string.in_game_menu_load_state),
getString(R.string.in_game_menu_reboot_system),
getString(R.string.in_game_menu_exit_to_library),
getString(R.string.in_game_menu_quit_app),
)
@@ -726,8 +740,9 @@ class MainActivity : SDLActivity(), InputManager.InputDeviceListener {
1 -> toggleOnScreenController()
2 -> showSaveStateDialog()
3 -> showLoadStateDialog()
4 -> exitToGameLibrary()
5 -> quitApp()
4 -> showRebootSystemConfirmation()
5 -> exitToGameLibrary()
6 -> quitApp()
}
}
.setOnDismissListener {
@@ -13,6 +13,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.button.MaterialButton
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.materialswitch.MaterialSwitch
import com.google.android.material.textfield.TextInputLayout
import java.io.File
@@ -31,6 +32,21 @@ class SettingsActivity : AppCompatActivity() {
val labelRes: Int,
)
private data class EepromAspectRatioOption(
val value: XboxEepromEditor.AspectRatio,
val labelRes: Int,
)
private data class EepromRefreshRateOption(
val value: XboxEepromEditor.RefreshRate,
val labelRes: Int,
)
private data class CacheClearResult(
val deletedEntries: Int,
val hadFailures: Boolean,
)
private val eepromLanguageOptions = listOf(
EepromLanguageOption(XboxEepromEditor.Language.ENGLISH, R.string.settings_eeprom_language_english),
EepromLanguageOption(XboxEepromEditor.Language.JAPANESE, R.string.settings_eeprom_language_japanese),
@@ -50,6 +66,18 @@ class SettingsActivity : AppCompatActivity() {
EepromVideoOption(XboxEepromEditor.VideoStandard.PAL_M, R.string.settings_eeprom_video_standard_pal_m),
)
private val eepromAspectRatioOptions = listOf(
EepromAspectRatioOption(XboxEepromEditor.AspectRatio.NORMAL, R.string.settings_eeprom_aspect_ratio_normal),
EepromAspectRatioOption(XboxEepromEditor.AspectRatio.WIDESCREEN, R.string.settings_eeprom_aspect_ratio_widescreen),
EepromAspectRatioOption(XboxEepromEditor.AspectRatio.LETTERBOX, R.string.settings_eeprom_aspect_ratio_letterbox),
)
private val eepromRefreshRateOptions = listOf(
EepromRefreshRateOption(XboxEepromEditor.RefreshRate.DEFAULT, R.string.settings_eeprom_refresh_rate_default),
EepromRefreshRateOption(XboxEepromEditor.RefreshRate.HZ_60, R.string.settings_eeprom_refresh_rate_60),
EepromRefreshRateOption(XboxEepromEditor.RefreshRate.HZ_50, R.string.settings_eeprom_refresh_rate_50),
)
private var pendingVulkanUri: String? = null
private var pendingVulkanName: String? = null
private var clearVulkan = false
@@ -58,11 +86,20 @@ class SettingsActivity : AppCompatActivity() {
private lateinit var tvEepromStatus: TextView
private lateinit var inputEepromLanguage: TextInputLayout
private lateinit var inputEepromVideoStandard: TextInputLayout
private lateinit var inputEepromAspectRatio: TextInputLayout
private lateinit var inputEepromRefreshRate: TextInputLayout
private lateinit var dropdownEepromLanguage: AutoCompleteTextView
private lateinit var dropdownEepromVideoStandard: AutoCompleteTextView
private lateinit var dropdownEepromAspectRatio: AutoCompleteTextView
private lateinit var dropdownEepromRefreshRate: AutoCompleteTextView
private lateinit var switchEeprom480p: MaterialSwitch
private lateinit var switchEeprom720p: MaterialSwitch
private lateinit var switchEeprom1080i: MaterialSwitch
private var selectedEepromLanguage = XboxEepromEditor.Language.ENGLISH
private var selectedEepromVideoStandard = XboxEepromEditor.VideoStandard.NTSC_M
private var selectedEepromAspectRatio = XboxEepromEditor.AspectRatio.NORMAL
private var selectedEepromRefreshRate = XboxEepromEditor.RefreshRate.DEFAULT
private var eepromEditable = false
private var eepromMissing = false
private var eepromError = false
@@ -103,14 +140,22 @@ class SettingsActivity : AppCompatActivity() {
val toggleAudioDriver = findViewById<MaterialButtonToggleGroup>(R.id.toggle_audio_driver)
val btnSave = findViewById<MaterialButton>(R.id.btn_settings_save)
val btnRedoSetup = findViewById<MaterialButton>(R.id.btn_redo_setup_wizard)
val btnClearCache = findViewById<MaterialButton>(R.id.btn_clear_system_cache)
tvVulkanDriverName = findViewById(R.id.tv_vulkan_driver_name)
val btnVulkanBrowse = findViewById<MaterialButton>(R.id.btn_vulkan_browse)
val btnVulkanClear = findViewById<MaterialButton>(R.id.btn_vulkan_clear)
tvEepromStatus = findViewById(R.id.tv_eeprom_status)
inputEepromLanguage = findViewById(R.id.input_eeprom_language)
inputEepromVideoStandard = findViewById(R.id.input_eeprom_video_standard)
inputEepromAspectRatio = findViewById(R.id.input_eeprom_aspect_ratio)
inputEepromRefreshRate = findViewById(R.id.input_eeprom_refresh_rate)
dropdownEepromLanguage = findViewById(R.id.dropdown_eeprom_language)
dropdownEepromVideoStandard = findViewById(R.id.dropdown_eeprom_video_standard)
dropdownEepromAspectRatio = findViewById(R.id.dropdown_eeprom_aspect_ratio)
dropdownEepromRefreshRate = findViewById(R.id.dropdown_eeprom_refresh_rate)
switchEeprom480p = findViewById(R.id.switch_eeprom_480p)
switchEeprom720p = findViewById(R.id.switch_eeprom_720p)
switchEeprom1080i = findViewById(R.id.switch_eeprom_1080i)
// Load current values
val renderer = prefs.getString("setting_renderer", "opengl") ?: "opengl"
@@ -198,7 +243,7 @@ class SettingsActivity : AppCompatActivity() {
setupEepromEditor()
btnSave.setOnClickListener {
fun persistSettings(): Pair<Int, Int> {
val selectedDisplayMode = when (toggleDisplayMode.checkedButtonId) {
R.id.btn_display_4_3 -> 1
R.id.btn_display_16_9 -> 2
@@ -263,7 +308,15 @@ class SettingsActivity : AppCompatActivity() {
edit.apply()
val toastResult = applyEepromEdits()
return applyEepromEdits()
}
btnClearCache.setOnClickListener {
showClearCacheConfirmation()
}
btnSave.setOnClickListener {
val toastResult = persistSettings()
Toast.makeText(this, toastResult.first, toastResult.second).show()
finish()
}
@@ -272,6 +325,8 @@ class SettingsActivity : AppCompatActivity() {
private fun setupEepromEditor() {
val languageLabels = eepromLanguageOptions.map { getString(it.labelRes) }
val videoLabels = eepromVideoOptions.map { getString(it.labelRes) }
val aspectRatioLabels = eepromAspectRatioOptions.map { getString(it.labelRes) }
val refreshRateLabels = eepromRefreshRateOptions.map { getString(it.labelRes) }
dropdownEepromLanguage.setAdapter(
ArrayAdapter(this, android.R.layout.simple_list_item_1, languageLabels)
@@ -279,6 +334,12 @@ class SettingsActivity : AppCompatActivity() {
dropdownEepromVideoStandard.setAdapter(
ArrayAdapter(this, android.R.layout.simple_list_item_1, videoLabels)
)
dropdownEepromAspectRatio.setAdapter(
ArrayAdapter(this, android.R.layout.simple_list_item_1, aspectRatioLabels)
)
dropdownEepromRefreshRate.setAdapter(
ArrayAdapter(this, android.R.layout.simple_list_item_1, refreshRateLabels)
)
dropdownEepromLanguage.setOnItemClickListener { _, _, position, _ ->
selectedEepromLanguage = eepromLanguageOptions[position].value
@@ -286,6 +347,12 @@ class SettingsActivity : AppCompatActivity() {
dropdownEepromVideoStandard.setOnItemClickListener { _, _, position, _ ->
selectedEepromVideoStandard = eepromVideoOptions[position].value
}
dropdownEepromAspectRatio.setOnItemClickListener { _, _, position, _ ->
selectedEepromAspectRatio = eepromAspectRatioOptions[position].value
}
dropdownEepromRefreshRate.setOnItemClickListener { _, _, position, _ ->
selectedEepromRefreshRate = eepromRefreshRateOptions[position].value
}
val eepromFile = resolveEepromFile()
if (!eepromFile.isFile) {
@@ -295,6 +362,15 @@ class SettingsActivity : AppCompatActivity() {
setEepromEditorEnabled(false)
setEepromLanguageSelection(selectedEepromLanguage)
setEepromVideoSelection(selectedEepromVideoStandard)
setEepromVideoSettingsSelection(
XboxEepromEditor.VideoSettings(
allow480p = false,
allow720p = false,
allow1080i = false,
aspectRatio = selectedEepromAspectRatio,
refreshRate = selectedEepromRefreshRate,
)
)
tvEepromStatus.text = getString(
R.string.settings_eeprom_status_missing,
eepromFile.absolutePath,
@@ -310,10 +386,12 @@ class SettingsActivity : AppCompatActivity() {
setEepromEditorEnabled(true)
setEepromLanguageSelection(snapshot.language)
setEepromVideoSelection(snapshot.videoStandard)
setEepromVideoSettingsSelection(snapshot.videoSettings)
val hasUnknownValues =
snapshot.rawLanguage != snapshot.language.id ||
snapshot.rawVideoStandard != snapshot.videoStandard.id
snapshot.rawVideoStandard != snapshot.videoStandard.id ||
snapshot.hasManagedVideoSettingsMismatch
tvEepromStatus.text = if (hasUnknownValues) {
getString(R.string.settings_eeprom_status_unknown, eepromFile.absolutePath)
} else {
@@ -326,6 +404,15 @@ class SettingsActivity : AppCompatActivity() {
setEepromEditorEnabled(false)
setEepromLanguageSelection(selectedEepromLanguage)
setEepromVideoSelection(selectedEepromVideoStandard)
setEepromVideoSettingsSelection(
XboxEepromEditor.VideoSettings(
allow480p = false,
allow720p = false,
allow1080i = false,
aspectRatio = selectedEepromAspectRatio,
refreshRate = selectedEepromRefreshRate,
)
)
tvEepromStatus.text = getString(
R.string.settings_eeprom_status_invalid,
eepromFile.absolutePath,
@@ -337,6 +424,15 @@ class SettingsActivity : AppCompatActivity() {
setEepromEditorEnabled(false)
setEepromLanguageSelection(selectedEepromLanguage)
setEepromVideoSelection(selectedEepromVideoStandard)
setEepromVideoSettingsSelection(
XboxEepromEditor.VideoSettings(
allow480p = false,
allow720p = false,
allow1080i = false,
aspectRatio = selectedEepromAspectRatio,
refreshRate = selectedEepromRefreshRate,
)
)
tvEepromStatus.text = getString(
R.string.settings_eeprom_status_error,
eepromFile.absolutePath,
@@ -357,6 +453,13 @@ class SettingsActivity : AppCompatActivity() {
resolveEepromFile(),
selectedEepromLanguage,
selectedEepromVideoStandard,
XboxEepromEditor.VideoSettings(
allow480p = switchEeprom480p.isChecked,
allow720p = switchEeprom720p.isChecked,
allow1080i = switchEeprom1080i.isChecked,
aspectRatio = selectedEepromAspectRatio,
refreshRate = selectedEepromRefreshRate,
),
)
if (changed) {
Pair(R.string.settings_saved_with_eeprom, Toast.LENGTH_SHORT)
@@ -371,8 +474,15 @@ class SettingsActivity : AppCompatActivity() {
private fun setEepromEditorEnabled(enabled: Boolean) {
inputEepromLanguage.isEnabled = enabled
inputEepromVideoStandard.isEnabled = enabled
inputEepromAspectRatio.isEnabled = enabled
inputEepromRefreshRate.isEnabled = enabled
dropdownEepromLanguage.isEnabled = enabled
dropdownEepromVideoStandard.isEnabled = enabled
dropdownEepromAspectRatio.isEnabled = enabled
dropdownEepromRefreshRate.isEnabled = enabled
switchEeprom480p.isEnabled = enabled
switchEeprom720p.isEnabled = enabled
switchEeprom1080i.isEnabled = enabled
}
private fun setEepromLanguageSelection(language: XboxEepromEditor.Language) {
@@ -389,6 +499,134 @@ class SettingsActivity : AppCompatActivity() {
dropdownEepromVideoStandard.setText(getString(option.labelRes), false)
}
private fun setEepromVideoSettingsSelection(videoSettings: XboxEepromEditor.VideoSettings) {
switchEeprom480p.isChecked = videoSettings.allow480p
switchEeprom720p.isChecked = videoSettings.allow720p
switchEeprom1080i.isChecked = videoSettings.allow1080i
setEepromAspectRatioSelection(videoSettings.aspectRatio)
setEepromRefreshRateSelection(videoSettings.refreshRate)
}
private fun setEepromAspectRatioSelection(aspectRatio: XboxEepromEditor.AspectRatio) {
selectedEepromAspectRatio = aspectRatio
val option = eepromAspectRatioOptions.firstOrNull { it.value == aspectRatio }
?: eepromAspectRatioOptions.first()
dropdownEepromAspectRatio.setText(getString(option.labelRes), false)
}
private fun setEepromRefreshRateSelection(refreshRate: XboxEepromEditor.RefreshRate) {
selectedEepromRefreshRate = refreshRate
val option = eepromRefreshRateOptions.firstOrNull { it.value == refreshRate }
?: eepromRefreshRateOptions.first()
dropdownEepromRefreshRate.setText(getString(option.labelRes), false)
}
private fun showClearCacheConfirmation() {
MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
.setTitle(R.string.settings_clear_cache_title)
.setMessage(R.string.settings_clear_cache_message)
.setPositiveButton(R.string.settings_clear_cache_action) { _, _ ->
val result = clearSystemCache()
val messageRes = when {
result.hadFailures -> R.string.settings_clear_cache_partial
result.deletedEntries > 0 -> R.string.settings_clear_cache_success
else -> R.string.settings_clear_cache_empty
}
Toast.makeText(this, getString(messageRes), Toast.LENGTH_SHORT).show()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun clearSystemCache(): CacheClearResult {
var result = CacheClearResult(0, false)
val cacheRoots = buildList {
add(cacheDir)
add(codeCacheDir)
externalCacheDir?.let { add(it) }
}
for (root in cacheRoots.distinctBy { it.absolutePath }) {
result = mergeCacheClearResults(result, clearDirectoryChildren(root))
}
val persistentRoots = buildList {
add(filesDir)
getExternalFilesDir(null)?.let { add(it) }
}
for (root in persistentRoots.distinctBy { it.absolutePath }) {
result = mergeCacheClearResults(result, clearPersistentCacheEntries(root))
}
return result
}
private fun clearDirectoryChildren(dir: File?): CacheClearResult {
if (dir == null || !dir.exists()) {
return CacheClearResult(0, false)
}
val children = dir.listFiles() ?: return CacheClearResult(0, false)
var deletedEntries = 0
var hadFailures = false
for (child in children) {
val deleted = runCatching { child.deleteRecursively() }.getOrDefault(false)
if (deleted) {
deletedEntries++
} else {
hadFailures = true
}
}
return CacheClearResult(deletedEntries, hadFailures)
}
private fun clearPersistentCacheEntries(root: File): CacheClearResult {
if (!root.exists() || !root.isDirectory) {
return CacheClearResult(0, false)
}
val children = root.listFiles() ?: return CacheClearResult(0, false)
var deletedEntries = 0
var hadFailures = false
for (child in children) {
if (isPersistentCacheEntry(child.name)) {
val deleted = runCatching { child.deleteRecursively() }.getOrDefault(false)
if (deleted) {
deletedEntries++
} else {
hadFailures = true
}
continue
}
if (child.isDirectory) {
val nested = clearPersistentCacheEntries(child)
deletedEntries += nested.deletedEntries
hadFailures = hadFailures || nested.hadFailures
}
}
return CacheClearResult(deletedEntries, hadFailures)
}
private fun isPersistentCacheEntry(name: String): Boolean {
return name == "shaders" ||
name == "shader_cache_list" ||
name.startsWith("scache-") ||
name.startsWith("vk_pipeline_cache_")
}
private fun mergeCacheClearResults(
first: CacheClearResult,
second: CacheClearResult,
): CacheClearResult {
return CacheClearResult(
deletedEntries = first.deletedEntries + second.deletedEntries,
hadFailures = first.hadFailures || second.hadFailures,
)
}
private fun resolveEepromFile(): File {
val base = getExternalFilesDir(null) ?: filesDir
return File(File(base, "x1box"), "eeprom.bin")
@@ -16,6 +16,24 @@ internal object XboxEepromEditor {
private const val VIDEO_STANDARD_OFFSET = 0x58
private const val LANGUAGE_OFFSET = 0x90
private const val VIDEO_SETTINGS_OFFSET = 0x94
private const val VIDEO_SETTINGS_WIDESCREEN = 0x00010000u
private const val VIDEO_SETTINGS_720P = 0x00020000u
private const val VIDEO_SETTINGS_1080I = 0x00040000u
private const val VIDEO_SETTINGS_480P = 0x00080000u
private const val VIDEO_SETTINGS_LETTERBOX = 0x00100000u
private const val VIDEO_SETTINGS_60HZ = 0x00400000u
private const val VIDEO_SETTINGS_50HZ = 0x00800000u
private val MANAGED_VIDEO_SETTINGS_MASK =
VIDEO_SETTINGS_WIDESCREEN or
VIDEO_SETTINGS_720P or
VIDEO_SETTINGS_1080I or
VIDEO_SETTINGS_480P or
VIDEO_SETTINGS_LETTERBOX or
VIDEO_SETTINGS_60HZ or
VIDEO_SETTINGS_50HZ
enum class Language(val id: UInt) {
ENGLISH(0x00000001u),
@@ -44,12 +62,71 @@ internal object XboxEepromEditor {
}
}
enum class AspectRatio(val bits: UInt) {
NORMAL(0u),
WIDESCREEN(VIDEO_SETTINGS_WIDESCREEN),
LETTERBOX(VIDEO_SETTINGS_LETTERBOX);
companion object {
fun fromBits(bits: UInt): AspectRatio? = when (bits and
(VIDEO_SETTINGS_WIDESCREEN or VIDEO_SETTINGS_LETTERBOX)) {
0u -> NORMAL
VIDEO_SETTINGS_WIDESCREEN -> WIDESCREEN
VIDEO_SETTINGS_LETTERBOX -> LETTERBOX
else -> null
}
}
}
enum class RefreshRate(val bits: UInt) {
DEFAULT(0u),
HZ_60(VIDEO_SETTINGS_60HZ),
HZ_50(VIDEO_SETTINGS_50HZ);
companion object {
fun fromBits(bits: UInt): RefreshRate? = when (bits and
(VIDEO_SETTINGS_60HZ or VIDEO_SETTINGS_50HZ)) {
0u -> DEFAULT
VIDEO_SETTINGS_60HZ -> HZ_60
VIDEO_SETTINGS_50HZ -> HZ_50
else -> null
}
}
}
data class VideoSettings(
val allow480p: Boolean,
val allow720p: Boolean,
val allow1080i: Boolean,
val aspectRatio: AspectRatio,
val refreshRate: RefreshRate,
) {
fun toManagedBits(): UInt {
var bits = aspectRatio.bits or refreshRate.bits
if (allow480p) {
bits = bits or VIDEO_SETTINGS_480P
}
if (allow720p) {
bits = bits or VIDEO_SETTINGS_720P
}
if (allow1080i) {
bits = bits or VIDEO_SETTINGS_1080I
}
return bits
}
}
data class Snapshot(
val language: Language,
val videoStandard: VideoStandard,
val videoSettings: VideoSettings,
val rawLanguage: UInt,
val rawVideoStandard: UInt,
)
val rawVideoSettings: UInt,
) {
val hasManagedVideoSettingsMismatch: Boolean
get() = (rawVideoSettings and MANAGED_VIDEO_SETTINGS_MASK) != videoSettings.toManagedBits()
}
@Throws(IOException::class, IllegalArgumentException::class)
fun load(file: File): Snapshot {
@@ -58,28 +135,49 @@ internal object XboxEepromEditor {
val rawLanguage = readLeUInt32(data, LANGUAGE_OFFSET)
val rawVideoStandard = readLeUInt32(data, VIDEO_STANDARD_OFFSET)
val rawVideoSettings = readLeUInt32(data, VIDEO_SETTINGS_OFFSET)
return Snapshot(
language = Language.fromId(rawLanguage) ?: Language.ENGLISH,
videoStandard = VideoStandard.fromId(rawVideoStandard) ?: VideoStandard.NTSC_M,
videoSettings = VideoSettings(
allow480p = (rawVideoSettings and VIDEO_SETTINGS_480P) != 0u,
allow720p = (rawVideoSettings and VIDEO_SETTINGS_720P) != 0u,
allow1080i = (rawVideoSettings and VIDEO_SETTINGS_1080I) != 0u,
aspectRatio = AspectRatio.fromBits(rawVideoSettings) ?: AspectRatio.NORMAL,
refreshRate = RefreshRate.fromBits(rawVideoSettings) ?: RefreshRate.DEFAULT,
),
rawLanguage = rawLanguage,
rawVideoStandard = rawVideoStandard,
rawVideoSettings = rawVideoSettings,
)
}
@Throws(IOException::class, IllegalArgumentException::class)
fun apply(file: File, language: Language, videoStandard: VideoStandard): Boolean {
fun apply(
file: File,
language: Language,
videoStandard: VideoStandard,
videoSettings: VideoSettings,
): Boolean {
val data = file.readBytes()
ensureValidSize(file, data)
val currentLanguage = readLeUInt32(data, LANGUAGE_OFFSET)
val currentVideoStandard = readLeUInt32(data, VIDEO_STANDARD_OFFSET)
if (currentLanguage == language.id && currentVideoStandard == videoStandard.id) {
val currentVideoSettings = readLeUInt32(data, VIDEO_SETTINGS_OFFSET)
val nextVideoSettings =
(currentVideoSettings and MANAGED_VIDEO_SETTINGS_MASK.inv()) or videoSettings.toManagedBits()
if (currentLanguage == language.id &&
currentVideoStandard == videoStandard.id &&
currentVideoSettings == nextVideoSettings
) {
return false
}
writeLeUInt32(data, LANGUAGE_OFFSET, language.id)
writeLeUInt32(data, VIDEO_STANDARD_OFFSET, videoStandard.id)
writeLeUInt32(data, VIDEO_SETTINGS_OFFSET, nextVideoSettings)
val factoryChecksum = xboxChecksum(data, FACTORY_CHECKSUM_START, FACTORY_CHECKSUM_LENGTH)
writeLeUInt32(data, FACTORY_CHECKSUM_OFFSET, factoryChecksum)
@@ -519,6 +519,7 @@
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:hint="@string/settings_eeprom_video_standard">
<AutoCompleteTextView
@@ -529,6 +530,67 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="6dp"
android:text="@string/settings_eeprom_resolution_modes"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
android:textColor="@color/xemu_text_muted" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_eeprom_480p"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_eeprom_resolution_480p"
android:textColor="@color/xemu_text_muted" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_eeprom_720p"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_eeprom_resolution_720p"
android:textColor="@color/xemu_text_muted" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_eeprom_1080i"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:text="@string/settings_eeprom_resolution_1080i"
android:textColor="@color/xemu_text_muted" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_eeprom_aspect_ratio"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:hint="@string/settings_eeprom_aspect_ratio">
<AutoCompleteTextView
android:id="@+id/dropdown_eeprom_aspect_ratio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/input_eeprom_refresh_rate"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/settings_eeprom_refresh_rate">
<AutoCompleteTextView
android:id="@+id/dropdown_eeprom_refresh_rate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -537,6 +599,22 @@
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="@color/xemu_text_muted" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp"
android:text="@string/settings_section_maintenance"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="@color/xemu_green" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_clear_system_cache"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/settings_clear_cache_action" />
<!-- Save button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_settings_save"
+24 -1
View File
@@ -150,23 +150,46 @@
<string name="settings_eeprom_video_standard_ntsc_j">NTSC-J</string>
<string name="settings_eeprom_video_standard_pal_i">PAL-I</string>
<string name="settings_eeprom_video_standard_pal_m">PAL-M</string>
<string name="settings_eeprom_resolution_modes">EEPROM Resolution Modes</string>
<string name="settings_eeprom_resolution_480p">Enable 480p</string>
<string name="settings_eeprom_resolution_720p">Enable 720p</string>
<string name="settings_eeprom_resolution_1080i">Enable 1080i</string>
<string name="settings_eeprom_aspect_ratio">EEPROM Aspect Ratio</string>
<string name="settings_eeprom_aspect_ratio_normal">Normal (4:3)</string>
<string name="settings_eeprom_aspect_ratio_widescreen">Widescreen (16:9)</string>
<string name="settings_eeprom_aspect_ratio_letterbox">Letterbox</string>
<string name="settings_eeprom_refresh_rate">EEPROM Refresh Rate</string>
<string name="settings_eeprom_refresh_rate_default">Default / Unset</string>
<string name="settings_eeprom_refresh_rate_60">60 Hz</string>
<string name="settings_eeprom_refresh_rate_50">50 Hz</string>
<string name="settings_eeprom_status_ready">EEPROM file: %1$s</string>
<string name="settings_eeprom_status_missing">EEPROM not found at %1$s. Launch a game once to create it.</string>
<string name="settings_eeprom_status_invalid">EEPROM is invalid at %1$s.</string>
<string name="settings_eeprom_status_error">Failed to read EEPROM at %1$s.</string>
<string name="settings_eeprom_status_unknown">EEPROM file: %1$s (contains unknown values; saving will normalize them)</string>
<string name="settings_eeprom_hint">Updates language and video standard in eeprom.bin. Restart emulation after saving.</string>
<string name="settings_eeprom_hint">Updates language and Xbox video settings in eeprom.bin. HD modes depend on the game, cable, and video standard. Restart emulation after saving.</string>
<string name="settings_saved">Settings saved</string>
<string name="settings_saved_with_eeprom">Settings and EEPROM saved</string>
<string name="settings_saved_eeprom_missing">Settings saved. Launch a game once, then edit EEPROM.</string>
<string name="settings_saved_eeprom_failed">Settings saved, but EEPROM update failed</string>
<string name="settings_section_maintenance">Maintenance</string>
<string name="settings_clear_cache_title">Clear Cache</string>
<string name="settings_clear_cache_message">Clear emulator cache files now? This removes shader and app cache data, but keeps your games, saves, and HDD image.</string>
<string name="settings_clear_cache_action">Clear Cache</string>
<string name="settings_clear_cache_success">Cache cleared</string>
<string name="settings_clear_cache_partial">Some cache files could not be removed</string>
<string name="settings_clear_cache_empty">Cache is already clear</string>
<string name="in_game_menu_title">Game Menu</string>
<string name="in_game_menu_resume">Resume</string>
<string name="in_game_menu_restart_game">Restart Game</string>
<string name="in_game_menu_show_touch_controls">Show Touch Controls</string>
<string name="in_game_menu_hide_touch_controls">Hide Touch Controls</string>
<string name="in_game_menu_reboot_system">Reboot System</string>
<string name="in_game_menu_reboot_title">Reboot System</string>
<string name="in_game_menu_reboot_message">Are you sure you want to reboot the emulated Xbox system?</string>
<string name="in_game_menu_reboot_action">Reboot</string>
<string name="in_game_menu_exit_to_library">Exit to Game Library</string>
<string name="in_game_menu_quit_app">Quit App</string>
<string name="in_game_menu_save_state">Save State</string>
+2 -1
View File
@@ -563,7 +563,8 @@ static void fe_method(MCPXAPUState *d, uint32_t method, uint32_t argument)
DPRINTF("idle voice %d\n", argument);
d->set_irq = true;
} else {
assert(false);
DPRINTF("ignoring idle voice %d without FETFORCE1 request\n",
argument);
}
break;
default: