mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-01-30 02:53:41 +00:00
Merge branch 'azahar-emu:master' into master
This commit is contained in:
commit
f2cfaa3de4
@ -28,6 +28,7 @@ import org.citra.citra_emu.activities.EmulationActivity
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.RemovableStorageHelper
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Date
|
||||
|
||||
@ -600,6 +601,47 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun logDeviceInfo()
|
||||
|
||||
enum class CompressStatus(val value: Int) {
|
||||
SUCCESS(0),
|
||||
COMPRESS_UNSUPPORTED(1),
|
||||
COMPRESS_ALREADY_COMPRESSED(2),
|
||||
COMPRESS_FAILED(3),
|
||||
DECOMPRESS_UNSUPPORTED(4),
|
||||
DECOMPRESS_NOT_COMPRESSED(5),
|
||||
DECOMPRESS_FAILED(6),
|
||||
INSTALLED_APPLICATION(7);
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): CompressStatus =
|
||||
CompressStatus.entries.first { it.value == value }
|
||||
}
|
||||
}
|
||||
|
||||
// Compression / Decompression
|
||||
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
compressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
|
||||
|
||||
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
|
||||
return CompressStatus.fromValue(
|
||||
decompressFileNative(inputPath, outputPath)
|
||||
)
|
||||
}
|
||||
|
||||
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onCompressProgress(total: Long, current: Long) {
|
||||
CompressProgressDialogViewModel.update(total, current)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun createFile(directory: String, filename: String): Boolean =
|
||||
|
||||
@ -342,6 +342,7 @@ class EmulationActivity : AppCompatActivity() {
|
||||
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
|
||||
val guestOrientation =
|
||||
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
|
||||
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
|
||||
if (nextMapping == -1 || guestOrientation == -1) {
|
||||
// Axis is unmapped
|
||||
continue
|
||||
@ -350,6 +351,8 @@ class EmulationActivity : AppCompatActivity() {
|
||||
// Skip joystick wobble
|
||||
value = 0f
|
||||
}
|
||||
if (inverted) value = -value;
|
||||
|
||||
when (nextMapping) {
|
||||
NativeLibrary.ButtonType.STICK_LEFT -> {
|
||||
axisValuesCirclePad[guestOrientation] = value
|
||||
|
||||
@ -57,7 +57,12 @@ import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GameIconUtils
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
|
||||
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater, private val openImageLauncher: ActivityResultLauncher<String>?) :
|
||||
class GameAdapter(
|
||||
private val activity: AppCompatActivity,
|
||||
private val inflater: LayoutInflater,
|
||||
private val openImageLauncher: ActivityResultLauncher<String>?,
|
||||
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
|
||||
) :
|
||||
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
|
||||
View.OnClickListener, View.OnLongClickListener {
|
||||
private var lastClickTime = 0L
|
||||
@ -441,6 +446,27 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
|
||||
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
|
||||
if (game.isInstalled) {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.compress_decompress_installed_app),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
compressDecompressButton.alpha = 0.38f
|
||||
} else {
|
||||
compressDecompressButton.setOnClickListener {
|
||||
val shouldCompress = !game.isCompressed
|
||||
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
|
||||
val baseName = holder.game.filename.substringBeforeLast('.')
|
||||
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
|
||||
bottomSheetDialog.dismiss()
|
||||
}
|
||||
}
|
||||
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
|
||||
|
||||
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
|
||||
showOpenContextMenu(it, game)
|
||||
}
|
||||
|
||||
@ -63,4 +63,37 @@ enum class SecondaryDisplayLayout(val int: Int) {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class StereoWhichDisplay(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
|
||||
NONE(0), // equivalent to StereoRenderOption = Off
|
||||
BOTH(1),
|
||||
PRIMARY_ONLY(2),
|
||||
SECONDARY_ONLY(3);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoWhichDisplay {
|
||||
return entries.firstOrNull { it.int == int } ?: NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class StereoMode(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
|
||||
OFF(0),
|
||||
SIDE_BY_SIDE(1),
|
||||
SIDE_BY_SIDE_FULL(2),
|
||||
ANAGLYPH(3),
|
||||
INTERLACED(4),
|
||||
REVERSE_INTERLACED (5),
|
||||
CARDBOARD_VR (6);
|
||||
|
||||
companion object {
|
||||
fun from(int: Int): StereoMode {
|
||||
return entries.firstOrNull { it.int == int } ?: OFF
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ enum class BooleanSetting(
|
||||
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
|
||||
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
|
||||
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
|
||||
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
|
||||
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
|
||||
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
|
||||
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
|
||||
@ -87,7 +88,8 @@ enum class BooleanSetting(
|
||||
USE_ARTIC_BASE_CONTROLLER,
|
||||
COMPRESS_INSTALLED_CIA_CONTENT,
|
||||
ANDROID_HIDE_IMAGES,
|
||||
PERF_OVERLAY_ENABLE // Works in overlay options, but not from the settings menu
|
||||
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
|
||||
APPLY_REGION_FREE_PATCH
|
||||
)
|
||||
|
||||
fun from(key: String): BooleanSetting? =
|
||||
|
||||
@ -17,7 +17,7 @@ enum class IntSetting(
|
||||
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
|
||||
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
|
||||
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
|
||||
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0),
|
||||
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
|
||||
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
|
||||
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
|
||||
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
|
||||
@ -53,6 +53,7 @@ enum class IntSetting(
|
||||
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
|
||||
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
|
||||
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
|
||||
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
|
||||
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
|
||||
|
||||
override var int: Int = defaultValue
|
||||
|
||||
@ -170,6 +170,7 @@ class InputBindingSetting(
|
||||
.remove(oldKey) // Used for button mapping
|
||||
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
|
||||
.remove(oldKey + "_GuestButton") // Used for axis button
|
||||
.remove(oldKey + "_Inverted") // used for axis inversion
|
||||
.apply()
|
||||
|
||||
// Also clear from config.ini
|
||||
@ -209,7 +210,7 @@ class InputBindingSetting(
|
||||
/**
|
||||
* Helper function to write a gamepad axis mapping for the setting.
|
||||
*/
|
||||
private fun writeAxisMapping(axis: Int, value: Int) {
|
||||
private fun writeAxisMapping(axis: Int, value: Int, inverted: Boolean) {
|
||||
// Cleanup old mapping
|
||||
removeOldMapping()
|
||||
|
||||
@ -217,6 +218,7 @@ class InputBindingSetting(
|
||||
preferences.edit()
|
||||
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
|
||||
.putInt(getInputAxisButtonKey(axis), value)
|
||||
.putBoolean(getInputAxisInvertedKey(axis),inverted)
|
||||
// Write next reverse mapping for future cleanup
|
||||
.putString(reverseKey, getInputAxisKey(axis))
|
||||
.apply()
|
||||
@ -251,7 +253,7 @@ class InputBindingSetting(
|
||||
*
|
||||
* @param device InputDevice from which the input event originated.
|
||||
* @param motionRange MotionRange of the movement
|
||||
* @param axisDir Either '-' or '+' (currently unused)
|
||||
* @param axisDir Either '-' or '+'
|
||||
*/
|
||||
fun onMotionInput(device: InputDevice, motionRange: MotionRange, axisDir: Char) {
|
||||
if (!isAxisMappingSupported()) {
|
||||
@ -267,8 +269,8 @@ class InputBindingSetting(
|
||||
} else {
|
||||
buttonCode
|
||||
}
|
||||
writeAxisMapping(motionRange.axis, button)
|
||||
val uiString = "${device.name}: Axis ${motionRange.axis}"
|
||||
writeAxisMapping(motionRange.axis, button, axisDir == '-')
|
||||
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
|
||||
value = uiString
|
||||
}
|
||||
|
||||
@ -323,6 +325,11 @@ class InputBindingSetting(
|
||||
*/
|
||||
fun getInputAxisButtonKey(axis: Int): String = "${getInputAxisKey(axis)}_GuestButton"
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an whether a gamepad axis is inverted.
|
||||
*/
|
||||
fun getInputAxisInvertedKey(axis: Int): String = "${getInputAxisKey(axis)}_Inverted"
|
||||
|
||||
/**
|
||||
* Helper function to get the settings key for an gamepad axis orientation.
|
||||
*/
|
||||
|
||||
@ -16,6 +16,9 @@ import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.display.ScreenLayout
|
||||
import org.citra.citra_emu.display.StereoMode
|
||||
import org.citra.citra_emu.display.StereoWhichDisplay
|
||||
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
|
||||
import org.citra.citra_emu.features.settings.model.AbstractSetting
|
||||
@ -44,7 +47,6 @@ import org.citra.citra_emu.utils.BirthdayMonth
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.SystemSaveGame
|
||||
import org.citra.citra_emu.utils.ThemeUtil
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
|
||||
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
|
||||
private var menuTag: String? = null
|
||||
@ -111,20 +113,24 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
}
|
||||
|
||||
/** Returns the portrait mode width */
|
||||
private fun getWidth(): Int {
|
||||
val dm = Resources.getSystem().displayMetrics;
|
||||
return if (dm.widthPixels < dm.heightPixels)
|
||||
dm.widthPixels
|
||||
else
|
||||
dm.heightPixels
|
||||
private fun getDimensions(): IntArray {
|
||||
val dm = Resources.getSystem().displayMetrics
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val wm = settingsActivity.windowManager.maximumWindowMetrics
|
||||
val height = wm.bounds.height().coerceAtLeast(dm.heightPixels)
|
||||
val width = wm.bounds.width().coerceAtLeast(dm.widthPixels)
|
||||
intArrayOf(width, height)
|
||||
} else {
|
||||
intArrayOf(dm.widthPixels, dm.heightPixels)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHeight(): Int {
|
||||
val dm = Resources.getSystem().displayMetrics;
|
||||
return if (dm.widthPixels < dm.heightPixels)
|
||||
dm.heightPixels
|
||||
else
|
||||
dm.widthPixels
|
||||
private fun getSmallerDimension(): Int {
|
||||
return getDimensions().min()
|
||||
}
|
||||
|
||||
private fun getLargerDimension(): Int {
|
||||
return getDimensions().max()
|
||||
}
|
||||
|
||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||
@ -945,17 +951,30 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.stereoscopy))
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.RENDER_3D_WHICH_DISPLAY,
|
||||
R.string.render_3d_which_display,
|
||||
R.string.render_3d_which_display_description,
|
||||
R.array.render3dWhichDisplay,
|
||||
R.array.render3dDisplayValues,
|
||||
IntSetting.RENDER_3D_WHICH_DISPLAY.key,
|
||||
IntSetting.RENDER_3D_WHICH_DISPLAY.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.STEREOSCOPIC_3D_MODE,
|
||||
R.string.render3d,
|
||||
0,
|
||||
R.string.render3d_description,
|
||||
R.array.render3dModes,
|
||||
R.array.render3dValues,
|
||||
IntSetting.STEREOSCOPIC_3D_MODE.key,
|
||||
IntSetting.STEREOSCOPIC_3D_MODE.defaultValue
|
||||
IntSetting.STEREOSCOPIC_3D_MODE.defaultValue,
|
||||
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SliderSetting(
|
||||
IntSetting.STEREOSCOPIC_3D_DEPTH,
|
||||
@ -978,6 +997,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
)
|
||||
)
|
||||
|
||||
add(
|
||||
SwitchSetting(
|
||||
BooleanSetting.SWAP_EYES_3D,
|
||||
R.string.swap_eyes_3d,
|
||||
R.string.swap_eyes_3d_description,
|
||||
BooleanSetting.SWAP_EYES_3D.key,
|
||||
BooleanSetting.SWAP_EYES_3D.defaultValue,
|
||||
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
|
||||
)
|
||||
)
|
||||
|
||||
add(HeaderSetting(R.string.cardboard_vr))
|
||||
add(
|
||||
SliderSetting(
|
||||
@ -988,7 +1018,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
100,
|
||||
"%",
|
||||
IntSetting.CARDBOARD_SCREEN_SIZE.key,
|
||||
IntSetting.CARDBOARD_SCREEN_SIZE.defaultValue.toFloat()
|
||||
IntSetting.CARDBOARD_SCREEN_SIZE.defaultValue.toFloat(),
|
||||
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
|
||||
)
|
||||
)
|
||||
add(
|
||||
@ -1000,7 +1031,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
100,
|
||||
"%",
|
||||
IntSetting.CARDBOARD_X_SHIFT.key,
|
||||
IntSetting.CARDBOARD_X_SHIFT.defaultValue.toFloat()
|
||||
IntSetting.CARDBOARD_X_SHIFT.defaultValue.toFloat(),
|
||||
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
|
||||
)
|
||||
)
|
||||
add(
|
||||
@ -1012,7 +1044,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
100,
|
||||
"%",
|
||||
IntSetting.CARDBOARD_Y_SHIFT.key,
|
||||
IntSetting.CARDBOARD_Y_SHIFT.defaultValue.toFloat()
|
||||
IntSetting.CARDBOARD_Y_SHIFT.defaultValue.toFloat(),
|
||||
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
|
||||
)
|
||||
)
|
||||
|
||||
@ -1146,7 +1179,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.array.aspectRatioValues,
|
||||
IntSetting.ASPECT_RATIO.key,
|
||||
IntSetting.ASPECT_RATIO.defaultValue,
|
||||
isEnabled = IntSetting.SCREEN_LAYOUT.int == 1,
|
||||
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.SINGLE_SCREEN.int,
|
||||
)
|
||||
)
|
||||
add(
|
||||
@ -1194,7 +1227,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
"%",
|
||||
FloatSetting.SECOND_SCREEN_OPACITY.key,
|
||||
FloatSetting.SECOND_SCREEN_OPACITY.defaultValue,
|
||||
isEnabled = IntSetting.SCREEN_LAYOUT.int == 5
|
||||
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.CUSTOM_LAYOUT.int
|
||||
)
|
||||
)
|
||||
add(HeaderSetting(R.string.bg_color, R.string.bg_color_description))
|
||||
@ -1405,7 +1438,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_x,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_TOP_X.key,
|
||||
IntSetting.LANDSCAPE_TOP_X.defaultValue.toFloat()
|
||||
@ -1417,7 +1450,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_y,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_TOP_Y.key,
|
||||
IntSetting.LANDSCAPE_TOP_Y.defaultValue.toFloat()
|
||||
@ -1429,7 +1462,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_width,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_TOP_WIDTH.key,
|
||||
IntSetting.LANDSCAPE_TOP_WIDTH.defaultValue.toFloat()
|
||||
@ -1441,7 +1474,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_height,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_TOP_HEIGHT.key,
|
||||
IntSetting.LANDSCAPE_TOP_HEIGHT.defaultValue.toFloat()
|
||||
@ -1454,7 +1487,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_x,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_BOTTOM_X.key,
|
||||
IntSetting.LANDSCAPE_BOTTOM_X.defaultValue.toFloat()
|
||||
@ -1466,7 +1499,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_y,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_BOTTOM_Y.key,
|
||||
IntSetting.LANDSCAPE_BOTTOM_Y.defaultValue.toFloat()
|
||||
@ -1478,7 +1511,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_width,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_BOTTOM_WIDTH.key,
|
||||
IntSetting.LANDSCAPE_BOTTOM_WIDTH.defaultValue.toFloat()
|
||||
@ -1490,7 +1523,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_height,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.key,
|
||||
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.defaultValue.toFloat()
|
||||
@ -1510,7 +1543,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_x,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_TOP_X.key,
|
||||
IntSetting.PORTRAIT_TOP_X.defaultValue.toFloat()
|
||||
@ -1522,7 +1555,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_y,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_TOP_Y.key,
|
||||
IntSetting.PORTRAIT_TOP_Y.defaultValue.toFloat()
|
||||
@ -1534,7 +1567,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_width,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_TOP_WIDTH.key,
|
||||
IntSetting.PORTRAIT_TOP_WIDTH.defaultValue.toFloat()
|
||||
@ -1546,7 +1579,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_height,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_TOP_HEIGHT.key,
|
||||
IntSetting.PORTRAIT_TOP_HEIGHT.defaultValue.toFloat()
|
||||
@ -1559,7 +1592,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_x,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_BOTTOM_X.key,
|
||||
IntSetting.PORTRAIT_BOTTOM_X.defaultValue.toFloat()
|
||||
@ -1571,7 +1604,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_y,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_BOTTOM_Y.key,
|
||||
IntSetting.PORTRAIT_BOTTOM_Y.defaultValue.toFloat()
|
||||
@ -1583,7 +1616,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_width,
|
||||
0,
|
||||
0,
|
||||
getWidth(),
|
||||
getSmallerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_BOTTOM_WIDTH.key,
|
||||
IntSetting.PORTRAIT_BOTTOM_WIDTH.defaultValue.toFloat()
|
||||
@ -1595,7 +1628,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
||||
R.string.emulation_custom_layout_height,
|
||||
0,
|
||||
0,
|
||||
getHeight(),
|
||||
getLargerDimension(),
|
||||
"px",
|
||||
IntSetting.PORTRAIT_BOTTOM_HEIGHT.key,
|
||||
IntSetting.PORTRAIT_BOTTOM_HEIGHT.defaultValue.toFloat()
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
|
||||
class CompressProgressDialogFragment : DialogFragment() {
|
||||
private lateinit var progressBar: ProgressBar
|
||||
private var outputPath: String? = null
|
||||
private var isCompressing: Boolean = true
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
isCompressing = it.getBoolean(ARG_IS_COMPRESSING, true)
|
||||
outputPath = it.getString(ARG_OUTPUT_PATH)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val view = layoutInflater.inflate(R.layout.dialog_compress_progress, null)
|
||||
progressBar = view.findViewById(R.id.compress_progress)
|
||||
val label = view.findViewById<android.widget.TextView>(R.id.compress_label)
|
||||
label.text = if (isCompressing) getString(R.string.compressing) else getString(R.string.decompressing)
|
||||
|
||||
isCancelable = false
|
||||
progressBar.isIndeterminate = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
combine(CompressProgressDialogViewModel.total, CompressProgressDialogViewModel.progress) { total, progress ->
|
||||
total to progress
|
||||
}.collectLatest { (total, progress) ->
|
||||
if (total <= 0) {
|
||||
progressBar.isIndeterminate = true
|
||||
label.visibility = View.GONE
|
||||
} else {
|
||||
progressBar.isIndeterminate = false
|
||||
label.visibility = View.VISIBLE
|
||||
progressBar.max = total
|
||||
progressBar.setProgress(progress, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
.setView(view)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(android.R.string.cancel) { _: android.content.DialogInterface, _: Int ->
|
||||
outputPath?.let { path ->
|
||||
NativeLibrary.deleteDocument(path)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "CompressProgressDialog"
|
||||
private const val ARG_IS_COMPRESSING = "isCompressing"
|
||||
private const val ARG_OUTPUT_PATH = "outputPath"
|
||||
|
||||
fun newInstance(isCompressing: Boolean, outputPath: String?): CompressProgressDialogFragment {
|
||||
val frag = CompressProgressDialogFragment()
|
||||
val args = Bundle()
|
||||
args.putBoolean(ARG_IS_COMPRESSING, isCompressing)
|
||||
args.putString(ARG_OUTPUT_PATH, outputPath)
|
||||
frag.arguments = args
|
||||
return frag
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -30,14 +30,17 @@ import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.adapters.GameAdapter
|
||||
import org.citra.citra_emu.databinding.FragmentGamesBinding
|
||||
import org.citra.citra_emu.features.settings.model.Settings
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
|
||||
@ -56,6 +59,58 @@ class GamesFragment : Fragment() {
|
||||
gameAdapter.handleShortcutImageResult(uri)
|
||||
}
|
||||
|
||||
private var shouldCompress: Boolean = true
|
||||
private var pendingCompressInvocation: String? = null
|
||||
|
||||
companion object {
|
||||
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
|
||||
if (outputUri != null) {
|
||||
CompressProgressDialogViewModel.reset()
|
||||
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputUri.toString())
|
||||
dialog.showNow(
|
||||
fragment.requireActivity().supportFragmentManager,
|
||||
CompressProgressDialogFragment.TAG
|
||||
)
|
||||
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val status = if (shouldCompress) {
|
||||
NativeLibrary.compressFile(inputPath, outputUri.toString())
|
||||
} else {
|
||||
NativeLibrary.decompressFile(inputPath, outputUri.toString())
|
||||
}
|
||||
|
||||
fragment.requireActivity().runOnUiThread {
|
||||
dialog.dismiss()
|
||||
val resId = when (status) {
|
||||
NativeLibrary.CompressStatus.SUCCESS -> if (shouldCompress) R.string.compress_success else R.string.decompress_success
|
||||
NativeLibrary.CompressStatus.COMPRESS_UNSUPPORTED -> R.string.compress_unsupported
|
||||
NativeLibrary.CompressStatus.COMPRESS_ALREADY_COMPRESSED -> R.string.compress_already
|
||||
NativeLibrary.CompressStatus.COMPRESS_FAILED -> R.string.compress_failed
|
||||
NativeLibrary.CompressStatus.DECOMPRESS_UNSUPPORTED -> R.string.decompress_unsupported
|
||||
NativeLibrary.CompressStatus.DECOMPRESS_NOT_COMPRESSED -> R.string.decompress_not_compressed
|
||||
NativeLibrary.CompressStatus.DECOMPRESS_FAILED -> R.string.decompress_failed
|
||||
NativeLibrary.CompressStatus.INSTALLED_APPLICATION -> R.string.compress_decompress_installed_app
|
||||
}
|
||||
|
||||
MaterialAlertDialogBuilder(fragment.requireContext())
|
||||
.setMessage(fragment.getString(resId))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
|
||||
gamesViewModel.reloadGames(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val onCompressDecompressLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/octet-stream")
|
||||
) { uri: Uri? ->
|
||||
doCompression(this, gamesViewModel, pendingCompressInvocation, uri, shouldCompress)
|
||||
pendingCompressInvocation = null
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enterTransition = MaterialFadeThrough()
|
||||
@ -81,7 +136,12 @@ class GamesFragment : Fragment() {
|
||||
gameAdapter = GameAdapter(
|
||||
requireActivity() as AppCompatActivity,
|
||||
inflater,
|
||||
openImageLauncher
|
||||
openImageLauncher,
|
||||
onRequestCompressOrDecompress = { inputPath, suggestedName, shouldCompress ->
|
||||
pendingCompressInvocation = inputPath
|
||||
onCompressDecompressLauncher.launch(suggestedName)
|
||||
this.shouldCompress = shouldCompress
|
||||
}
|
||||
)
|
||||
|
||||
binding.gridGames.apply {
|
||||
|
||||
@ -7,11 +7,13 @@ package org.citra.citra_emu.fragments
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
@ -26,18 +28,19 @@ import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.adapters.GameAdapter
|
||||
import org.citra.citra_emu.databinding.FragmentSearchBinding
|
||||
import org.citra.citra_emu.model.Game
|
||||
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
|
||||
import org.citra.citra_emu.viewmodel.GamesViewModel
|
||||
import org.citra.citra_emu.viewmodel.HomeViewModel
|
||||
import java.time.temporal.ChronoField
|
||||
import java.util.Locale
|
||||
import android.net.Uri
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: FragmentSearchBinding? = null
|
||||
@ -53,6 +56,15 @@ class SearchFragment : Fragment() {
|
||||
gameAdapter.handleShortcutImageResult(uri)
|
||||
}
|
||||
|
||||
private var shouldCompress: Boolean = true
|
||||
private var pendingCompressInvocation: String? = null
|
||||
private val onCompressDecompressLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/octet-stream")
|
||||
) { uri: Uri? ->
|
||||
GamesFragment.doCompression(this, gamesViewModel, pendingCompressInvocation, uri, shouldCompress)
|
||||
pendingCompressInvocation = null
|
||||
}
|
||||
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
companion object {
|
||||
@ -85,7 +97,13 @@ class SearchFragment : Fragment() {
|
||||
gameAdapter = GameAdapter(
|
||||
requireActivity() as AppCompatActivity,
|
||||
inflater,
|
||||
openImageLauncher
|
||||
openImageLauncher,
|
||||
onRequestCompressOrDecompress = { inputPath, suggestedName, shouldCompress ->
|
||||
pendingCompressInvocation = inputPath
|
||||
onCompressDecompressLauncher.launch(suggestedName)
|
||||
this.shouldCompress = shouldCompress
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
binding.gridGamesSearch.apply {
|
||||
|
||||
@ -27,6 +27,7 @@ class Game(
|
||||
val isVisibleSystemTitle: Boolean = false,
|
||||
val icon: IntArray? = null,
|
||||
val fileType: String = "",
|
||||
val isCompressed: Boolean = false,
|
||||
val filename: String,
|
||||
) : Parcelable {
|
||||
val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime"
|
||||
|
||||
@ -90,6 +90,7 @@ object GameHelper {
|
||||
gameInfo?.getIsVisibleSystemTitle() ?: false,
|
||||
gameInfo?.getIcon(),
|
||||
gameInfo?.getFileType() ?: "",
|
||||
gameInfo?.getFileType()?.contains("(Z)") ?: false,
|
||||
if (FileUtil.isNativePath(filePath)) {
|
||||
CitraApplication.documentsTree.getFilename(filePath)
|
||||
} else {
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
package org.citra.citra_emu.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
object CompressProgressDialogViewModel: ViewModel() {
|
||||
private val _progress = MutableStateFlow(0)
|
||||
val progress = _progress.asStateFlow()
|
||||
|
||||
private val _total = MutableStateFlow(0)
|
||||
val total = _total.asStateFlow()
|
||||
|
||||
private val _message = MutableStateFlow("")
|
||||
val message = _message.asStateFlow()
|
||||
|
||||
fun update(totalBytes: Long, currentBytes: Long) {
|
||||
val percent = ((currentBytes * 100L) / totalBytes).coerceIn(0L, 100L).toInt()
|
||||
_total.value = 100
|
||||
_progress.value = percent
|
||||
_message.value = ""
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
_progress.value = 0
|
||||
_total.value = 0
|
||||
_message.value = ""
|
||||
}
|
||||
}
|
||||
@ -175,7 +175,8 @@ void Config::ReadValues() {
|
||||
ReadSetting("Renderer", Settings::values.custom_second_layer_opacity);
|
||||
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
|
||||
ReadSetting("Renderer", Settings::values.disable_right_eye_render);
|
||||
|
||||
ReadSetting("Renderer", Settings::values.swap_eyes_3d);
|
||||
ReadSetting("Renderer", Settings::values.render_3d_which_display);
|
||||
// Layout
|
||||
// Somewhat inelegant solution to ensure layout value is between 0 and 5 on read
|
||||
// since older config files may have other values
|
||||
|
||||
@ -170,13 +170,23 @@ bg_green =
|
||||
custom_second_layer_opacity =
|
||||
|
||||
# Whether and how Stereoscopic 3D should be rendered
|
||||
# 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
|
||||
# 0: Off, 1: Half Width Side by Side, 2 (default): Full Width Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
|
||||
# 0 is no longer supported in the interface, as using render_3d_which_display = 0 has the same effect, but supported here for backwards compatibility
|
||||
render_3d =
|
||||
|
||||
# Change 3D Intensity
|
||||
# 0 - 255: Intensity. 0 (default)
|
||||
factor_3d =
|
||||
|
||||
# Swap Eyes in 3d
|
||||
# true: Swap eyes, false (default): Do not swap eyes
|
||||
swap_eyes_3d =
|
||||
|
||||
# Which Display to render 3d mode to
|
||||
# 0 (default) - None. Equivalent to render_3d=0
|
||||
# 1: Both, 2: Primary Only, 3: Secondary Only
|
||||
render_3d_which_display =
|
||||
|
||||
# The name of the post processing shader to apply.
|
||||
# Loaded from shaders if render_3d is off or side by side.
|
||||
pp_shader_name =
|
||||
|
||||
@ -222,7 +222,7 @@ jboolean Java_org_citra_citra_1emu_model_GameInfo_getIsVisibleSystemTitle(JNIEnv
|
||||
return false;
|
||||
}
|
||||
|
||||
return smdh->flags & Loader::SMDH::Flags::Visible;
|
||||
return smdh->flags.visible;
|
||||
}
|
||||
|
||||
jstring Java_org_citra_citra_1emu_model_GameInfo_getFileType(JNIEnv* env, jobject obj) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -40,6 +40,7 @@ static jfieldID s_game_info_pointer;
|
||||
|
||||
static jclass s_disk_cache_progress_class;
|
||||
static jmethodID s_disk_cache_load_progress;
|
||||
static jmethodID s_compress_progress_method;
|
||||
static std::unordered_map<VideoCore::LoadCallbackStage, jobject> s_java_load_callback_stages;
|
||||
|
||||
static jclass s_cia_install_helper_class;
|
||||
@ -131,6 +132,10 @@ jmethodID GetDiskCacheLoadProgress() {
|
||||
return s_disk_cache_load_progress;
|
||||
}
|
||||
|
||||
jmethodID GetCompressProgressMethod() {
|
||||
return s_compress_progress_method;
|
||||
}
|
||||
|
||||
jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage) {
|
||||
const auto it = s_java_load_callback_stages.find(stage);
|
||||
ASSERT_MSG(it != s_java_load_callback_stages.end(), "Invalid LoadCallbackStage: {}", stage);
|
||||
@ -205,6 +210,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
s_disk_cache_load_progress = env->GetStaticMethodID(
|
||||
s_disk_cache_progress_class, "loadProgress",
|
||||
"(Lorg/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage;II)V");
|
||||
s_compress_progress_method =
|
||||
env->GetStaticMethodID(s_native_library_class, "onCompressProgress", "(JJ)V");
|
||||
// Initialize LoadCallbackStage map
|
||||
const auto to_java_load_callback_stage = [env,
|
||||
load_callback_stage_class](const std::string& stage) {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -40,6 +40,7 @@ jfieldID GetGameInfoPointer();
|
||||
|
||||
jclass GetDiskCacheProgressClass();
|
||||
jmethodID GetDiskCacheLoadProgress();
|
||||
jmethodID GetCompressProgressMethod();
|
||||
jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage);
|
||||
|
||||
jclass GetCiaInstallHelperClass();
|
||||
|
||||
@ -34,10 +34,12 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "common/zstd_compression.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/default_applets.h"
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/hw/unique_data.h"
|
||||
#include "core/loader/loader.h"
|
||||
@ -74,6 +76,17 @@ namespace {
|
||||
ANativeWindow* s_surface;
|
||||
ANativeWindow* s_secondary_surface;
|
||||
|
||||
enum class CompressionStatus : jint {
|
||||
Success = 0,
|
||||
Compress_Unsupported = 1,
|
||||
Compress_AlreadyCompressed = 2,
|
||||
Compress_Failed = 3,
|
||||
Decompress_Unsupported = 4,
|
||||
Decompress_NotCompressed = 5,
|
||||
Decompress_Failed = 6,
|
||||
Installed_Application = 7,
|
||||
};
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
|
||||
std::unique_ptr<EmuWindow_Android> window;
|
||||
std::unique_ptr<EmuWindow_Android> secondary_window;
|
||||
@ -464,6 +477,163 @@ jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env,
|
||||
return ToJString(env, "");
|
||||
}
|
||||
|
||||
static CompressionStatus GetCompressFileInfo(Loader::AppLoader::CompressFileInfo& out_info,
|
||||
size_t& out_frame_size, const std::string& filepath,
|
||||
bool compress) {
|
||||
|
||||
if (Service::FS::IsInstalledApplication(filepath)) {
|
||||
return CompressionStatus::Installed_Application;
|
||||
}
|
||||
|
||||
Loader::AppLoader::CompressFileInfo compress_info{};
|
||||
compress_info.is_supported = false;
|
||||
size_t frame_size{};
|
||||
auto loader = Loader::GetLoader(filepath);
|
||||
if (loader) {
|
||||
compress_info = loader->GetCompressFileInfo();
|
||||
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_FRAME_SIZE;
|
||||
} else {
|
||||
bool is_compressed = false;
|
||||
if (Service::AM::CheckCIAToInstall(filepath, is_compressed, compress ? true : false) ==
|
||||
Service::AM::InstallStatus::Success) {
|
||||
compress_info.is_supported = true;
|
||||
compress_info.is_compressed = is_compressed;
|
||||
compress_info.recommended_compressed_extension = "zcia";
|
||||
compress_info.recommended_uncompressed_extension = "cia";
|
||||
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
|
||||
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_CIA_FRAME_SIZE;
|
||||
if (compress) {
|
||||
auto meta_info = Service::AM::GetCIAInfos(filepath);
|
||||
if (meta_info.Succeeded()) {
|
||||
const auto& meta_info_val = meta_info.Unwrap();
|
||||
std::vector<u8> value(sizeof(Service::AM::TitleInfo));
|
||||
memcpy(value.data(), &meta_info_val.first, sizeof(Service::AM::TitleInfo));
|
||||
compress_info.default_metadata.emplace("titleinfo", value);
|
||||
if (meta_info_val.second) {
|
||||
value.resize(sizeof(Loader::SMDH));
|
||||
memcpy(value.data(), meta_info_val.second.get(), sizeof(Loader::SMDH));
|
||||
compress_info.default_metadata.emplace("smdh", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!compress_info.is_supported) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Error {} file {}, the selected file is not a compatible 3DS ROM format or is "
|
||||
"encrypted.",
|
||||
compress ? "compressing" : "decompressing", filepath);
|
||||
return compress ? CompressionStatus::Compress_Unsupported
|
||||
: CompressionStatus::Decompress_Unsupported;
|
||||
}
|
||||
if (compress_info.is_compressed && compress) {
|
||||
LOG_ERROR(Frontend, "Error compressing file {}, the selected file is already compressed",
|
||||
filepath);
|
||||
return CompressionStatus::Compress_AlreadyCompressed;
|
||||
}
|
||||
if (!compress_info.is_compressed && !compress) {
|
||||
LOG_ERROR(Frontend,
|
||||
"Error decompressing file {}, the selected file is already decompressed",
|
||||
filepath);
|
||||
return CompressionStatus::Decompress_NotCompressed;
|
||||
}
|
||||
|
||||
out_info = compress_info;
|
||||
out_frame_size = frame_size;
|
||||
return CompressionStatus::Success;
|
||||
}
|
||||
|
||||
jint Java_org_citra_citra_1emu_NativeLibrary_compressFileNative(JNIEnv* env, jobject obj,
|
||||
jstring j_input_path,
|
||||
jstring j_output_path) {
|
||||
const std::string input_path = GetJString(env, j_input_path);
|
||||
const std::string output_path = GetJString(env, j_output_path);
|
||||
|
||||
Loader::AppLoader::CompressFileInfo compress_info{};
|
||||
size_t frame_size{};
|
||||
CompressionStatus stat = GetCompressFileInfo(compress_info, frame_size, input_path, true);
|
||||
if (stat != CompressionStatus::Success) {
|
||||
return static_cast<jint>(stat);
|
||||
}
|
||||
|
||||
auto progress = [](std::size_t processed, std::size_t total) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetCompressProgressMethod(), static_cast<jlong>(total),
|
||||
static_cast<jlong>(processed));
|
||||
};
|
||||
|
||||
bool success =
|
||||
FileUtil::CompressZ3DSFile(input_path, output_path, compress_info.underlying_magic,
|
||||
frame_size, progress, compress_info.default_metadata);
|
||||
if (!success) {
|
||||
FileUtil::Delete(output_path);
|
||||
return static_cast<jint>(CompressionStatus::Compress_Failed);
|
||||
}
|
||||
|
||||
return static_cast<jint>(CompressionStatus::Success);
|
||||
}
|
||||
|
||||
jint Java_org_citra_citra_1emu_NativeLibrary_decompressFileNative(JNIEnv* env, jobject obj,
|
||||
jstring j_input_path,
|
||||
jstring j_output_path) {
|
||||
const std::string input_path = GetJString(env, j_input_path);
|
||||
const std::string output_path = GetJString(env, j_output_path);
|
||||
|
||||
Loader::AppLoader::CompressFileInfo compress_info{};
|
||||
size_t frame_size{};
|
||||
CompressionStatus stat = GetCompressFileInfo(compress_info, frame_size, input_path, false);
|
||||
if (stat != CompressionStatus::Success) {
|
||||
return static_cast<jint>(stat);
|
||||
}
|
||||
|
||||
auto progress = [](std::size_t processed, std::size_t total) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetCompressProgressMethod(), static_cast<jlong>(total),
|
||||
static_cast<jlong>(processed));
|
||||
};
|
||||
|
||||
bool success = FileUtil::DeCompressZ3DSFile(input_path, output_path, progress);
|
||||
if (!success) {
|
||||
FileUtil::Delete(output_path);
|
||||
return static_cast<jint>(CompressionStatus::Decompress_Failed);
|
||||
}
|
||||
|
||||
return static_cast<jint>(CompressionStatus::Success);
|
||||
}
|
||||
|
||||
jstring Java_org_citra_citra_1emu_NativeLibrary_getRecommendedExtension(
|
||||
JNIEnv* env, jobject obj, jstring j_input_path, jboolean j_should_compress) {
|
||||
const std::string input_path = GetJString(env, j_input_path);
|
||||
|
||||
std::string compressed_ext;
|
||||
std::string uncompressed_ext;
|
||||
|
||||
auto loader = Loader::GetLoader(input_path);
|
||||
if (loader) {
|
||||
auto compress_info = loader->GetCompressFileInfo();
|
||||
if (compress_info.is_supported) {
|
||||
compressed_ext = compress_info.recommended_compressed_extension;
|
||||
uncompressed_ext = compress_info.recommended_uncompressed_extension;
|
||||
}
|
||||
} else {
|
||||
bool is_compressed = false;
|
||||
if (Service::AM::CheckCIAToInstall(input_path, is_compressed, true) ==
|
||||
Service::AM::InstallStatus::Success) {
|
||||
compressed_ext = "zcia";
|
||||
uncompressed_ext = "cia";
|
||||
}
|
||||
}
|
||||
|
||||
if (compressed_ext.empty()) {
|
||||
return env->NewStringUTF("");
|
||||
}
|
||||
|
||||
return env->NewStringUTF(j_should_compress ? compressed_ext.c_str() : uncompressed_ext.c_str());
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env,
|
||||
[[maybe_unused]] jobject obj,
|
||||
jstring j_directory) {
|
||||
|
||||
@ -45,7 +45,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" app:layout_constraintStart_toStartOf="parent"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Application Title" />
|
||||
|
||||
@ -166,7 +167,6 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="start|center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/horizontal_layout">
|
||||
@ -179,6 +179,29 @@
|
||||
android:contentDescription="@string/cheats"
|
||||
android:text="@string/cheats" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/compress_tray"
|
||||
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="start|center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/game_button_tray">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/compress_decompress"
|
||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/compress"
|
||||
android:text="@string/compress" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/compress_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||
android:textColor="?attr/colorOnSurface"
|
||||
android:visibility="visible"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/compress_progress"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:indeterminate="false"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
</LinearLayout>
|
||||
@ -238,9 +238,8 @@
|
||||
</integer-array>
|
||||
|
||||
<string-array name="render3dModes">
|
||||
<item>@string/off</item>
|
||||
<item>@string/side_by_side</item>
|
||||
<item>@string/reverse_side_by_side</item>
|
||||
<item>@string/side_by_side_full</item>
|
||||
<item>@string/anaglyph</item>
|
||||
<item>@string/interlaced</item>
|
||||
<item>@string/reverse_interlaced</item>
|
||||
@ -248,7 +247,6 @@
|
||||
</string-array>
|
||||
|
||||
<integer-array name="render3dValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
@ -257,6 +255,20 @@
|
||||
<item>6</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="render3dWhichDisplay">
|
||||
<item>@string/off</item>
|
||||
<item>@string/render_3d_which_display_both</item>
|
||||
<item>@string/render_3d_which_display_primary</item>
|
||||
<item>@string/render_3d_which_display_secondary</item>
|
||||
</string-array>
|
||||
|
||||
<integer-array name="render3dDisplayValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
</integer-array>
|
||||
|
||||
<string-array name="graphicsApiNames">
|
||||
<item>@string/opengles</item>
|
||||
<item>@string/vulkan</item>
|
||||
|
||||
@ -140,8 +140,8 @@
|
||||
<string name="input_dialog_description">Press or move an input.</string>
|
||||
<string name="input_binding">Input Binding</string>
|
||||
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string>
|
||||
<string name="input_binding_description_vertical_axis">Move your joystick up or down.</string>
|
||||
<string name="input_binding_description_horizontal_axis">Move your joystick left or right.</string>
|
||||
<string name="input_binding_description_vertical_axis">Move your joystick down</string>
|
||||
<string name="input_binding_description_horizontal_axis">Move your joystick right</string>
|
||||
<string name="button_a" translatable="false">A</string>
|
||||
<string name="button_b" translatable="false">B</string>
|
||||
<string name="button_select" translatable="false">SELECT</string>
|
||||
@ -289,10 +289,18 @@
|
||||
<string name="debug_warning">Warning: Modifying these settings will slow emulation</string>
|
||||
<string name="stereoscopy">Stereoscopy</string>
|
||||
<string name="render3d">Stereoscopic 3D Mode</string>
|
||||
<string name="render3d_description">Choose the stereoscopic 3D mode for 3D rendering. Side by Side modes are most common in modern use. Anaglyph and Interlaced modes will always apply to all connected displays.</string>
|
||||
<string name="factor3d">Depth</string>
|
||||
<string name="factor3d_description">Specifies the value of the 3D slider. This should be set to higher than 0% when Stereoscopic 3D is enabled.\nNote: Depth values over 100% are not possible on real hardware and may cause graphical issues</string>
|
||||
<string name="disable_right_eye_render">Disable Right Eye Render</string>
|
||||
<string name="disable_right_eye_render_description">Greatly improves performance in some applications, but can cause flickering in others.</string>
|
||||
<string name="swap_eyes_3d">Swap Eyes</string>
|
||||
<string name="swap_eyes_3d_description">Swaps which eye is shown in which side. Combined with Side by Side mode makes it possible to see 3D by crossing your eyes!</string>
|
||||
<string name="render_3d_which_display">Render Stereoscopic 3D</string>
|
||||
<string name="render_3d_which_display_description">Whether to enable stereoscopic 3D, and on which displays. The single display options are only relevant when multiple displays are connected.</string>
|
||||
<string name="render_3d_which_display_both">On (All Displays)</string>
|
||||
<string name="render_3d_which_display_primary">On (Primary Display Only)</string>
|
||||
<string name="render_3d_which_display_secondary">On (Secondary Display Only)</string>
|
||||
<string name="cardboard_vr">Cardboard VR</string>
|
||||
<string name="cardboard_screen_size">Cardboard Screen Size</string>
|
||||
<string name="cardboard_screen_size_description">Scales the screen to a percentage of its original size.</string>
|
||||
@ -723,7 +731,7 @@
|
||||
|
||||
<!-- Render 3D modes -->
|
||||
<string name="side_by_side">Side by Side</string>
|
||||
<string name="reverse_side_by_side">Reverse Side by Side</string>
|
||||
<string name="side_by_side_full">Side by Side Full Width</string>
|
||||
<string name="anaglyph">Anaglyph</string>
|
||||
<string name="interlaced">Interlaced</string>
|
||||
<string name="reverse_interlaced">Reverse Interlaced</string>
|
||||
@ -912,4 +920,19 @@
|
||||
<string name="emulation_occupied_quicksave_slot">Quicksave - %1$tF %1$tR</string>
|
||||
<string name="quickload_not_found">No Quicksave available.</string>
|
||||
|
||||
<!-- File Compression -->
|
||||
<string name="compress">Compress</string>
|
||||
<string name="compressing">Compressing…</string>
|
||||
<string name="decompress">Decompress</string>
|
||||
<string name="decompressing">Decompressing…</string>
|
||||
<string name="compress_success">Compression completed successfully.</string>
|
||||
<string name="compress_unsupported">Compression not supported for this file.</string>
|
||||
<string name="compress_already">File is already compressed.</string>
|
||||
<string name="compress_failed">Compression failed.</string>
|
||||
<string name="decompress_success">Decompression completed successfully.</string>
|
||||
<string name="decompress_unsupported">Decompression not supported for this file.</string>
|
||||
<string name="decompress_not_compressed">File is not compressed.</string>
|
||||
<string name="decompress_failed">Decompression failed.</string>
|
||||
<string name="compress_decompress_installed_app">Already installed applications cannot be compressed or decompressed.</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@ -517,6 +517,8 @@ void QtConfig::ReadLayoutValues() {
|
||||
|
||||
ReadGlobalSetting(Settings::values.render_3d);
|
||||
ReadGlobalSetting(Settings::values.factor_3d);
|
||||
ReadGlobalSetting(Settings::values.swap_eyes_3d);
|
||||
ReadGlobalSetting(Settings::values.render_3d_which_display);
|
||||
ReadGlobalSetting(Settings::values.filter_mode);
|
||||
ReadGlobalSetting(Settings::values.pp_shader_name);
|
||||
ReadGlobalSetting(Settings::values.anaglyph_shader_name);
|
||||
@ -1083,6 +1085,8 @@ void QtConfig::SaveLayoutValues() {
|
||||
|
||||
WriteGlobalSetting(Settings::values.render_3d);
|
||||
WriteGlobalSetting(Settings::values.factor_3d);
|
||||
WriteGlobalSetting(Settings::values.swap_eyes_3d);
|
||||
WriteGlobalSetting(Settings::values.render_3d_which_display);
|
||||
WriteGlobalSetting(Settings::values.filter_mode);
|
||||
WriteGlobalSetting(Settings::values.pp_shader_name);
|
||||
WriteGlobalSetting(Settings::values.anaglyph_shader_name);
|
||||
|
||||
@ -57,6 +57,7 @@ void ConfigureEnhancements::SetConfiguration() {
|
||||
|
||||
ui->render_3d_combobox->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.render_3d.GetValue()));
|
||||
ui->swap_eyes_3d->setChecked(Settings::values.swap_eyes_3d.GetValue());
|
||||
ui->factor_3d->setValue(Settings::values.factor_3d.GetValue());
|
||||
ui->mono_rendering_eye->setCurrentIndex(
|
||||
static_cast<int>(Settings::values.mono_render_option.GetValue()));
|
||||
@ -111,6 +112,7 @@ void ConfigureEnhancements::ApplyConfiguration() {
|
||||
ui->resolution_factor_combobox);
|
||||
Settings::values.render_3d =
|
||||
static_cast<Settings::StereoRenderOption>(ui->render_3d_combobox->currentIndex());
|
||||
Settings::values.swap_eyes_3d = ui->swap_eyes_3d->isChecked();
|
||||
Settings::values.factor_3d = ui->factor_3d->value();
|
||||
Settings::values.mono_render_option =
|
||||
static_cast<Settings::MonoRenderOption>(ui->mono_rendering_eye->currentIndex());
|
||||
|
||||
@ -113,7 +113,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="toggle_linear_filter">
|
||||
<property name="text">
|
||||
<string>Enable linear filtering</string>
|
||||
<string>Enable Linear Filtering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -236,7 +236,7 @@
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reverse Side by Side</string>
|
||||
<string>Side by Side Full Width</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
@ -318,14 +318,21 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="disable_right_eye_render">
|
||||
<property name="text">
|
||||
<string>Disable right eye rendering</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Disable Right Eye Rendering</p><p>Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some applications, but can cause flickering in others.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="disable_right_eye_render">
|
||||
<property name="text">
|
||||
<string>Disable Right Eye Rendering</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Disable Right Eye Rendering</p><p>Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some applications, but can cause flickering in others.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="swap_eyes_3d">
|
||||
<property name="text">
|
||||
<string>Swap Eyes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include "citra_qt/uisettings.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/archive_extsavedata.h"
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
@ -1054,6 +1055,13 @@ const QStringList GameList::supported_file_extensions = {
|
||||
};
|
||||
|
||||
void GameList::RefreshGameDirectory() {
|
||||
|
||||
// Do not scan directories when the system is powered on, it will be
|
||||
// repopulated on shutdown anyways.
|
||||
if (Core::System::GetInstance().IsPoweredOn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
|
||||
LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list.");
|
||||
PopulateAsync(UISettings::values.game_dirs);
|
||||
|
||||
@ -92,7 +92,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||
if (Loader::IsValidSMDH(smdh)) {
|
||||
if (system_title) {
|
||||
auto smdh_struct = reinterpret_cast<Loader::SMDH*>(smdh.data());
|
||||
if (!(smdh_struct->flags & Loader::SMDH::Flags::Visible)) {
|
||||
if (!smdh_struct->flags.visible) {
|
||||
// Skip system titles without the visible flag.
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -163,6 +163,10 @@ render_3d =
|
||||
# 0 - 100: Intensity. 0 (default)
|
||||
factor_3d =
|
||||
|
||||
# Swap Eyes in 3D
|
||||
# true or false (default)
|
||||
swap_eyes_3d =
|
||||
|
||||
# Change Default Eye to Render When in Monoscopic Mode
|
||||
# 0 (default): Left, 1: Right
|
||||
mono_render_option =
|
||||
|
||||
@ -305,7 +305,7 @@ bool DeleteDir(const std::string& filename) {
|
||||
}
|
||||
|
||||
bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcPath, destFullPath);
|
||||
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFullPath, destFullPath);
|
||||
#ifdef _WIN32
|
||||
if (_wrename(Common::UTF8ToUTF16W(srcFullPath).c_str(),
|
||||
Common::UTF8ToUTF16W(destFullPath).c_str()) == 0)
|
||||
|
||||
@ -108,6 +108,9 @@ void LogSettings() {
|
||||
log_setting("Renderer_DisableRightEyeRender", values.disable_right_eye_render.GetValue());
|
||||
log_setting("Stereoscopy_Render3d", values.render_3d.GetValue());
|
||||
log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue());
|
||||
log_setting("Stereoscopy_Swap_Eyes", values.swap_eyes_3d.GetValue());
|
||||
log_setting("Stereoscopy_Render_3d_to_which_display",
|
||||
values.render_3d_which_display.GetValue());
|
||||
log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue());
|
||||
if (values.render_3d.GetValue() == StereoRenderOption::Anaglyph) {
|
||||
log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue());
|
||||
@ -219,6 +222,7 @@ void RestoreGlobalState(bool is_powered_on) {
|
||||
values.bg_green.SetGlobal(true);
|
||||
values.bg_blue.SetGlobal(true);
|
||||
values.render_3d.SetGlobal(true);
|
||||
values.swap_eyes_3d.SetGlobal(true);
|
||||
values.factor_3d.SetGlobal(true);
|
||||
values.filter_mode.SetGlobal(true);
|
||||
values.pp_shader_name.SetGlobal(true);
|
||||
|
||||
@ -72,7 +72,7 @@ enum class SmallScreenPosition : u32 {
|
||||
enum class StereoRenderOption : u32 {
|
||||
Off = 0,
|
||||
SideBySide = 1,
|
||||
ReverseSideBySide = 2,
|
||||
SideBySideFull = 2,
|
||||
Anaglyph = 3,
|
||||
Interlaced = 4,
|
||||
ReverseInterlaced = 5,
|
||||
@ -86,6 +86,14 @@ enum class MonoRenderOption : u32 {
|
||||
RightEye = 1,
|
||||
};
|
||||
|
||||
// on android, which displays to render stereo mode to
|
||||
enum class StereoWhichDisplay : u32 {
|
||||
None = 0, // equivalent to StereoRenderOption = Off
|
||||
Both = 1,
|
||||
PrimaryOnly = 2,
|
||||
SecondaryOnly = 3
|
||||
};
|
||||
|
||||
enum class AudioEmulation : u32 {
|
||||
HLE = 0,
|
||||
LLE = 1,
|
||||
@ -562,6 +570,10 @@ struct Values {
|
||||
|
||||
SwitchableSetting<StereoRenderOption> render_3d{StereoRenderOption::Off, "render_3d"};
|
||||
SwitchableSetting<u32> factor_3d{0, "factor_3d"};
|
||||
SwitchableSetting<bool> swap_eyes_3d{false, "swap_eyes_3d"};
|
||||
|
||||
SwitchableSetting<StereoWhichDisplay> render_3d_which_display{StereoWhichDisplay::None,
|
||||
"render_3d_which_display"};
|
||||
SwitchableSetting<MonoRenderOption> mono_render_option{MonoRenderOption::LeftEye,
|
||||
"mono_render_option"};
|
||||
|
||||
|
||||
@ -362,8 +362,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
ASSERT(n3ds_hw_caps.first);
|
||||
app_n3ds_hw_capabilities = n3ds_hw_caps.first.value();
|
||||
|
||||
if (!Settings::values.is_new_3ds.GetValue() &&
|
||||
app_n3ds_hw_capabilities.memory_mode != Kernel::New3dsMemoryMode::Legacy) {
|
||||
if (!Settings::values.is_new_3ds.GetValue() && app_loader->IsN3DSExclusive()) {
|
||||
return ResultStatus::ErrorN3DSApplication;
|
||||
}
|
||||
|
||||
@ -374,14 +373,10 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
// proper memory mode.
|
||||
if (used_default_mem_mode) {
|
||||
|
||||
// If we are on the Old 3DS prod mode, the application is not a New 3DS application and
|
||||
// the application memory mode does not match, we need to adjust it. We do not need
|
||||
// adjustment if we are on the New 3DS prod mode, as that one overrides all the Old 3DS
|
||||
// memory modes.
|
||||
if (system_mem_mode == Kernel::MemoryMode::Prod &&
|
||||
app_n3ds_hw_capabilities.memory_mode == Kernel::New3dsMemoryMode::Legacy &&
|
||||
app_mem_mode != system_mem_mode) {
|
||||
|
||||
// If we are on the Old 3DS prod mode and the application memory mode does not match, we
|
||||
// need to adjust it. We do not need adjustment if we are on the New 3DS prod mode, as that
|
||||
// one overrides all the Old 3DS memory modes.
|
||||
if (system_mem_mode == Kernel::MemoryMode::Prod && app_mem_mode != system_mem_mode) {
|
||||
system_mem_mode = app_mem_mode;
|
||||
}
|
||||
|
||||
@ -389,7 +384,6 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||
// memory mode (only CTRAging is known to do this), adjust the memory mode.
|
||||
else if (system_mem_mode == Kernel::MemoryMode::NewProd &&
|
||||
app_n3ds_hw_capabilities.memory_mode == Kernel::New3dsMemoryMode::NewDev1) {
|
||||
|
||||
system_mem_mode = Kernel::MemoryMode::NewDev1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -22,6 +24,7 @@
|
||||
#include "core/file_sys/file_backend.h"
|
||||
#include "core/file_sys/plugin_3gx.h"
|
||||
#include "core/file_sys/plugin_3gx_bootloader.h"
|
||||
#include "core/hle/kernel/config_mem.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@ -173,10 +176,10 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
}
|
||||
|
||||
const std::array<u32, 4> mem_region_sizes = {
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
3 * 1024 * 1024, // 3 MiB
|
||||
4 * 1024 * 1024 // 4 MiB
|
||||
5 * 1024 * 1024, // 5 MiB
|
||||
2 * 1024 * 1024, // 2 MiB
|
||||
10 * 1024 * 1024, // 10 MiB
|
||||
5 * 1024 * 1024 // 5 MiB (reserved)
|
||||
};
|
||||
|
||||
const bool is_mem_private = header.infos.flags.use_private_memory != 0;
|
||||
@ -184,65 +187,64 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
|
||||
// Calculate the sizes of the different memory regions
|
||||
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
|
||||
const u32 exe_offset = 0;
|
||||
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
|
||||
data_section.size() + header.executable.bss_size + 0x1000) &
|
||||
~0xFFFu;
|
||||
const u32 bootloader_offset = exe_offset + exe_size;
|
||||
const u32 bootloader_size = bootloader_memory_size;
|
||||
const u32 heap_offset = bootloader_offset + bootloader_size;
|
||||
const u32 heap_size = block_size - heap_offset;
|
||||
|
||||
// Allocate the framebuffer block so that is in the highest FCRAM position possible
|
||||
auto offset_fb =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
|
||||
if (!offset_fb) {
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
// Allocate a block of memory for the plugin
|
||||
std::optional<u32> offset;
|
||||
if (kernel.GetMemoryMode() == Kernel::MemoryMode::NewProd ||
|
||||
(plg_context.use_user_load_parameters &&
|
||||
plg_context.user_load_parameters.plugin_memory_strategy ==
|
||||
Service::PLGLDR::PLG_LDR::PluginMemoryStrategy::PLG_STRATEGY_MODE3)) {
|
||||
// Allocate memory block from the end of the APPLICATION region
|
||||
offset =
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->RLinearAllocate(block_size);
|
||||
|
||||
// If the reported available APP mem equals the actual size, remove the plugin block size.
|
||||
if (offset) {
|
||||
auto& config_mem = kernel.GetConfigMemHandler();
|
||||
if (config_mem.GetConfigMem().app_mem_alloc ==
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->size) {
|
||||
config_mem.GetConfigMem().app_mem_alloc -= block_size;
|
||||
}
|
||||
}
|
||||
plg_context.memory_region = Kernel::MemoryRegion::APPLICATION;
|
||||
} else {
|
||||
// Allocate memory block from the start of the SYSTEM region
|
||||
offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->LinearAllocate(block_size);
|
||||
plg_context.memory_region = Kernel::MemoryRegion::SYSTEM;
|
||||
}
|
||||
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
|
||||
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
|
||||
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
|
||||
|
||||
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap_fb.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(block_size - _3GX_fb_size);
|
||||
if (!offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
|
||||
|
||||
// Then we map part of the memory, which contains the executable
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private
|
||||
: Kernel::MemoryState::Shared);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
u32 fcram_offset = offset.value();
|
||||
|
||||
auto backing_memory_exe = kernel.memory.GetFCRAMRef(fcram_offset + exe_offset);
|
||||
std::fill(backing_memory_exe.GetPtr(), backing_memory_exe.GetPtr() + exe_size, 0);
|
||||
|
||||
// Map the executable
|
||||
auto vma_exe = process.vm_manager.MapBackingMemory(
|
||||
_3GX_exe_load_addr, backing_memory_exe, exe_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_exe.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_exe.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
// Prepare plugin header and write it
|
||||
PluginHeader plugin_header = {0};
|
||||
plugin_header.version = header.version;
|
||||
plugin_header.exe_size = exe_size;
|
||||
plugin_header.heap_VA = _3GX_heap_load_addr;
|
||||
plugin_header.heap_size = block_size - exe_size;
|
||||
plugin_header.heap_size = heap_size;
|
||||
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
|
||||
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
|
||||
plugin_header.plgldr_event = plg_context.plg_event;
|
||||
@ -254,39 +256,46 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
|
||||
}
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
|
||||
// Write text section
|
||||
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
|
||||
text_section.data(), header.executable.code_size);
|
||||
// Write rodata section
|
||||
kernel.memory.WriteBlock(
|
||||
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
|
||||
rodata_section.data(), header.executable.rodata_size);
|
||||
|
||||
// Map the rest of the memory at the heap location
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
|
||||
block_size - exe_size - _3GX_fb_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
// Write data section
|
||||
kernel.memory.WriteBlock(process,
|
||||
_3GX_exe_load_addr + sizeof(PluginHeader) +
|
||||
header.executable.code_size + header.executable.rodata_size,
|
||||
data_section.data(), header.executable.data_size);
|
||||
|
||||
// Allocate a block from the end of FCRAM and clear it
|
||||
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->RLinearAllocate(bootloader_memory_size);
|
||||
if (!bootloader_offset) {
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
|
||||
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
|
||||
->Free(*offset, block_size - _3GX_fb_size);
|
||||
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
|
||||
plg_context.plugin_path);
|
||||
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
|
||||
}
|
||||
// Map bootloader
|
||||
const bool use_internal = plg_context.load_exe_func.empty();
|
||||
MapBootloader(
|
||||
process, kernel, *bootloader_offset,
|
||||
process, kernel, fcram_offset + bootloader_offset,
|
||||
(use_internal) ? exe_load_func : plg_context.load_exe_func,
|
||||
(use_internal) ? exe_load_args : plg_context.load_exe_args,
|
||||
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
|
||||
header.infos.exe_load_checksum,
|
||||
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
|
||||
|
||||
// Map plugin heap
|
||||
auto backing_memory_heap = kernel.memory.GetFCRAMRef(fcram_offset + heap_offset);
|
||||
std::fill(backing_memory_heap.GetPtr(), backing_memory_heap.GetPtr() + heap_size, 0);
|
||||
|
||||
auto vma_heap = process.vm_manager.MapBackingMemory(
|
||||
_3GX_heap_load_addr, backing_memory_heap, heap_size,
|
||||
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
|
||||
ASSERT(vma_heap.Succeeded());
|
||||
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + fcram_offset + heap_offset);
|
||||
plg_context.plugin_loaded = true;
|
||||
plg_context.plugin_process_id = process.process_id;
|
||||
plg_context.use_user_load_parameters = false;
|
||||
plg_context.memory_block = {fcram_offset, block_size};
|
||||
|
||||
return Loader::ResultStatus::Success;
|
||||
}
|
||||
|
||||
@ -355,7 +364,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
|
||||
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
|
||||
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
|
||||
backing_memory, bootloader_memory_size,
|
||||
Kernel::MemoryState::Continuous);
|
||||
Kernel::MemoryState::Private);
|
||||
ASSERT(vma.Succeeded());
|
||||
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
|
||||
|
||||
|
||||
@ -25,9 +25,8 @@ public:
|
||||
std::mutex mutex;
|
||||
|
||||
bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
|
||||
|
||||
float touch_x = 0.0f; ///< Touchpad X-position
|
||||
float touch_y = 0.0f; ///< Touchpad Y-position
|
||||
float touch_x = 0.0f; ///< Touchpad X-position
|
||||
float touch_y = 0.0f; ///< Touchpad Y-position
|
||||
|
||||
private:
|
||||
class Device : public Input::TouchDevice {
|
||||
@ -56,24 +55,44 @@ EmuWindow::EmuWindow(bool is_secondary_) : is_secondary{is_secondary_} {
|
||||
|
||||
EmuWindow::~EmuWindow() = default;
|
||||
|
||||
Settings::StereoRenderOption EmuWindow::get3DMode() const {
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
#ifndef ANDROID
|
||||
// on desktop, if separate windows and this is the bottom screen, then no stereo
|
||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
|
||||
((!is_secondary && Settings::values.swap_screen.GetValue()) ||
|
||||
(is_secondary && !Settings::values.swap_screen.GetValue()))) {
|
||||
render_3d_mode = Settings::StereoRenderOption::Off;
|
||||
}
|
||||
#else
|
||||
// adjust the StereoRenderOption setting to Off if appropriate on mobile
|
||||
Settings::StereoWhichDisplay whichDisplay = Settings::values.render_3d_which_display.GetValue();
|
||||
if (whichDisplay == Settings::StereoWhichDisplay::None ||
|
||||
whichDisplay == Settings::StereoWhichDisplay::PrimaryOnly && is_secondary ||
|
||||
whichDisplay == Settings::StereoWhichDisplay::SecondaryOnly && !is_secondary) {
|
||||
render_3d_mode = Settings::StereoRenderOption::Off;
|
||||
}
|
||||
#endif
|
||||
return render_3d_mode;
|
||||
}
|
||||
|
||||
bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x,
|
||||
unsigned framebuffer_y) {
|
||||
#ifndef ANDROID
|
||||
// If separate windows and the touch is in the primary (top) screen, ignore it.
|
||||
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
|
||||
!is_secondary && !Settings::values.swap_screen.GetValue()) {
|
||||
((!is_secondary && !Settings::values.swap_screen.GetValue()) ||
|
||||
(is_secondary && Settings::values.swap_screen.GetValue()))) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
Settings::StereoRenderOption render_3d_mode = get3DMode();
|
||||
|
||||
if (!layout.bottom_screen_enabled) {
|
||||
return false;
|
||||
if (framebuffer_x > layout.width / 2 &&
|
||||
render_3d_mode == Settings::StereoRenderOption::SideBySideFull) {
|
||||
framebuffer_x = static_cast<unsigned>(framebuffer_x - layout.width / 2);
|
||||
}
|
||||
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) {
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
|
||||
return (framebuffer_y >= layout.bottom_screen.top &&
|
||||
framebuffer_y < layout.bottom_screen.bottom &&
|
||||
((framebuffer_x >= layout.bottom_screen.left / 2 &&
|
||||
@ -97,25 +116,19 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns
|
||||
}
|
||||
|
||||
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const {
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
bool separate_win = false;
|
||||
#ifndef ANDROID
|
||||
separate_win =
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
|
||||
#endif
|
||||
|
||||
Settings::StereoRenderOption render_3d_mode = get3DMode();
|
||||
|
||||
if (new_x >= framebuffer_layout.width / 2) {
|
||||
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
|
||||
!separate_win)
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::SideBySideFull)
|
||||
new_x -= framebuffer_layout.width / 2;
|
||||
else if (render_3d_mode == Settings::StereoRenderOption::CardboardVR)
|
||||
new_x -=
|
||||
(framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
|
||||
}
|
||||
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
|
||||
!separate_win) {
|
||||
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
|
||||
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left / 2);
|
||||
new_x = std::min(new_x, framebuffer_layout.bottom_screen.right / 2 - 1);
|
||||
} else {
|
||||
@ -140,29 +153,22 @@ void EmuWindow::CreateTouchState() {
|
||||
}
|
||||
|
||||
bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
bool separate_win = false;
|
||||
#ifndef ANDROID
|
||||
separate_win =
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
|
||||
#endif
|
||||
|
||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||
return false;
|
||||
Settings::StereoRenderOption render_3d_mode = get3DMode();
|
||||
|
||||
if (framebuffer_x >= framebuffer_layout.width / 2) {
|
||||
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
|
||||
!separate_win)
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::SideBySideFull)
|
||||
framebuffer_x -= framebuffer_layout.width / 2;
|
||||
else if (render_3d_mode == Settings::StereoRenderOption::CardboardVR)
|
||||
framebuffer_x -=
|
||||
(framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
|
||||
}
|
||||
|
||||
std::scoped_lock guard(touch_state->mutex);
|
||||
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
|
||||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
|
||||
!separate_win) {
|
||||
|
||||
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
|
||||
touch_state->touch_x =
|
||||
static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left / 2) /
|
||||
(framebuffer_layout.bottom_screen.right / 2 -
|
||||
@ -204,8 +210,13 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
|
||||
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) {
|
||||
Layout::FramebufferLayout layout;
|
||||
|
||||
const Settings::LayoutOption layout_option = Settings::values.layout_option.GetValue();
|
||||
const Settings::StereoRenderOption stereo_option = get3DMode();
|
||||
bool render_full_stereo = (stereo_option == Settings::StereoRenderOption::SideBySideFull);
|
||||
bool is_bottom = is_secondary;
|
||||
if (Settings::values.swap_screen.GetValue())
|
||||
is_bottom = !is_bottom;
|
||||
|
||||
const Settings::PortraitLayoutOption portrait_layout_option =
|
||||
Settings::values.portrait_layout_option.GetValue();
|
||||
const auto min_size = is_portrait_mode
|
||||
@ -215,6 +226,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
|
||||
|
||||
width = std::max(width, min_size.first);
|
||||
height = std::max(height, min_size.second);
|
||||
if (render_full_stereo) {
|
||||
width = width / 2;
|
||||
}
|
||||
if (is_portrait_mode) {
|
||||
switch (portrait_layout_option) {
|
||||
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
|
||||
@ -280,11 +294,16 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
|
||||
layout = Layout::AndroidSecondaryLayout(width, height);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (render_full_stereo) {
|
||||
layout.width = width * 2;
|
||||
}
|
||||
UpdateMinimumWindowSize(min_size);
|
||||
|
||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
|
||||
layout = Layout::GetCardboardSettings(layout);
|
||||
}
|
||||
layout.render_3d_mode = stereo_option;
|
||||
NotifyFramebufferLayoutChanged(layout);
|
||||
}
|
||||
|
||||
|
||||
@ -258,6 +258,8 @@ public:
|
||||
return is_secondary;
|
||||
}
|
||||
|
||||
Settings::StereoRenderOption get3DMode() const;
|
||||
|
||||
protected:
|
||||
EmuWindow();
|
||||
EmuWindow(bool is_secondary);
|
||||
|
||||
@ -79,7 +79,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
|
||||
|
||||
// TODO: This is kind of gross, make it platform agnostic. -OS
|
||||
#ifdef ANDROID
|
||||
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
|
||||
const float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
const auto aspect_ratio_setting = Settings::values.aspect_ratio.GetValue();
|
||||
|
||||
float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
|
||||
@ -413,53 +413,56 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
bool is_portrait) {
|
||||
u32 width, height, gap;
|
||||
gap = (int)(Settings::values.screen_gap.GetValue()) * res_scale;
|
||||
|
||||
FramebufferLayout layout;
|
||||
if (is_portrait) {
|
||||
auto layout_option = Settings::values.portrait_layout_option.GetValue();
|
||||
switch (layout_option) {
|
||||
case Settings::PortraitLayoutOption::PortraitCustomLayout:
|
||||
return CustomFrameLayout(
|
||||
std::max(Settings::values.custom_portrait_top_x.GetValue() +
|
||||
Settings::values.custom_portrait_top_width.GetValue(),
|
||||
Settings::values.custom_portrait_bottom_x.GetValue() +
|
||||
Settings::values.custom_portrait_bottom_width.GetValue()),
|
||||
std::max(Settings::values.custom_portrait_top_y.GetValue() +
|
||||
Settings::values.custom_portrait_top_height.GetValue(),
|
||||
Settings::values.custom_portrait_bottom_y.GetValue() +
|
||||
Settings::values.custom_portrait_bottom_height.GetValue()),
|
||||
Settings::values.swap_screen.GetValue(), is_portrait);
|
||||
width = std::max(Settings::values.custom_portrait_top_x.GetValue() +
|
||||
Settings::values.custom_portrait_top_width.GetValue(),
|
||||
Settings::values.custom_portrait_bottom_x.GetValue() +
|
||||
Settings::values.custom_portrait_bottom_width.GetValue());
|
||||
height = std::max(Settings::values.custom_portrait_top_y.GetValue() +
|
||||
Settings::values.custom_portrait_top_height.GetValue(),
|
||||
Settings::values.custom_portrait_bottom_y.GetValue() +
|
||||
Settings::values.custom_portrait_bottom_height.GetValue());
|
||||
layout = CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
is_portrait);
|
||||
|
||||
break;
|
||||
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
|
||||
width = Core::kScreenTopWidth * res_scale;
|
||||
// clang-format off
|
||||
height = (static_cast<int>(Core::kScreenTopHeight + Core::kScreenBottomHeight * 1.25) *
|
||||
res_scale) + gap;
|
||||
// clang-format on
|
||||
return PortraitTopFullFrameLayout(width, height,
|
||||
Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
layout =
|
||||
PortraitTopFullFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
break;
|
||||
case Settings::PortraitLayoutOption::PortraitOriginal:
|
||||
width = Core::kScreenTopWidth * res_scale;
|
||||
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
|
||||
return PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
|
||||
layout = PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
auto layout_option = Settings::values.layout_option.GetValue();
|
||||
switch (layout_option) {
|
||||
case Settings::LayoutOption::CustomLayout:
|
||||
return CustomFrameLayout(std::max(Settings::values.custom_top_x.GetValue() +
|
||||
Settings::values.custom_top_width.GetValue(),
|
||||
Settings::values.custom_bottom_x.GetValue() +
|
||||
Settings::values.custom_bottom_width.GetValue()),
|
||||
std::max(Settings::values.custom_top_y.GetValue() +
|
||||
Settings::values.custom_top_height.GetValue(),
|
||||
Settings::values.custom_bottom_y.GetValue() +
|
||||
Settings::values.custom_bottom_height.GetValue()),
|
||||
Settings::values.swap_screen.GetValue(), is_portrait);
|
||||
|
||||
case Settings::LayoutOption::SingleScreen:
|
||||
#ifndef ANDROID
|
||||
case Settings::LayoutOption::SeparateWindows:
|
||||
#endif
|
||||
{
|
||||
layout =
|
||||
CustomFrameLayout(std::max(Settings::values.custom_top_x.GetValue() +
|
||||
Settings::values.custom_top_width.GetValue(),
|
||||
Settings::values.custom_bottom_x.GetValue() +
|
||||
Settings::values.custom_bottom_width.GetValue()),
|
||||
std::max(Settings::values.custom_top_y.GetValue() +
|
||||
Settings::values.custom_top_height.GetValue(),
|
||||
Settings::values.custom_bottom_y.GetValue() +
|
||||
Settings::values.custom_bottom_height.GetValue()),
|
||||
Settings::values.swap_screen.GetValue(), is_portrait);
|
||||
break;
|
||||
case Settings::LayoutOption::SingleScreen: {
|
||||
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
|
||||
if (swap_screens) {
|
||||
width = Core::kScreenBottomWidth * res_scale;
|
||||
@ -471,8 +474,10 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
if (Settings::values.upright_screen.GetValue()) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
return SingleFrameLayout(width, height, swap_screens,
|
||||
Settings::values.upright_screen.GetValue());
|
||||
|
||||
layout = SingleFrameLayout(width, height, swap_screens,
|
||||
Settings::values.upright_screen.GetValue());
|
||||
break;
|
||||
}
|
||||
|
||||
case Settings::LayoutOption::LargeScreen: {
|
||||
@ -501,10 +506,11 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
if (Settings::values.upright_screen.GetValue()) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue(),
|
||||
Settings::values.large_screen_proportion.GetValue(),
|
||||
Settings::values.small_screen_position.GetValue());
|
||||
layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue(),
|
||||
Settings::values.large_screen_proportion.GetValue(),
|
||||
Settings::values.small_screen_position.GetValue());
|
||||
break;
|
||||
}
|
||||
case Settings::LayoutOption::SideScreen:
|
||||
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale + gap;
|
||||
@ -513,10 +519,10 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
if (Settings::values.upright_screen.GetValue()) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue(), 1,
|
||||
Settings::SmallScreenPosition::MiddleRight);
|
||||
|
||||
layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue(), 1,
|
||||
Settings::SmallScreenPosition::MiddleRight);
|
||||
break;
|
||||
case Settings::LayoutOption::HybridScreen:
|
||||
height = Core::kScreenTopHeight * res_scale;
|
||||
|
||||
@ -532,9 +538,9 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
std::swap(width, height);
|
||||
}
|
||||
|
||||
return HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
|
||||
layout = HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
break;
|
||||
case Settings::LayoutOption::Default:
|
||||
default:
|
||||
width = Core::kScreenTopWidth * res_scale;
|
||||
@ -543,10 +549,13 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
|
||||
if (Settings::values.upright_screen.GetValue()) {
|
||||
std::swap(width, height);
|
||||
}
|
||||
return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
|
||||
Settings::values.upright_screen.GetValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return layout;
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,8 @@ struct FramebufferLayout {
|
||||
u32 GetScalingRatio() const;
|
||||
|
||||
static float GetAspectRatioValue(Settings::AspectRatio aspect_ratio);
|
||||
|
||||
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include "common/archives.h"
|
||||
#include "common/serialization/atomic.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/config_mem.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
@ -172,7 +173,9 @@ void KernelSystem::ResetThreadIDs() {
|
||||
|
||||
void KernelSystem::UpdateCPUAndMemoryState(u64 title_id, MemoryMode memory_mode,
|
||||
New3dsHwCapabilities n3ds_hw_cap) {
|
||||
SetRunning804MHz(n3ds_hw_cap.enable_804MHz_cpu);
|
||||
if (Settings::values.is_new_3ds) {
|
||||
SetRunning804MHz(n3ds_hw_cap.enable_804MHz_cpu);
|
||||
}
|
||||
|
||||
u32 tid_high = static_cast<u32>(title_id >> 32);
|
||||
|
||||
|
||||
@ -119,6 +119,11 @@ enum class SystemInfoType {
|
||||
* For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi."
|
||||
*/
|
||||
KERNEL_SPAWNED_PIDS = 26,
|
||||
/**
|
||||
* Check various Luma3DS config values. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications.
|
||||
*/
|
||||
LUMA_CFW_INFO = 0x10000,
|
||||
/**
|
||||
* Check if the current system is a new 3DS. This parameter is not available on real systems,
|
||||
* but can be used by homebrew applications.
|
||||
@ -270,6 +275,13 @@ enum class SystemInfoMemUsageRegion {
|
||||
BASE = 3,
|
||||
};
|
||||
|
||||
enum class SystemInfoLumaCFWInformation {
|
||||
REAL_APP_REGION_SIZE = 0x80, // Gets the real APPLICATION region size,
|
||||
// instead of the one reported by the kernel shared page
|
||||
// which depends on the memory mode.
|
||||
IS_N3DS = 0x201, // Checks if the system is a N3DS or not.
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepted by svcGetSystemInfo param with CITRA_INFORMATION type. Selects which information
|
||||
* to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
|
||||
@ -1827,6 +1839,20 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
|
||||
case SystemInfoType::KERNEL_SPAWNED_PIDS:
|
||||
*out = 5;
|
||||
break;
|
||||
case SystemInfoType::LUMA_CFW_INFO:
|
||||
switch ((SystemInfoLumaCFWInformation)param) {
|
||||
case SystemInfoLumaCFWInformation::REAL_APP_REGION_SIZE:
|
||||
*out = kernel.GetMemoryRegion(MemoryRegion::APPLICATION)->size;
|
||||
break;
|
||||
case SystemInfoLumaCFWInformation::IS_N3DS:
|
||||
*out = Settings::values.is_new_3ds ? 1 : 0;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Kernel_SVC, "unknown GetSystemInfo type=0x10000 region: param={}", param);
|
||||
*out = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case SystemInfoType::NEW_3DS_INFO:
|
||||
// The actual subtypes are not implemented, homebrew just check
|
||||
// this doesn't return an error in n3ds to know the system type
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -30,11 +30,16 @@
|
||||
|
||||
namespace Service::FS {
|
||||
|
||||
bool IsInstalledApplication(std::string_view path) {
|
||||
return path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "title", 0) == 0 ||
|
||||
path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo 3DS", 0) == 0;
|
||||
}
|
||||
|
||||
MediaType GetMediaTypeFromPath(std::string_view path) {
|
||||
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 0) == 0) {
|
||||
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "title", 0) == 0) {
|
||||
return MediaType::NAND;
|
||||
}
|
||||
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 0) == 0) {
|
||||
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo 3DS", 0) == 0) {
|
||||
return MediaType::SDMC;
|
||||
}
|
||||
return MediaType::GameCard;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -51,6 +51,7 @@ enum class ArchiveIdCode : u32 {
|
||||
/// Media types for the archives
|
||||
enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 };
|
||||
|
||||
bool IsInstalledApplication(std::string_view path);
|
||||
MediaType GetMediaTypeFromPath(std::string_view path);
|
||||
|
||||
enum class SpecialContentType : u8 {
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -70,6 +72,9 @@ void PLG_LDR::PluginLoaderContext::serialize(Archive& ar, const unsigned int) {
|
||||
ar & plugin_loaded;
|
||||
ar & is_default_path;
|
||||
ar & plugin_path;
|
||||
ar & memory_region;
|
||||
ar & memory_block;
|
||||
ar & plugin_process_id;
|
||||
ar & use_user_load_parameters;
|
||||
ar & user_load_parameters;
|
||||
ar & plg_event;
|
||||
@ -148,12 +153,21 @@ void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kerne
|
||||
}
|
||||
|
||||
void PLG_LDR::OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel) {
|
||||
if (plgldr_context.plugin_loaded) {
|
||||
if (plgldr_context.plugin_loaded && process.process_id == plgldr_context.plugin_process_id) {
|
||||
u32 status = kernel.memory.Read32(FileSys::Plugin3GXLoader::_3GX_exe_load_addr - 0xC);
|
||||
if (status == 0) {
|
||||
LOG_CRITICAL(Service_PLGLDR, "Failed to launch {}: Checksum failed",
|
||||
plgldr_context.plugin_path);
|
||||
}
|
||||
if (plgldr_context.memory_region != static_cast<Kernel::MemoryRegion>(0)) {
|
||||
kernel.GetMemoryRegion(plgldr_context.memory_region)
|
||||
->Free(plgldr_context.memory_block.first, plgldr_context.memory_block.second);
|
||||
plgldr_context.memory_region = static_cast<Kernel::MemoryRegion>(0);
|
||||
}
|
||||
plgldr_context.plugin_loaded = false;
|
||||
plgldr_context.plugin_process_id = UINT32_MAX;
|
||||
plgldr_context.memory_changed_handle = 0;
|
||||
LOG_INFO(Service_PLGLDR, "Plugin unloaded successfully.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
// Originally MIT-licensed code from The Pixellizer Group
|
||||
|
||||
// Copyright 2022 The Pixellizer Group
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
@ -62,6 +64,9 @@ public:
|
||||
bool plugin_loaded = false;
|
||||
bool is_default_path = false;
|
||||
std::string plugin_path = "";
|
||||
u32 plugin_process_id = UINT32_MAX;
|
||||
Kernel::MemoryRegion memory_region{};
|
||||
std::pair<u32, u32> memory_block{};
|
||||
|
||||
bool use_user_load_parameters = false;
|
||||
PluginLoadParameters user_load_parameters;
|
||||
|
||||
@ -134,6 +134,16 @@ Apploader_Artic::LoadNew3dsHwCapabilities() {
|
||||
return std::make_pair(std::move(caps), ResultStatus::Success);
|
||||
}
|
||||
|
||||
bool Apploader_Artic::IsN3DSExclusive() {
|
||||
std::vector<u8> smdh_buffer;
|
||||
if (ReadIcon(smdh_buffer) == ResultStatus::Success && IsValidSMDH(smdh_buffer)) {
|
||||
SMDH* smdh = reinterpret_cast<SMDH*>(smdh_buffer.data());
|
||||
return smdh->flags.n3ds_exclusive != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) {
|
||||
|
||||
if (!is_loaded)
|
||||
|
||||
@ -56,6 +56,8 @@ public:
|
||||
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities()
|
||||
override;
|
||||
|
||||
bool IsN3DSExclusive() override;
|
||||
|
||||
ResultStatus IsExecutable(bool& out_executable) override;
|
||||
|
||||
ResultStatus ReadCode(std::vector<u8>& buffer) override;
|
||||
|
||||
@ -163,6 +163,10 @@ public:
|
||||
ResultStatus::Success);
|
||||
}
|
||||
|
||||
virtual bool IsN3DSExclusive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this application is executable.
|
||||
* @param out_executable Reference to store the executable flag into.
|
||||
|
||||
@ -125,6 +125,23 @@ AppLoader_NCCH::LoadNew3dsHwCapabilities() {
|
||||
return std::make_pair(std::move(caps), ResultStatus::Success);
|
||||
}
|
||||
|
||||
bool AppLoader_NCCH::IsN3DSExclusive() {
|
||||
if (!is_loaded) {
|
||||
ResultStatus res = base_ncch.Load();
|
||||
if (res != ResultStatus::Success) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> smdh_buffer;
|
||||
if (ReadIcon(smdh_buffer) == ResultStatus::Success && IsValidSMDH(smdh_buffer)) {
|
||||
SMDH* smdh = reinterpret_cast<SMDH*>(smdh_buffer.data());
|
||||
return smdh->flags.n3ds_exclusive != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process) {
|
||||
using Kernel::CodeSet;
|
||||
|
||||
|
||||
@ -50,6 +50,8 @@ public:
|
||||
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities()
|
||||
override;
|
||||
|
||||
bool IsN3DSExclusive() override;
|
||||
|
||||
ResultStatus IsExecutable(bool& out_executable) override;
|
||||
|
||||
ResultStatus ReadCode(std::vector<u8>& buffer) override;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
@ -37,7 +38,23 @@ struct SMDH {
|
||||
u32_le region_lockout;
|
||||
u32_le match_maker_id;
|
||||
u64_le match_maker_bit_id;
|
||||
u32_le flags;
|
||||
union {
|
||||
u32_le raw;
|
||||
|
||||
BitField<0, 1, u32> visible;
|
||||
BitField<1, 1, u32> autoboot;
|
||||
BitField<2, 1, u32> allow_3D;
|
||||
BitField<3, 1, u32> require_eula;
|
||||
BitField<4, 1, u32> autosave;
|
||||
BitField<5, 1, u32> extended_banner;
|
||||
BitField<6, 1, u32> rating_required;
|
||||
BitField<7, 1, u32> uses_savedata;
|
||||
BitField<8, 1, u32> record_usage;
|
||||
BitField<10, 1, u32> disable_save_backup;
|
||||
BitField<12, 1, u32> n3ds_exclusive;
|
||||
BitField<14, 1, u32> parental_restricted;
|
||||
} flags;
|
||||
|
||||
u16_le eula_version;
|
||||
INSERT_PADDING_BYTES(2);
|
||||
float_le banner_animation_frame;
|
||||
@ -73,10 +90,6 @@ struct SMDH {
|
||||
Taiwan = 6,
|
||||
};
|
||||
|
||||
enum Flags {
|
||||
Visible = 1 << 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if SMDH is valid.
|
||||
*/
|
||||
|
||||
@ -326,7 +326,7 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
|
||||
ReloadShader();
|
||||
ReloadShader(Settings::values.render_3d.GetValue());
|
||||
|
||||
// Generate VBO handle for drawing
|
||||
vertex_buffer.Create();
|
||||
@ -372,10 +372,10 @@ void RendererOpenGL::InitOpenGLObjects() {
|
||||
state.Apply();
|
||||
}
|
||||
|
||||
void RendererOpenGL::ReloadShader() {
|
||||
void RendererOpenGL::ReloadShader(Settings::StereoRenderOption render_3d) {
|
||||
// Link shaders and get variable locations
|
||||
std::string shader_data = fragment_shader_precision_OES;
|
||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph) {
|
||||
if (render_3d == Settings::StereoRenderOption::Anaglyph) {
|
||||
if (Settings::values.anaglyph_shader_name.GetValue() == "Dubois (builtin)") {
|
||||
shader_data += HostShaders::OPENGL_PRESENT_ANAGLYPH_FRAG;
|
||||
} else {
|
||||
@ -388,9 +388,8 @@ void RendererOpenGL::ReloadShader() {
|
||||
shader_data += shader_text;
|
||||
}
|
||||
}
|
||||
} else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
|
||||
Settings::values.render_3d.GetValue() ==
|
||||
Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
} else if (render_3d == Settings::StereoRenderOption::Interlaced ||
|
||||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
shader_data += HostShaders::OPENGL_PRESENT_INTERLACED_FRAG;
|
||||
} else {
|
||||
if (Settings::values.pp_shader_name.GetValue() == "None (builtin)") {
|
||||
@ -411,17 +410,16 @@ void RendererOpenGL::ReloadShader() {
|
||||
state.Apply();
|
||||
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
|
||||
uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
|
||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
if (render_3d == Settings::StereoRenderOption::Anaglyph ||
|
||||
render_3d == Settings::StereoRenderOption::Interlaced ||
|
||||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r");
|
||||
}
|
||||
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
if (render_3d == Settings::StereoRenderOption::Interlaced ||
|
||||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
|
||||
GLuint uniform_reverse_interlaced =
|
||||
glGetUniformLocation(shader.handle, "reverse_interlaced");
|
||||
if (Settings::values.render_3d.GetValue() ==
|
||||
Settings::StereoRenderOption::ReverseInterlaced)
|
||||
if (render_3d == Settings::StereoRenderOption::ReverseInterlaced)
|
||||
glUniform1i(uniform_reverse_interlaced, 1);
|
||||
else
|
||||
glUniform1i(uniform_reverse_interlaced, 0);
|
||||
@ -658,7 +656,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
||||
// Update fragment shader before drawing
|
||||
shader.Release();
|
||||
// Link shaders and get variable locations
|
||||
ReloadShader();
|
||||
ReloadShader(layout.render_3d_mode);
|
||||
}
|
||||
|
||||
const auto& top_screen = layout.top_screen;
|
||||
@ -676,9 +674,9 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
||||
glUniform1i(uniform_color_texture, 0);
|
||||
|
||||
const bool stereo_single_screen =
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
|
||||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced;
|
||||
layout.render_3d_mode == Settings::StereoRenderOption::Anaglyph ||
|
||||
layout.render_3d_mode == Settings::StereoRenderOption::Interlaced ||
|
||||
layout.render_3d_mode == Settings::StereoRenderOption::ReverseInterlaced;
|
||||
|
||||
// Bind a second texture for the right eye if in Anaglyph mode
|
||||
if (stereo_single_screen) {
|
||||
@ -730,6 +728,9 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
if (!layout.top_screen_enabled) {
|
||||
return;
|
||||
}
|
||||
int leftside, rightside;
|
||||
leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0;
|
||||
rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1;
|
||||
|
||||
const float top_screen_left = static_cast<float>(top_screen.left);
|
||||
const float top_screen_top = static_cast<float>(top_screen.top);
|
||||
@ -738,7 +739,7 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
|
||||
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
|
||||
: Layout::DisplayOrientation::Portrait;
|
||||
switch (Settings::values.render_3d.GetValue()) {
|
||||
switch (layout.render_3d_mode) {
|
||||
case Settings::StereoRenderOption::Off: {
|
||||
const int eye = static_cast<int>(Settings::values.mono_render_option.GetValue());
|
||||
DrawSingleScreen(screen_infos[eye], top_screen_left, top_screen_top, top_screen_width,
|
||||
@ -746,29 +747,29 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySide: {
|
||||
DrawSingleScreen(screen_infos[0], top_screen_left / 2, top_screen_top, top_screen_width / 2,
|
||||
top_screen_height, orientation);
|
||||
DrawSingleScreen(screen_infos[leftside], top_screen_left / 2, top_screen_top,
|
||||
top_screen_width / 2, top_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(screen_infos[1],
|
||||
DrawSingleScreen(screen_infos[rightside],
|
||||
static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::ReverseSideBySide: {
|
||||
DrawSingleScreen(screen_infos[1], top_screen_left / 2, top_screen_top, top_screen_width / 2,
|
||||
case Settings::StereoRenderOption::SideBySideFull: {
|
||||
DrawSingleScreen(screen_infos[leftside], top_screen_left, top_screen_top, top_screen_width,
|
||||
top_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(screen_infos[0],
|
||||
static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
|
||||
DrawSingleScreen(screen_infos[rightside],
|
||||
static_cast<float>(top_screen_left + layout.width / 2), top_screen_top,
|
||||
top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::CardboardVR: {
|
||||
DrawSingleScreen(screen_infos[0], top_screen_left, top_screen_top, top_screen_width,
|
||||
DrawSingleScreen(screen_infos[leftside], top_screen_left, top_screen_top, top_screen_width,
|
||||
top_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(
|
||||
screen_infos[1],
|
||||
screen_infos[rightside],
|
||||
static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
@ -776,8 +777,8 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
case Settings::StereoRenderOption::Anaglyph:
|
||||
case Settings::StereoRenderOption::Interlaced:
|
||||
case Settings::StereoRenderOption::ReverseInterlaced: {
|
||||
DrawSingleScreenStereo(screen_infos[0], screen_infos[1], top_screen_left, top_screen_top,
|
||||
top_screen_width, top_screen_height, orientation);
|
||||
DrawSingleScreenStereo(screen_infos[leftside], screen_infos[rightside], top_screen_left,
|
||||
top_screen_top, top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -797,31 +798,30 @@ void RendererOpenGL::DrawBottomScreen(const Layout::FramebufferLayout& layout,
|
||||
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
|
||||
: Layout::DisplayOrientation::Portrait;
|
||||
|
||||
bool separate_win = false;
|
||||
#ifndef ANDROID
|
||||
separate_win =
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
|
||||
#endif
|
||||
|
||||
switch (Settings::values.render_3d.GetValue()) {
|
||||
switch (layout.render_3d_mode) {
|
||||
case Settings::StereoRenderOption::Off: {
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
|
||||
bottom_screen_width, bottom_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySide: // Bottom screen is identical on both sides
|
||||
case Settings::StereoRenderOption::ReverseSideBySide: {
|
||||
if (separate_win) {
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
|
||||
bottom_screen_width, bottom_screen_height, orientation);
|
||||
} else {
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left / 2, bottom_screen_top,
|
||||
bottom_screen_width / 2, bottom_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(
|
||||
screen_infos[2], static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
|
||||
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height, orientation);
|
||||
}
|
||||
{
|
||||
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left / 2, bottom_screen_top,
|
||||
bottom_screen_width / 2, bottom_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(
|
||||
screen_infos[2], static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
|
||||
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height, orientation);
|
||||
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySideFull: {
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
|
||||
bottom_screen_width, bottom_screen_height, orientation);
|
||||
glUniform1i(uniform_layer, 1);
|
||||
DrawSingleScreen(screen_infos[2], bottom_screen_left + layout.width / 2, bottom_screen_top,
|
||||
bottom_screen_width, bottom_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::CardboardVR: {
|
||||
|
||||
@ -56,7 +56,7 @@ public:
|
||||
|
||||
private:
|
||||
void InitOpenGLObjects();
|
||||
void ReloadShader();
|
||||
void ReloadShader(Settings::StereoRenderOption render_3d);
|
||||
void PrepareRendertarget();
|
||||
void RenderScreenshot();
|
||||
void RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||
|
||||
@ -575,8 +575,7 @@ void RendererVulkan::FillScreen(Common::Vec3<u8> color, const TextureInfo& textu
|
||||
});
|
||||
}
|
||||
|
||||
void RendererVulkan::ReloadPipeline() {
|
||||
const Settings::StereoRenderOption render_3d = Settings::values.render_3d.GetValue();
|
||||
void RendererVulkan::ReloadPipeline(Settings::StereoRenderOption render_3d) {
|
||||
switch (render_3d) {
|
||||
case Settings::StereoRenderOption::Anaglyph:
|
||||
current_pipeline = 1;
|
||||
@ -748,7 +747,9 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
if (!layout.top_screen_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
int leftside, rightside;
|
||||
leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0;
|
||||
rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1;
|
||||
const float top_screen_left = static_cast<float>(top_screen.left);
|
||||
const float top_screen_top = static_cast<float>(top_screen.top);
|
||||
const float top_screen_width = static_cast<float>(top_screen.GetWidth());
|
||||
@ -756,7 +757,7 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
|
||||
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
|
||||
: Layout::DisplayOrientation::Portrait;
|
||||
switch (Settings::values.render_3d.GetValue()) {
|
||||
switch (layout.render_3d_mode) {
|
||||
case Settings::StereoRenderOption::Off: {
|
||||
const int eye = static_cast<int>(Settings::values.mono_render_option.GetValue());
|
||||
DrawSingleScreen(eye, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
|
||||
@ -764,35 +765,36 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySide: {
|
||||
DrawSingleScreen(0, top_screen_left / 2, top_screen_top, top_screen_width / 2,
|
||||
DrawSingleScreen(leftside, top_screen_left / 2, top_screen_top, top_screen_width / 2,
|
||||
top_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(1, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
|
||||
DrawSingleScreen(rightside, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::ReverseSideBySide: {
|
||||
DrawSingleScreen(1, top_screen_left / 2, top_screen_top, top_screen_width / 2,
|
||||
case Settings::StereoRenderOption::SideBySideFull: {
|
||||
DrawSingleScreen(leftside, top_screen_left, top_screen_top, top_screen_width,
|
||||
top_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(0, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
|
||||
DrawSingleScreen(rightside, top_screen_left + layout.width / 2, top_screen_top,
|
||||
top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::CardboardVR: {
|
||||
DrawSingleScreen(0, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
|
||||
orientation);
|
||||
DrawSingleScreen(leftside, top_screen_left, top_screen_top, top_screen_width,
|
||||
top_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(
|
||||
1, static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
|
||||
rightside,
|
||||
static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
|
||||
top_screen_top, top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::Anaglyph:
|
||||
case Settings::StereoRenderOption::Interlaced:
|
||||
case Settings::StereoRenderOption::ReverseInterlaced: {
|
||||
DrawSingleScreenStereo(0, 1, top_screen_left, top_screen_top, top_screen_width,
|
||||
top_screen_height, orientation);
|
||||
DrawSingleScreenStereo(leftside, rightside, top_screen_left, top_screen_top,
|
||||
top_screen_width, top_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -812,31 +814,29 @@ void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout,
|
||||
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
|
||||
: Layout::DisplayOrientation::Portrait;
|
||||
|
||||
bool separate_win = false;
|
||||
#ifndef ANDROID
|
||||
separate_win =
|
||||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
|
||||
#endif
|
||||
|
||||
switch (Settings::values.render_3d.GetValue()) {
|
||||
switch (layout.render_3d_mode) {
|
||||
case Settings::StereoRenderOption::Off: {
|
||||
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySide: // Bottom screen is identical on both sides
|
||||
case Settings::StereoRenderOption::ReverseSideBySide: {
|
||||
if (separate_win) {
|
||||
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
} else {
|
||||
DrawSingleScreen(2, bottom_screen_left / 2, bottom_screen_top, bottom_screen_width / 2,
|
||||
bottom_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(2, static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
|
||||
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height,
|
||||
orientation);
|
||||
}
|
||||
{
|
||||
DrawSingleScreen(2, bottom_screen_left / 2, bottom_screen_top, bottom_screen_width / 2,
|
||||
bottom_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(2, static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
|
||||
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height,
|
||||
orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::SideBySideFull: {
|
||||
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
draw_info.layer = 1;
|
||||
DrawSingleScreen(2, bottom_screen_left + layout.width / 2, bottom_screen_top,
|
||||
bottom_screen_width, bottom_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
case Settings::StereoRenderOption::CardboardVR: {
|
||||
@ -851,13 +851,8 @@ void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout,
|
||||
case Settings::StereoRenderOption::Anaglyph:
|
||||
case Settings::StereoRenderOption::Interlaced:
|
||||
case Settings::StereoRenderOption::ReverseInterlaced: {
|
||||
if (separate_win) {
|
||||
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
} else {
|
||||
DrawSingleScreenStereo(2, 2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
}
|
||||
DrawSingleScreenStereo(2, 2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
|
||||
bottom_screen_height, orientation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -871,7 +866,7 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
|
||||
clear_color.float32[2] = Settings::values.bg_blue.GetValue();
|
||||
}
|
||||
if (settings.shader_update_requested.exchange(false)) {
|
||||
ReloadPipeline();
|
||||
ReloadPipeline(layout.render_3d_mode);
|
||||
}
|
||||
|
||||
PrepareDraw(frame, layout);
|
||||
|
||||
@ -80,7 +80,7 @@ public:
|
||||
void TryPresent(int timeout_ms, bool is_secondary) override {}
|
||||
|
||||
private:
|
||||
void ReloadPipeline();
|
||||
void ReloadPipeline(Settings::StereoRenderOption render_3d);
|
||||
void CompileShaders();
|
||||
void BuildLayouts();
|
||||
void BuildPipelines();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -229,7 +229,7 @@ public:
|
||||
}
|
||||
|
||||
/// Returns the maximum supported elements in a texel buffer
|
||||
u32 MaxTexelBufferElements() const {
|
||||
u64 MaxTexelBufferElements() const {
|
||||
return properties.limits.maxTexelBufferElements;
|
||||
}
|
||||
|
||||
|
||||
@ -1369,17 +1369,19 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
|
||||
vk::ImageView Surface::FramebufferView() noexcept {
|
||||
is_framebuffer = true;
|
||||
|
||||
const u32 index = res_scale == 1 ? 0u : 1u;
|
||||
|
||||
// If we already have a framebuffer-compatible view, return it
|
||||
if (framebuffer_view) {
|
||||
return framebuffer_view.get();
|
||||
if (framebuffer_view[index]) {
|
||||
return framebuffer_view[index].get();
|
||||
}
|
||||
|
||||
// Create a new view with a single mip level for framebuffer compatibility
|
||||
// This is critical to avoid VUID-VkFramebufferCreateInfo-pAttachments-00883 validation errors
|
||||
framebuffer_view = MakeFramebufferImageView(
|
||||
framebuffer_view[index] = MakeFramebufferImageView(
|
||||
instance->GetDevice(), Image(), instance->GetTraits(pixel_format).native, Aspect(), 0);
|
||||
|
||||
return framebuffer_view.get();
|
||||
return framebuffer_view[index].get();
|
||||
}
|
||||
|
||||
vk::ImageView Surface::DepthView() noexcept {
|
||||
@ -1482,9 +1484,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
||||
if (is_depth_stencil && !depth_traits.blit_support) {
|
||||
LOG_WARNING(Render_Vulkan, "Depth scale unsupported by hardware");
|
||||
return;
|
||||
} // Check if texture filtering is enabled
|
||||
const bool texture_filter_enabled =
|
||||
Settings::values.texture_filter.GetValue() != Settings::TextureFilter::NoFilter;
|
||||
}
|
||||
|
||||
// Always use consistent source and destination images for proper scaling
|
||||
// When upscaling: source = unscaled (0), destination = scaled (1)
|
||||
@ -1493,8 +1493,8 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
||||
const vk::Image dst_image = up_scale ? Image(1) : Image(0);
|
||||
|
||||
scheduler->Record([src_image, aspect = Aspect(), filter = MakeFilter(pixel_format), dst_image,
|
||||
src_access = AccessFlags(), dst_access = AccessFlags(), blit,
|
||||
texture_filter_enabled](vk::CommandBuffer render_cmdbuf) {
|
||||
src_access = AccessFlags(), dst_access = AccessFlags(),
|
||||
blit](vk::CommandBuffer render_cmdbuf) {
|
||||
// Adjust blitting parameters for filtered upscaling
|
||||
const std::array source_offsets = {
|
||||
vk::Offset3D{static_cast<s32>(blit.src_rect.left),
|
||||
@ -1508,15 +1508,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
||||
static_cast<s32>(blit.dst_rect.bottom), 0},
|
||||
vk::Offset3D{static_cast<s32>(blit.dst_rect.right), static_cast<s32>(blit.dst_rect.top),
|
||||
1},
|
||||
}; // Ensure we're using the right filter for texture filtered upscaling
|
||||
vk::Filter actual_filter;
|
||||
if (texture_filter_enabled) {
|
||||
// When texture filtering is enabled, always use LINEAR filtering
|
||||
actual_filter = vk::Filter::eLinear;
|
||||
} else {
|
||||
// When texture filtering is disabled, use the filter appropriate for the texture format
|
||||
actual_filter = filter;
|
||||
}
|
||||
};
|
||||
|
||||
const vk::ImageBlit blit_area = {
|
||||
.srcSubresource{
|
||||
@ -1585,7 +1577,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers);
|
||||
|
||||
render_cmdbuf.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image,
|
||||
vk::ImageLayout::eTransferDstOptimal, blit_area, actual_filter);
|
||||
vk::ImageLayout::eTransferDstOptimal, blit_area, filter);
|
||||
|
||||
render_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eAllCommands,
|
||||
|
||||
@ -201,7 +201,7 @@ public:
|
||||
std::array<Handle, 3> handles{};
|
||||
std::array<vk::UniqueFramebuffer, 2> framebuffers{};
|
||||
Handle copy_handle;
|
||||
vk::UniqueImageView framebuffer_view;
|
||||
std::array<vk::UniqueImageView, 2> framebuffer_view;
|
||||
vk::UniqueImageView depth_view;
|
||||
vk::UniqueImageView stencil_view;
|
||||
vk::UniqueImageView storage_view;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user