mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
add xbox hdd formatter
This commit is contained in:
@@ -950,6 +950,7 @@ add_library(xemu SHARED
|
||||
xemu_android.cpp
|
||||
android_crash_handler.cpp
|
||||
vmstate_if_android.c
|
||||
xemu_hdd_tools_jni.c
|
||||
)
|
||||
|
||||
target_compile_definitions(xemu PRIVATE ANDROID __ANDROID__ XBOX CONFIG_SDL CONFIG_OPENGL)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.format.Formatter
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AutoCompleteTextView
|
||||
import android.widget.TextView
|
||||
@@ -81,9 +82,11 @@ class SettingsActivity : AppCompatActivity() {
|
||||
private var pendingVulkanUri: String? = null
|
||||
private var pendingVulkanName: String? = null
|
||||
private var clearVulkan = false
|
||||
private var isInitializingHdd = false
|
||||
|
||||
private lateinit var tvVulkanDriverName: TextView
|
||||
private lateinit var tvEepromStatus: TextView
|
||||
private lateinit var tvHddToolsStatus: TextView
|
||||
private lateinit var inputEepromLanguage: TextInputLayout
|
||||
private lateinit var inputEepromVideoStandard: TextInputLayout
|
||||
private lateinit var inputEepromAspectRatio: TextInputLayout
|
||||
@@ -141,10 +144,12 @@ class SettingsActivity : AppCompatActivity() {
|
||||
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)
|
||||
val btnInitializeRetailHdd = findViewById<MaterialButton>(R.id.btn_initialize_retail_hdd)
|
||||
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)
|
||||
tvHddToolsStatus = findViewById(R.id.tv_hdd_tools_status)
|
||||
inputEepromLanguage = findViewById(R.id.input_eeprom_language)
|
||||
inputEepromVideoStandard = findViewById(R.id.input_eeprom_video_standard)
|
||||
inputEepromAspectRatio = findViewById(R.id.input_eeprom_aspect_ratio)
|
||||
@@ -242,6 +247,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
setupEepromEditor()
|
||||
refreshHddToolsPreview(btnInitializeRetailHdd)
|
||||
|
||||
fun persistSettings(): Pair<Int, Int> {
|
||||
val selectedDisplayMode = when (toggleDisplayMode.checkedButtonId) {
|
||||
@@ -315,6 +321,10 @@ class SettingsActivity : AppCompatActivity() {
|
||||
showClearCacheConfirmation()
|
||||
}
|
||||
|
||||
btnInitializeRetailHdd.setOnClickListener {
|
||||
showInitializeHddLayoutPicker(btnInitializeRetailHdd)
|
||||
}
|
||||
|
||||
btnSave.setOnClickListener {
|
||||
val toastResult = persistSettings()
|
||||
Toast.makeText(this, toastResult.first, toastResult.second).show()
|
||||
@@ -538,6 +548,216 @@ class SettingsActivity : AppCompatActivity() {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showInitializeHddLayoutPicker(button: MaterialButton) {
|
||||
val hddFile = resolveHddFile()
|
||||
if (hddFile == null) {
|
||||
refreshHddToolsPreview(button)
|
||||
return
|
||||
}
|
||||
|
||||
val inspection = runCatching { XboxHddFormatter.inspect(hddFile) }.getOrElse { error ->
|
||||
tvHddToolsStatus.text = getString(
|
||||
R.string.settings_hdd_status_error,
|
||||
error.message ?: hddFile.absolutePath,
|
||||
)
|
||||
button.isEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
if (!inspection.supportsRetailFormat) {
|
||||
refreshHddToolsState(button)
|
||||
return
|
||||
}
|
||||
|
||||
val supportedLayouts = XboxHddFormatter.supportedLayouts(inspection).toSet()
|
||||
if (supportedLayouts.isEmpty()) {
|
||||
refreshHddToolsState(button)
|
||||
return
|
||||
}
|
||||
|
||||
val allLayouts = XboxHddFormatter.Layout.entries
|
||||
val labels = allLayouts
|
||||
.map { layout ->
|
||||
val label = getString(hddLayoutLabelRes(layout))
|
||||
val availability = XboxHddFormatter.availabilityFor(inspection, layout)
|
||||
if (availability == XboxHddFormatter.LayoutAvailability.AVAILABLE) {
|
||||
label
|
||||
} else {
|
||||
getString(
|
||||
R.string.settings_hdd_layout_unavailable_format,
|
||||
label,
|
||||
getString(hddLayoutUnavailableReasonRes(availability)),
|
||||
)
|
||||
}
|
||||
}
|
||||
.toTypedArray()
|
||||
MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
|
||||
.setTitle(R.string.settings_hdd_layout_pick_title)
|
||||
.setItems(labels) { _, which ->
|
||||
val layout = allLayouts[which]
|
||||
val availability = XboxHddFormatter.availabilityFor(inspection, layout)
|
||||
if (availability == XboxHddFormatter.LayoutAvailability.AVAILABLE) {
|
||||
showInitializeHddConfirmation(hddFile, layout, button)
|
||||
} else {
|
||||
MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
|
||||
.setTitle(R.string.settings_hdd_layout_unavailable_title)
|
||||
.setMessage(getString(hddLayoutUnavailableReasonRes(availability)))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showInitializeHddConfirmation(
|
||||
hddFile: File,
|
||||
layout: XboxHddFormatter.Layout,
|
||||
button: MaterialButton,
|
||||
) {
|
||||
MaterialAlertDialogBuilder(this, R.style.ThemeOverlay_Xemu_RoundedDialog)
|
||||
.setTitle(R.string.settings_hdd_init_title)
|
||||
.setMessage(
|
||||
getString(
|
||||
R.string.settings_hdd_init_message,
|
||||
getString(hddLayoutLabelRes(layout)),
|
||||
getString(hddLayoutSummaryRes(layout)),
|
||||
hddFile.absolutePath,
|
||||
)
|
||||
)
|
||||
.setPositiveButton(R.string.settings_hdd_init_action) { _, _ ->
|
||||
initializeHddLayout(hddFile, layout, button)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun initializeHddLayout(
|
||||
hddFile: File,
|
||||
layout: XboxHddFormatter.Layout,
|
||||
button: MaterialButton,
|
||||
) {
|
||||
if (isInitializingHdd) {
|
||||
return
|
||||
}
|
||||
|
||||
isInitializingHdd = true
|
||||
button.isEnabled = false
|
||||
Toast.makeText(this, R.string.settings_hdd_init_working, Toast.LENGTH_SHORT).show()
|
||||
|
||||
Thread {
|
||||
val result = runCatching {
|
||||
XboxHddFormatter.initialize(hddFile, layout)
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
isInitializingHdd = false
|
||||
refreshHddToolsState(button)
|
||||
result.onSuccess {
|
||||
Toast.makeText(this, R.string.settings_hdd_init_success, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { error ->
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(
|
||||
R.string.settings_hdd_init_failed,
|
||||
error.message ?: hddFile.absolutePath,
|
||||
),
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun refreshHddToolsState(button: MaterialButton) {
|
||||
val hddFile = resolveHddFile()
|
||||
if (hddFile == null) {
|
||||
tvHddToolsStatus.text = getString(R.string.settings_hdd_status_missing)
|
||||
button.isEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
val inspection = runCatching { XboxHddFormatter.inspect(hddFile) }.getOrElse { error ->
|
||||
tvHddToolsStatus.text = getString(
|
||||
R.string.settings_hdd_status_error,
|
||||
error.message ?: hddFile.absolutePath,
|
||||
)
|
||||
button.isEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
val sizeLabel = Formatter.formatFileSize(this, inspection.totalBytes)
|
||||
val formatLabel = getString(hddFormatLabelRes(inspection.format))
|
||||
tvHddToolsStatus.text = when {
|
||||
inspection.totalBytes < XboxHddFormatter.MINIMUM_RETAIL_DISK_BYTES -> getString(
|
||||
R.string.settings_hdd_status_too_small,
|
||||
formatLabel,
|
||||
sizeLabel,
|
||||
hddFile.absolutePath,
|
||||
)
|
||||
else -> getString(
|
||||
R.string.settings_hdd_status_ready,
|
||||
formatLabel,
|
||||
sizeLabel,
|
||||
hddFile.absolutePath,
|
||||
)
|
||||
}
|
||||
button.isEnabled = !isInitializingHdd && XboxHddFormatter.supportedLayouts(inspection).isNotEmpty()
|
||||
}
|
||||
|
||||
private fun refreshHddToolsPreview(button: MaterialButton) {
|
||||
val hddFile = resolveHddFile()
|
||||
if (hddFile == null || !hddFile.isFile) {
|
||||
tvHddToolsStatus.text = getString(R.string.settings_hdd_status_missing)
|
||||
button.isEnabled = false
|
||||
return
|
||||
}
|
||||
|
||||
tvHddToolsStatus.text = getString(
|
||||
R.string.settings_hdd_status_configured,
|
||||
hddFile.absolutePath,
|
||||
)
|
||||
button.isEnabled = !isInitializingHdd
|
||||
}
|
||||
|
||||
private fun hddFormatLabelRes(format: XboxHddFormatter.ImageFormat): Int {
|
||||
return when (format) {
|
||||
XboxHddFormatter.ImageFormat.RAW -> R.string.settings_hdd_format_raw
|
||||
XboxHddFormatter.ImageFormat.QCOW2 -> R.string.settings_hdd_format_qcow2
|
||||
}
|
||||
}
|
||||
|
||||
private fun hddLayoutLabelRes(layout: XboxHddFormatter.Layout): Int {
|
||||
return when (layout) {
|
||||
XboxHddFormatter.Layout.RETAIL -> R.string.settings_hdd_layout_retail
|
||||
XboxHddFormatter.Layout.RETAIL_PLUS_F -> R.string.settings_hdd_layout_retail_f
|
||||
XboxHddFormatter.Layout.RETAIL_PLUS_F_G -> R.string.settings_hdd_layout_retail_f_g
|
||||
}
|
||||
}
|
||||
|
||||
private fun hddLayoutSummaryRes(layout: XboxHddFormatter.Layout): Int {
|
||||
return when (layout) {
|
||||
XboxHddFormatter.Layout.RETAIL -> R.string.settings_hdd_layout_summary_retail
|
||||
XboxHddFormatter.Layout.RETAIL_PLUS_F -> R.string.settings_hdd_layout_summary_retail_f
|
||||
XboxHddFormatter.Layout.RETAIL_PLUS_F_G -> R.string.settings_hdd_layout_summary_retail_f_g
|
||||
}
|
||||
}
|
||||
|
||||
private fun hddLayoutUnavailableReasonRes(
|
||||
availability: XboxHddFormatter.LayoutAvailability,
|
||||
): Int {
|
||||
return when (availability) {
|
||||
XboxHddFormatter.LayoutAvailability.AVAILABLE ->
|
||||
R.string.settings_hdd_layout_unavailable_not_enough_space
|
||||
XboxHddFormatter.LayoutAvailability.NO_EXTENDED_SPACE ->
|
||||
R.string.settings_hdd_layout_unavailable_no_extended_space
|
||||
XboxHddFormatter.LayoutAvailability.NEEDS_STANDARD_G_BOUNDARY ->
|
||||
R.string.settings_hdd_layout_unavailable_needs_standard_g_boundary
|
||||
XboxHddFormatter.LayoutAvailability.NOT_ENOUGH_SPACE ->
|
||||
R.string.settings_hdd_layout_unavailable_not_enough_space
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearSystemCache(): CacheClearResult {
|
||||
var result = CacheClearResult(0, false)
|
||||
|
||||
@@ -632,6 +852,12 @@ class SettingsActivity : AppCompatActivity() {
|
||||
return File(File(base, "x1box"), "eeprom.bin")
|
||||
}
|
||||
|
||||
private fun resolveHddFile(): File? {
|
||||
val path = prefs.getString("hddPath", null) ?: return null
|
||||
val file = File(path)
|
||||
return file.takeIf { it.isFile }
|
||||
}
|
||||
|
||||
private fun getFileName(uri: Uri): String? {
|
||||
return contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||
val col = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.izzy2lost.x1box
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
internal object XboxHddFormatter {
|
||||
const val MINIMUM_RETAIL_DISK_BYTES = 0x1DD156000L
|
||||
|
||||
private const val SECTOR_SIZE_BYTES = 512L
|
||||
private const val FATX_SUPERBLOCK_SIZE = 4096
|
||||
private const val FATX_FAT_OFFSET = FATX_SUPERBLOCK_SIZE.toLong()
|
||||
private const val FATX_RESERVED_ENTRY_COUNT = 1L
|
||||
private const val FATX_NON_RETAIL_SECTORS_PER_CLUSTER = 128
|
||||
|
||||
private const val EXTENDED_PARTITION_OFFSET = 0x1DD156000L
|
||||
private const val G_PARTITION_OFFSET = 0x0FFFFFFFL * SECTOR_SIZE_BYTES
|
||||
|
||||
enum class Layout {
|
||||
RETAIL,
|
||||
RETAIL_PLUS_F,
|
||||
RETAIL_PLUS_F_G,
|
||||
}
|
||||
|
||||
enum class LayoutAvailability {
|
||||
AVAILABLE,
|
||||
NO_EXTENDED_SPACE,
|
||||
NEEDS_STANDARD_G_BOUNDARY,
|
||||
NOT_ENOUGH_SPACE,
|
||||
}
|
||||
|
||||
enum class ImageFormat {
|
||||
RAW,
|
||||
QCOW2,
|
||||
}
|
||||
|
||||
data class Inspection(
|
||||
val format: ImageFormat,
|
||||
val totalBytes: Long,
|
||||
) {
|
||||
val supportsRetailFormat: Boolean
|
||||
get() = totalBytes >= MINIMUM_RETAIL_DISK_BYTES
|
||||
|
||||
val extendedBytes: Long
|
||||
get() = alignedExtendedBytes(totalBytes)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun inspect(file: File): Inspection {
|
||||
require(file.isFile) { "No local HDD image is configured." }
|
||||
|
||||
val details = NativeBridge.nativeInspectHdd(file.absolutePath)
|
||||
require(details.size >= 2) { "Failed to inspect the current HDD image." }
|
||||
|
||||
val format = ImageFormat.values().getOrNull(details[0].toInt())
|
||||
?: throw IOException("Unsupported HDD image format.")
|
||||
return Inspection(
|
||||
format = format,
|
||||
totalBytes = details[1],
|
||||
)
|
||||
}
|
||||
|
||||
fun supportedLayouts(inspection: Inspection): List<Layout> {
|
||||
if (!inspection.supportsRetailFormat) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return Layout.entries.filter { availabilityFor(inspection, it) == LayoutAvailability.AVAILABLE }
|
||||
}
|
||||
|
||||
@Throws(IOException::class, IllegalArgumentException::class)
|
||||
fun initialize(file: File, layout: Layout) {
|
||||
require(file.isFile) { "No local HDD image is configured." }
|
||||
NativeBridge.nativeInitializeHdd(file.absolutePath, layout.ordinal)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, IllegalArgumentException::class)
|
||||
fun initializeRetail(file: File) {
|
||||
initialize(file, Layout.RETAIL)
|
||||
}
|
||||
|
||||
fun availabilityFor(inspection: Inspection, layout: Layout): LayoutAvailability {
|
||||
if (!inspection.supportsRetailFormat) {
|
||||
return LayoutAvailability.NOT_ENOUGH_SPACE
|
||||
}
|
||||
|
||||
return when (layout) {
|
||||
Layout.RETAIL -> LayoutAvailability.AVAILABLE
|
||||
Layout.RETAIL_PLUS_F -> {
|
||||
if (inspection.extendedBytes <= 0L) {
|
||||
LayoutAvailability.NO_EXTENDED_SPACE
|
||||
} else if (canFormatExtendedPartition(inspection.extendedBytes)) {
|
||||
LayoutAvailability.AVAILABLE
|
||||
} else {
|
||||
LayoutAvailability.NOT_ENOUGH_SPACE
|
||||
}
|
||||
}
|
||||
Layout.RETAIL_PLUS_F_G -> {
|
||||
val split = standardFgPartitionSizes(inspection.totalBytes)
|
||||
?: return LayoutAvailability.NEEDS_STANDARD_G_BOUNDARY
|
||||
if (canFormatExtendedPartition(split.first) &&
|
||||
canFormatExtendedPartition(split.second)
|
||||
) {
|
||||
LayoutAvailability.AVAILABLE
|
||||
} else {
|
||||
LayoutAvailability.NOT_ENOUGH_SPACE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun canBuildPartition(size: Long, sectorsPerCluster: Int): Boolean {
|
||||
val bytesPerCluster = sectorsPerCluster * SECTOR_SIZE_BYTES
|
||||
if (bytesPerCluster <= 0L || size <= 0L) {
|
||||
return false
|
||||
}
|
||||
|
||||
var fatEntries = size / bytesPerCluster
|
||||
fatEntries += FATX_RESERVED_ENTRY_COUNT
|
||||
|
||||
var fatLengthBytes = fatEntries * if (fatEntries < 0xFFF0) 2L else 4L
|
||||
if (fatLengthBytes % FATX_SUPERBLOCK_SIZE != 0L) {
|
||||
fatLengthBytes += FATX_SUPERBLOCK_SIZE - (fatLengthBytes % FATX_SUPERBLOCK_SIZE)
|
||||
}
|
||||
return FATX_FAT_OFFSET + fatLengthBytes + bytesPerCluster <= size
|
||||
}
|
||||
|
||||
private fun canFormatExtendedPartition(size: Long): Boolean {
|
||||
return canBuildPartition(size, FATX_NON_RETAIL_SECTORS_PER_CLUSTER)
|
||||
}
|
||||
|
||||
private fun standardFgPartitionSizes(totalBytes: Long): Pair<Long, Long>? {
|
||||
val alignedTotalBytes = (totalBytes / SECTOR_SIZE_BYTES) * SECTOR_SIZE_BYTES
|
||||
if (alignedTotalBytes <= G_PARTITION_OFFSET || G_PARTITION_OFFSET <= EXTENDED_PARTITION_OFFSET) {
|
||||
return null
|
||||
}
|
||||
|
||||
val fSize = G_PARTITION_OFFSET - EXTENDED_PARTITION_OFFSET
|
||||
val gSize = alignedTotalBytes - G_PARTITION_OFFSET
|
||||
if (gSize <= 0L) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Pair(fSize, gSize)
|
||||
}
|
||||
|
||||
private fun alignedExtendedBytes(totalBytes: Long): Long {
|
||||
if (totalBytes <= EXTENDED_PARTITION_OFFSET) {
|
||||
return 0L
|
||||
}
|
||||
return ((totalBytes - EXTENDED_PARTITION_OFFSET) / SECTOR_SIZE_BYTES) * SECTOR_SIZE_BYTES
|
||||
}
|
||||
|
||||
private object NativeBridge {
|
||||
init {
|
||||
System.loadLibrary("SDL2")
|
||||
System.loadLibrary("xemu")
|
||||
}
|
||||
|
||||
external fun nativeInspectHdd(path: String): LongArray
|
||||
external fun nativeInitializeHdd(path: String, layoutOrdinal: Int)
|
||||
}
|
||||
}
|
||||
@@ -599,6 +599,39 @@
|
||||
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_hdd_tools"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/xemu_green" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_hdd_tools_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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="8dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/settings_hdd_tools_hint"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_initialize_retail_hdd"
|
||||
style="?attr/materialButtonOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_hdd_init_action" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -173,6 +173,33 @@
|
||||
<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_hdd_tools">HDD Tools</string>
|
||||
<string name="settings_hdd_tools_hint">Initializes the current local HDD image with a stock retail layout or optional extended F/G partitions. Raw and QCOW2 images are supported. This does not install a dashboard.</string>
|
||||
<string name="settings_hdd_status_missing">No local HDD image is configured. Pick one in the setup wizard first.</string>
|
||||
<string name="settings_hdd_status_configured">Current HDD image:\n%1$s\nTap Initialize HDD Layout to inspect the image and choose a layout.</string>
|
||||
<string name="settings_hdd_format_raw">Raw</string>
|
||||
<string name="settings_hdd_format_qcow2">QCOW2</string>
|
||||
<string name="settings_hdd_status_ready">Current HDD image: %1$s (%2$s)\n%3$s</string>
|
||||
<string name="settings_hdd_status_too_small">Current HDD image: %1$s (%2$s)\n%3$s\nThis image is smaller than a stock Xbox disk and cannot be initialized as retail.</string>
|
||||
<string name="settings_hdd_status_error">Failed to inspect the current HDD image.\n%1$s</string>
|
||||
<string name="settings_hdd_layout_pick_title">Choose HDD Layout</string>
|
||||
<string name="settings_hdd_layout_retail">Retail Only (X, Y, Z, C, E)</string>
|
||||
<string name="settings_hdd_layout_retail_f">Retail + F</string>
|
||||
<string name="settings_hdd_layout_retail_f_g">Retail + F + G</string>
|
||||
<string name="settings_hdd_layout_summary_retail">Rewrites X, Y, Z, C, and E.</string>
|
||||
<string name="settings_hdd_layout_summary_retail_f">Rewrites X, Y, Z, C, E, and uses all remaining space as F.</string>
|
||||
<string name="settings_hdd_layout_summary_retail_f_g">Rewrites X, Y, Z, C, E, keeps F up to the standard 137 GB boundary, and uses the rest as G.</string>
|
||||
<string name="settings_hdd_layout_unavailable_format">%1$s (%2$s)</string>
|
||||
<string name="settings_hdd_layout_unavailable_title">Layout Unavailable</string>
|
||||
<string name="settings_hdd_layout_unavailable_no_extended_space">This HDD image only has enough room for the stock retail partitions. There is no extra space available for F or G.</string>
|
||||
<string name="settings_hdd_layout_unavailable_needs_standard_g_boundary">Retail + F + G needs an HDD image larger than 128 GiB so G can start at the standard Xbox boundary.</string>
|
||||
<string name="settings_hdd_layout_unavailable_not_enough_space">This HDD image does not have enough usable space for that layout.</string>
|
||||
<string name="settings_hdd_init_title">Initialize HDD Layout</string>
|
||||
<string name="settings_hdd_init_message">Initialize the current HDD image with %1$s?\n\n%2$s\n\nImage: %3$s</string>
|
||||
<string name="settings_hdd_init_action">Initialize HDD Layout</string>
|
||||
<string name="settings_hdd_init_working">Initializing HDD...</string>
|
||||
<string name="settings_hdd_init_success">HDD layout initialized</string>
|
||||
<string name="settings_hdd_init_failed">Failed to initialize HDD: %1$s</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>
|
||||
|
||||
Reference in New Issue
Block a user