mirror of
https://github.com/PabloMK7/citra
synced 2024-11-15 05:08:23 +00:00
Add Artic Base support (#105)
* Add Artic Base support * Add Android support
This commit is contained in:
parent
572d3ab71c
commit
24c6ec5e6a
83 changed files with 5592 additions and 516 deletions
|
@ -183,13 +183,13 @@ object NativeLibrary {
|
||||||
private var coreErrorAlertResult = false
|
private var coreErrorAlertResult = false
|
||||||
private val coreErrorAlertLock = Object()
|
private val coreErrorAlertLock = Object()
|
||||||
|
|
||||||
private fun onCoreErrorImpl(title: String, message: String) {
|
private fun onCoreErrorImpl(title: String, message: String, canContinue: Boolean) {
|
||||||
val emulationActivity = sEmulationActivity.get()
|
val emulationActivity = sEmulationActivity.get()
|
||||||
if (emulationActivity == null) {
|
if (emulationActivity == null) {
|
||||||
Log.error("[NativeLibrary] EmulationActivity not present")
|
Log.error("[NativeLibrary] EmulationActivity not present")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val fragment = CoreErrorDialogFragment.newInstance(title, message)
|
val fragment = CoreErrorDialogFragment.newInstance(title, message, canContinue)
|
||||||
fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG)
|
fragment.show(emulationActivity.supportFragmentManager, CoreErrorDialogFragment.TAG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +207,7 @@ object NativeLibrary {
|
||||||
}
|
}
|
||||||
val title: String
|
val title: String
|
||||||
val message: String
|
val message: String
|
||||||
|
val canContinue: Boolean
|
||||||
when (error) {
|
when (error) {
|
||||||
CoreError.ErrorSystemFiles -> {
|
CoreError.ErrorSystemFiles -> {
|
||||||
title = emulationActivity.getString(R.string.system_archive_not_found)
|
title = emulationActivity.getString(R.string.system_archive_not_found)
|
||||||
|
@ -214,16 +215,25 @@ object NativeLibrary {
|
||||||
R.string.system_archive_not_found_message,
|
R.string.system_archive_not_found_message,
|
||||||
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
|
details.ifEmpty { emulationActivity.getString(R.string.system_archive_general) }
|
||||||
)
|
)
|
||||||
|
canContinue = true
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreError.ErrorSavestate -> {
|
CoreError.ErrorSavestate -> {
|
||||||
title = emulationActivity.getString(R.string.save_load_error)
|
title = emulationActivity.getString(R.string.save_load_error)
|
||||||
message = details
|
message = details
|
||||||
|
canContinue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreError.ErrorArticDisconnected -> {
|
||||||
|
title = emulationActivity.getString(R.string.artic_base)
|
||||||
|
message = emulationActivity.getString(R.string.artic_server_comm_error)
|
||||||
|
canContinue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreError.ErrorUnknown -> {
|
CoreError.ErrorUnknown -> {
|
||||||
title = emulationActivity.getString(R.string.fatal_error)
|
title = emulationActivity.getString(R.string.fatal_error)
|
||||||
message = emulationActivity.getString(R.string.fatal_error_message)
|
message = emulationActivity.getString(R.string.fatal_error_message)
|
||||||
|
canContinue = true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -232,7 +242,7 @@ object NativeLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the AlertDialog on the main thread.
|
// Show the AlertDialog on the main thread.
|
||||||
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message) })
|
emulationActivity.runOnUiThread(Runnable { onCoreErrorImpl(title, message, canContinue) })
|
||||||
|
|
||||||
// Wait for the lock to notify that it is complete.
|
// Wait for the lock to notify that it is complete.
|
||||||
synchronized(coreErrorAlertLock) {
|
synchronized(coreErrorAlertLock) {
|
||||||
|
@ -346,6 +356,11 @@ object NativeLibrary {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resultCode == EmulationErrorDialogFragment.ShutdownRequested) {
|
||||||
|
emulationActivity.finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
emulationActivity.runOnUiThread {
|
emulationActivity.runOnUiThread {
|
||||||
EmulationErrorDialogFragment.newInstance(resultCode).showNow(
|
EmulationErrorDialogFragment.newInstance(resultCode).showNow(
|
||||||
emulationActivity.supportFragmentManager,
|
emulationActivity.supportFragmentManager,
|
||||||
|
@ -361,16 +376,23 @@ object NativeLibrary {
|
||||||
emulationActivity = requireActivity() as EmulationActivity
|
emulationActivity = requireActivity() as EmulationActivity
|
||||||
|
|
||||||
var captionId = R.string.loader_error_invalid_format
|
var captionId = R.string.loader_error_invalid_format
|
||||||
if (requireArguments().getInt(RESULT_CODE) == ErrorLoader_ErrorEncrypted) {
|
val result = requireArguments().getInt(RESULT_CODE)
|
||||||
|
if (result == ErrorLoader_ErrorEncrypted) {
|
||||||
captionId = R.string.loader_error_encrypted
|
captionId = R.string.loader_error_encrypted
|
||||||
}
|
}
|
||||||
|
if (result == ErrorArticDisconnected) {
|
||||||
|
captionId = R.string.artic_base
|
||||||
|
}
|
||||||
|
|
||||||
val alert = MaterialAlertDialogBuilder(requireContext())
|
val alert = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(captionId)
|
.setTitle(captionId)
|
||||||
.setMessage(
|
.setMessage(
|
||||||
Html.fromHtml(
|
Html.fromHtml(
|
||||||
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
if (result == ErrorArticDisconnected)
|
||||||
Html.FROM_HTML_MODE_LEGACY
|
CitraApplication.appContext.resources.getString(R.string.artic_server_comm_error)
|
||||||
|
else
|
||||||
|
CitraApplication.appContext.resources.getString(R.string.redump_games),
|
||||||
|
Html.FROM_HTML_MODE_LEGACY
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
@ -398,7 +420,10 @@ object NativeLibrary {
|
||||||
const val ErrorLoader = 4
|
const val ErrorLoader = 4
|
||||||
const val ErrorLoader_ErrorEncrypted = 5
|
const val ErrorLoader_ErrorEncrypted = 5
|
||||||
const val ErrorLoader_ErrorInvalidFormat = 6
|
const val ErrorLoader_ErrorInvalidFormat = 6
|
||||||
const val ErrorSystemFiles = 7
|
const val ErrorLoader_ErrorGBATitle = 7
|
||||||
|
const val ErrorSystemFiles = 8
|
||||||
|
const val ErrorSavestate = 9
|
||||||
|
const val ErrorArticDisconnected = 10
|
||||||
const val ShutdownRequested = 11
|
const val ShutdownRequested = 11
|
||||||
const val ErrorUnknown = 12
|
const val ErrorUnknown = 12
|
||||||
|
|
||||||
|
@ -619,6 +644,7 @@ object NativeLibrary {
|
||||||
enum class CoreError {
|
enum class CoreError {
|
||||||
ErrorSystemFiles,
|
ErrorSystemFiles,
|
||||||
ErrorSavestate,
|
ErrorSavestate,
|
||||||
|
ErrorArticDisconnected,
|
||||||
ErrorUnknown
|
ErrorUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,23 +659,33 @@ object NativeLibrary {
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoreErrorDialogFragment : DialogFragment() {
|
class CoreErrorDialogFragment : DialogFragment() {
|
||||||
|
private var userChosen = false
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
val title = requireArguments().getString(TITLE)
|
val title = requireArguments().getString(TITLE)
|
||||||
val message = requireArguments().getString(MESSAGE)
|
val message = requireArguments().getString(MESSAGE)
|
||||||
return MaterialAlertDialogBuilder(requireContext())
|
val canContinue = requireArguments().getBoolean(CAN_CONTINUE)
|
||||||
|
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int ->
|
if (canContinue) {
|
||||||
|
dialog.setPositiveButton(R.string.continue_button) { _: DialogInterface?, _: Int ->
|
||||||
coreErrorAlertResult = true
|
coreErrorAlertResult = true
|
||||||
|
userChosen = true
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
}
|
||||||
coreErrorAlertResult = false
|
dialog.setNegativeButton(R.string.abort_button) { _: DialogInterface?, _: Int ->
|
||||||
}.show()
|
coreErrorAlertResult = false
|
||||||
|
userChosen = true
|
||||||
|
}
|
||||||
|
return dialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDismiss(dialog: DialogInterface) {
|
override fun onDismiss(dialog: DialogInterface) {
|
||||||
super.onDismiss(dialog)
|
super.onDismiss(dialog)
|
||||||
coreErrorAlertResult = true
|
val canContinue = requireArguments().getBoolean(CAN_CONTINUE)
|
||||||
|
if (!userChosen) {
|
||||||
|
coreErrorAlertResult = canContinue
|
||||||
|
}
|
||||||
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
synchronized(coreErrorAlertLock) { coreErrorAlertLock.notify() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -658,12 +694,14 @@ object NativeLibrary {
|
||||||
|
|
||||||
const val TITLE = "title"
|
const val TITLE = "title"
|
||||||
const val MESSAGE = "message"
|
const val MESSAGE = "message"
|
||||||
|
const val CAN_CONTINUE = "canContinue"
|
||||||
|
|
||||||
fun newInstance(title: String, message: String): CoreErrorDialogFragment {
|
fun newInstance(title: String, message: String, canContinue: Boolean): CoreErrorDialogFragment {
|
||||||
val frag = CoreErrorDialogFragment()
|
val frag = CoreErrorDialogFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putString(TITLE, title)
|
args.putString(TITLE, title)
|
||||||
args.putString(MESSAGE, message)
|
args.putString(MESSAGE, message)
|
||||||
|
args.putBoolean(CAN_CONTINUE, canContinue)
|
||||||
frag.arguments = args
|
frag.arguments = args
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
@ -23,14 +24,19 @@ import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import org.citra.citra_emu.CitraApplication
|
import org.citra.citra_emu.CitraApplication
|
||||||
|
import org.citra.citra_emu.HomeNavigationDirections
|
||||||
import org.citra.citra_emu.R
|
import org.citra.citra_emu.R
|
||||||
import org.citra.citra_emu.adapters.HomeSettingAdapter
|
import org.citra.citra_emu.adapters.HomeSettingAdapter
|
||||||
|
import org.citra.citra_emu.databinding.DialogSoftwareKeyboardBinding
|
||||||
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
import org.citra.citra_emu.databinding.FragmentHomeSettingsBinding
|
||||||
import org.citra.citra_emu.features.settings.model.Settings
|
import org.citra.citra_emu.features.settings.model.Settings
|
||||||
|
import org.citra.citra_emu.features.settings.model.StringSetting
|
||||||
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
import org.citra.citra_emu.features.settings.ui.SettingsActivity
|
||||||
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
import org.citra.citra_emu.features.settings.utils.SettingsFile
|
||||||
|
import org.citra.citra_emu.model.Game
|
||||||
import org.citra.citra_emu.model.HomeSetting
|
import org.citra.citra_emu.model.HomeSetting
|
||||||
import org.citra.citra_emu.ui.main.MainActivity
|
import org.citra.citra_emu.ui.main.MainActivity
|
||||||
import org.citra.citra_emu.utils.GameHelper
|
import org.citra.citra_emu.utils.GameHelper
|
||||||
|
@ -76,6 +82,41 @@ class HomeSettingsFragment : Fragment() {
|
||||||
R.drawable.ic_settings,
|
R.drawable.ic_settings,
|
||||||
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
{ SettingsActivity.launch(requireContext(), SettingsFile.FILE_NAME_CONFIG, "") }
|
||||||
),
|
),
|
||||||
|
HomeSetting(
|
||||||
|
R.string.artic_base_connect,
|
||||||
|
R.string.artic_base_connect_description,
|
||||||
|
R.drawable.ic_network,
|
||||||
|
{
|
||||||
|
val inflater = LayoutInflater.from(context)
|
||||||
|
val inputBinding = DialogSoftwareKeyboardBinding.inflate(inflater)
|
||||||
|
var textInputValue: String = ""
|
||||||
|
|
||||||
|
inputBinding.editTextInput.setText(textInputValue)
|
||||||
|
inputBinding.editTextInput.doOnTextChanged { text, _, _, _ ->
|
||||||
|
textInputValue = text.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = context?.let {
|
||||||
|
MaterialAlertDialogBuilder(it)
|
||||||
|
.setView(inputBinding.root)
|
||||||
|
.setTitle(getString(R.string.artic_base_enter_address))
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
if (textInputValue.isNotEmpty()) {
|
||||||
|
val menu = Game(
|
||||||
|
title = getString(R.string.artic_base),
|
||||||
|
path = "articbase://$textInputValue",
|
||||||
|
filename = ""
|
||||||
|
)
|
||||||
|
val action =
|
||||||
|
HomeNavigationDirections.actionGlobalEmulationActivity(menu)
|
||||||
|
binding.root.findNavController().navigate(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) {_, _ -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
HomeSetting(
|
HomeSetting(
|
||||||
R.string.system_files,
|
R.string.system_files,
|
||||||
R.string.system_files_description,
|
R.string.system_files_description,
|
||||||
|
|
|
@ -82,6 +82,7 @@ static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
||||||
static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{
|
static const std::map<Core::System::ResultStatus, const char*> CoreErrorNameMap{
|
||||||
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
|
{Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"},
|
||||||
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
|
{Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"},
|
||||||
|
{Core::System::ResultStatus::ErrorArticDisconnected, "ErrorArticDisconnected"},
|
||||||
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
|
{Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -178,6 +179,7 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||||
auto app_loader = Loader::GetLoader(filepath);
|
auto app_loader = Loader::GetLoader(filepath);
|
||||||
if (app_loader) {
|
if (app_loader) {
|
||||||
app_loader->ReadProgramId(program_id);
|
app_loader->ReadProgramId(program_id);
|
||||||
|
system.RegisterAppLoaderEarly(app_loader);
|
||||||
GameSettings::LoadOverrides(program_id);
|
GameSettings::LoadOverrides(program_id);
|
||||||
}
|
}
|
||||||
system.ApplySettings();
|
system.ApplySettings();
|
||||||
|
@ -231,6 +233,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||||
InputManager::NDKMotionHandler()->DisableSensors();
|
InputManager::NDKMotionHandler()->DisableSensors();
|
||||||
if (!HandleCoreError(result, system.GetStatusDetails())) {
|
if (!HandleCoreError(result, system.GetStatusDetails())) {
|
||||||
// Frontend requests us to abort
|
// Frontend requests us to abort
|
||||||
|
// If the error was an Artic disconnect, return shutdown request.
|
||||||
|
if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
|
||||||
|
return Core::System::ResultStatus::ShutdownRequested;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
InputManager::NDKMotionHandler()->EnableSensors();
|
InputManager::NDKMotionHandler()->EnableSensors();
|
||||||
|
@ -314,7 +320,9 @@ void Java_org_citra_citra_1emu_NativeLibrary_doFrame([[maybe_unused]] JNIEnv* en
|
||||||
if (stop_run || pause_emulation) {
|
if (stop_run || pause_emulation) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window->TryPresenting();
|
if (window) {
|
||||||
|
window->TryPresenting();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
|
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_initializeGpuDriver(
|
||||||
|
|
9
src/android/app/src/main/res/drawable/ic_network.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_network.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorControlNormal"
|
||||||
|
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760q0,-33 23.5,-56.5T200,680q33,0 56.5,23.5T280,760q0,33 -23.5,56.5T200,840ZM680,840q0,-117 -44,-218.5T516,444q-76,-76 -177.5,-120T120,280v-120q142,0 265,53t216,146q93,93 146,216t53,265L680,840ZM440,840q0,-67 -25,-124.5T346,614q-44,-44 -101.5,-69T120,520v-120q92,0 171.5,34.5T431,529q60,60 94.5,139.5T560,840L440,840Z"/>
|
||||||
|
</vector>
|
|
@ -657,4 +657,11 @@ Se esperan fallos gráficos temporales cuando ésta esté activado.</string>
|
||||||
<string name="november">Noviembre</string>
|
<string name="november">Noviembre</string>
|
||||||
<string name="december">Diciembre</string>
|
<string name="december">Diciembre</string>
|
||||||
|
|
||||||
|
<!-- Artic base -->
|
||||||
|
<string name="artic_server_comm_error">Fallo de comunicación con el servidor Artic Base. La emulación se detendrá.</string>
|
||||||
|
<string name="artic_base">Artic Base</string>
|
||||||
|
<string name="artic_base_connect">Conectar con Artic Base</string>
|
||||||
|
<string name="artic_base_connect_description">Conectar con una consola real que esté ejecutando un servidor Artic Base</string>
|
||||||
|
<string name="artic_base_enter_address">Introduce la dirección del servidor Artic Base</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -683,4 +683,11 @@
|
||||||
<string name="november">November</string>
|
<string name="november">November</string>
|
||||||
<string name="december">December</string>
|
<string name="december">December</string>
|
||||||
|
|
||||||
|
<!-- Artic base -->
|
||||||
|
<string name="artic_server_comm_error">Failed to communicate with the Artic Base server. Emulation will stop.</string>
|
||||||
|
<string name="artic_base">Artic Base</string>
|
||||||
|
<string name="artic_base_connect_description">Connect to a real console that is running an Artic Base server</string>
|
||||||
|
<string name="artic_base_connect">Connect to Artic Base</string>
|
||||||
|
<string name="artic_base_enter_address">Enter Artic Base server address</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -636,6 +636,8 @@ void Config::ReadPathValues() {
|
||||||
UISettings::values.game_dirs.append(game_dir);
|
UISettings::values.game_dirs.append(game_dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UISettings::values.last_artic_base_addr =
|
||||||
|
ReadSetting(QStringLiteral("last_artic_base_addr"), QString{}).toString();
|
||||||
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
|
UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
|
||||||
UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
|
UISettings::values.language = ReadSetting(QStringLiteral("language"), QString{}).toString();
|
||||||
}
|
}
|
||||||
|
@ -1135,6 +1137,8 @@ void Config::SavePathValues() {
|
||||||
WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
|
WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
|
||||||
}
|
}
|
||||||
qt_config->endArray();
|
qt_config->endArray();
|
||||||
|
WriteSetting(QStringLiteral("last_artic_base_addr"),
|
||||||
|
UISettings::values.last_artic_base_addr, QString{});
|
||||||
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
|
WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
|
||||||
WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
|
WriteSetting(QStringLiteral("language"), UISettings::values.language, QString{});
|
||||||
}
|
}
|
||||||
|
|
|
@ -381,6 +381,10 @@ void GMainWindow::InitializeWidgets() {
|
||||||
progress_bar->hide();
|
progress_bar->hide();
|
||||||
statusBar()->addPermanentWidget(progress_bar);
|
statusBar()->addPermanentWidget(progress_bar);
|
||||||
|
|
||||||
|
artic_traffic_label = new QLabel();
|
||||||
|
artic_traffic_label->setToolTip(
|
||||||
|
tr("Current Artic Base traffic speed. Higher values indicate bigger transfer loads."));
|
||||||
|
|
||||||
emu_speed_label = new QLabel();
|
emu_speed_label = new QLabel();
|
||||||
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
|
emu_speed_label->setToolTip(tr("Current emulation speed. Values higher or lower than 100% "
|
||||||
"indicate emulation is running faster or slower than a 3DS."));
|
"indicate emulation is running faster or slower than a 3DS."));
|
||||||
|
@ -392,7 +396,8 @@ void GMainWindow::InitializeWidgets() {
|
||||||
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
|
tr("Time taken to emulate a 3DS frame, not counting framelimiting or v-sync. For "
|
||||||
"full-speed emulation this should be at most 16.67 ms."));
|
"full-speed emulation this should be at most 16.67 ms."));
|
||||||
|
|
||||||
for (auto& label : {emu_speed_label, game_fps_label, emu_frametime_label}) {
|
for (auto& label :
|
||||||
|
{artic_traffic_label, emu_speed_label, game_fps_label, emu_frametime_label}) {
|
||||||
label->setVisible(false);
|
label->setVisible(false);
|
||||||
label->setFrameStyle(QFrame::NoFrame);
|
label->setFrameStyle(QFrame::NoFrame);
|
||||||
label->setContentsMargins(4, 0, 4, 0);
|
label->setContentsMargins(4, 0, 4, 0);
|
||||||
|
@ -866,6 +871,7 @@ void GMainWindow::ConnectMenuEvents() {
|
||||||
// File
|
// File
|
||||||
connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile);
|
connect_menu(ui->action_Load_File, &GMainWindow::OnMenuLoadFile);
|
||||||
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
|
connect_menu(ui->action_Install_CIA, &GMainWindow::OnMenuInstallCIA);
|
||||||
|
connect_menu(ui->action_Connect_Artic, &GMainWindow::OnMenuConnectArticBase);
|
||||||
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
|
for (u32 region = 0; region < Core::NUM_SYSTEM_TITLE_REGIONS; region++) {
|
||||||
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
|
connect_menu(ui->menu_Boot_Home_Menu->actions().at(region),
|
||||||
[this, region] { OnMenuBootHomeMenu(region); });
|
[this, region] { OnMenuBootHomeMenu(region); });
|
||||||
|
@ -1203,6 +1209,11 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
tr("GBA Virtual Console ROMs are not supported by Citra."));
|
tr("GBA Virtual Console ROMs are not supported by Citra."));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Core::System::ResultStatus::ErrorArticDisconnected:
|
||||||
|
QMessageBox::critical(
|
||||||
|
this, tr("Artic Base Server"),
|
||||||
|
tr("An error has occurred whilst communicating with the Artic Base Server."));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(
|
||||||
this, tr("Error while loading ROM!"),
|
this, tr("Error while loading ROM!"),
|
||||||
|
@ -1223,7 +1234,9 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::BootGame(const QString& filename) {
|
void GMainWindow::BootGame(const QString& filename) {
|
||||||
if (filename.endsWith(QStringLiteral(".cia"))) {
|
const bool is_artic = filename.startsWith(QString::fromStdString("articbase://"));
|
||||||
|
|
||||||
|
if (!is_artic && filename.endsWith(QStringLiteral(".cia"))) {
|
||||||
const auto answer = QMessageBox::question(
|
const auto answer = QMessageBox::question(
|
||||||
this, tr("CIA must be installed before usage"),
|
this, tr("CIA must be installed before usage"),
|
||||||
tr("Before using this CIA, you must install it. Do you want to install it now?"),
|
tr("Before using this CIA, you must install it. Do you want to install it now?"),
|
||||||
|
@ -1235,8 +1248,12 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
show_artic_label = is_artic;
|
||||||
|
|
||||||
LOG_INFO(Frontend, "Citra starting...");
|
LOG_INFO(Frontend, "Citra starting...");
|
||||||
StoreRecentFile(filename); // Put the filename on top of the list
|
if (!is_artic) {
|
||||||
|
StoreRecentFile(filename); // Put the filename on top of the list
|
||||||
|
}
|
||||||
|
|
||||||
if (movie_record_on_start) {
|
if (movie_record_on_start) {
|
||||||
movie.PrepareForRecording();
|
movie.PrepareForRecording();
|
||||||
|
@ -1246,16 +1263,26 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string path = filename.toStdString();
|
const std::string path = filename.toStdString();
|
||||||
const auto loader = Loader::GetLoader(path);
|
auto loader = Loader::GetLoader(path);
|
||||||
|
|
||||||
u64 title_id{0};
|
u64 title_id{0};
|
||||||
loader->ReadProgramId(title_id);
|
Loader::ResultStatus res = loader->ReadProgramId(title_id);
|
||||||
|
|
||||||
|
if (Loader::ResultStatus::Success == res) {
|
||||||
|
// Load per game settings
|
||||||
|
const std::string name{is_artic ? "" : FileUtil::GetFilename(filename.toStdString())};
|
||||||
|
const std::string config_file_name =
|
||||||
|
title_id == 0 ? name : fmt::format("{:016X}", title_id);
|
||||||
|
LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name);
|
||||||
|
Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artic Base Server cannot accept a client multiple times, so multiple loaders are not
|
||||||
|
// possible. Instead register the app loader early and do not create it again on system load.
|
||||||
|
if (!loader->SupportsMultipleInstancesForSameFile()) {
|
||||||
|
system.RegisterAppLoaderEarly(loader);
|
||||||
|
}
|
||||||
|
|
||||||
// Load per game settings
|
|
||||||
const std::string name{FileUtil::GetFilename(filename.toStdString())};
|
|
||||||
const std::string config_file_name = title_id == 0 ? name : fmt::format("{:016X}", title_id);
|
|
||||||
LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name);
|
|
||||||
Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig);
|
|
||||||
system.ApplySettings();
|
system.ApplySettings();
|
||||||
|
|
||||||
Settings::LogSettings();
|
Settings::LogSettings();
|
||||||
|
@ -1265,8 +1292,11 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
game_list->SaveInterfaceLayout();
|
game_list->SaveInterfaceLayout();
|
||||||
config->Save();
|
config->Save();
|
||||||
|
|
||||||
if (!LoadROM(filename))
|
if (!LoadROM(filename)) {
|
||||||
|
render_window->ReleaseRenderTarget();
|
||||||
|
secondary_window->ReleaseRenderTarget();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set everything up
|
// Set everything up
|
||||||
if (movie_record_on_start) {
|
if (movie_record_on_start) {
|
||||||
|
@ -1420,6 +1450,8 @@ void GMainWindow::ShutdownGame() {
|
||||||
// Disable status bar updates
|
// Disable status bar updates
|
||||||
status_bar_update_timer.stop();
|
status_bar_update_timer.stop();
|
||||||
message_label_used_for_movie = false;
|
message_label_used_for_movie = false;
|
||||||
|
show_artic_label = false;
|
||||||
|
artic_traffic_label->setVisible(false);
|
||||||
emu_speed_label->setVisible(false);
|
emu_speed_label->setVisible(false);
|
||||||
game_fps_label->setVisible(false);
|
game_fps_label->setVisible(false);
|
||||||
emu_frametime_label->setVisible(false);
|
emu_frametime_label->setVisible(false);
|
||||||
|
@ -1759,6 +1791,17 @@ void GMainWindow::OnMenuInstallCIA() {
|
||||||
InstallCIA(filepaths);
|
InstallCIA(filepaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnMenuConnectArticBase() {
|
||||||
|
bool ok = false;
|
||||||
|
auto res = QInputDialog::getText(this, tr("Connect to Artic Base"),
|
||||||
|
tr("Enter Artic Base server address:"), QLineEdit::Normal,
|
||||||
|
UISettings::values.last_artic_base_addr, &ok);
|
||||||
|
if (ok) {
|
||||||
|
UISettings::values.last_artic_base_addr = res;
|
||||||
|
BootGame(QString::fromStdString("articbase://").append(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::OnMenuBootHomeMenu(u32 region) {
|
void GMainWindow::OnMenuBootHomeMenu(u32 region) {
|
||||||
BootGame(QString::fromStdString(Core::GetHomeMenuNcchPath(region)));
|
BootGame(QString::fromStdString(Core::GetHomeMenuNcchPath(region)));
|
||||||
}
|
}
|
||||||
|
@ -2575,6 +2618,51 @@ void GMainWindow::UpdateStatusBar() {
|
||||||
|
|
||||||
auto results = system.GetAndResetPerfStats();
|
auto results = system.GetAndResetPerfStats();
|
||||||
|
|
||||||
|
if (show_artic_label) {
|
||||||
|
const bool do_mb = results.artic_transmitted >= (1000.0 * 1000.0);
|
||||||
|
const double value = do_mb ? (results.artic_transmitted / (1000.0 * 1000.0))
|
||||||
|
: (results.artic_transmitted / 1000.0);
|
||||||
|
static const std::array<std::pair<Core::PerfStats::PerfArticEventBits, QString>, 4>
|
||||||
|
perf_events = {
|
||||||
|
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA,
|
||||||
|
tr("(Accessing SharedExtData)")),
|
||||||
|
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA,
|
||||||
|
tr("(Accessing BossExtData)")),
|
||||||
|
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA,
|
||||||
|
tr("(Accessing ExtData)")),
|
||||||
|
std::make_pair(Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA,
|
||||||
|
tr("(Accessing SaveData)")),
|
||||||
|
};
|
||||||
|
|
||||||
|
const QString unit = do_mb ? tr("MB/s") : tr("KB/s");
|
||||||
|
QString event{};
|
||||||
|
for (auto p : perf_events) {
|
||||||
|
if (results.artic_events.Get(p.first)) {
|
||||||
|
event = QString::fromStdString(" ") + p.second;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::array label_color = {QStringLiteral("#ffffff"), QStringLiteral("#eed202"),
|
||||||
|
QStringLiteral("#ff3333")};
|
||||||
|
|
||||||
|
int style_index;
|
||||||
|
|
||||||
|
if (value > 200.0) {
|
||||||
|
style_index = 2;
|
||||||
|
} else if (value > 125.0) {
|
||||||
|
style_index = 1;
|
||||||
|
} else {
|
||||||
|
style_index = 0;
|
||||||
|
}
|
||||||
|
const QString style_sheet =
|
||||||
|
QStringLiteral("QLabel { color: %0; }").arg(label_color[style_index]);
|
||||||
|
|
||||||
|
artic_traffic_label->setText(
|
||||||
|
tr("Artic Base Traffic: %1 %2%3").arg(value, 0, 'f', 0).arg(unit).arg(event));
|
||||||
|
artic_traffic_label->setStyleSheet(style_sheet);
|
||||||
|
}
|
||||||
|
|
||||||
if (Settings::values.frame_limit.GetValue() == 0) {
|
if (Settings::values.frame_limit.GetValue() == 0) {
|
||||||
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
|
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
|
||||||
} else {
|
} else {
|
||||||
|
@ -2585,6 +2673,9 @@ void GMainWindow::UpdateStatusBar() {
|
||||||
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
|
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
|
||||||
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
|
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
|
||||||
|
|
||||||
|
if (show_artic_label) {
|
||||||
|
artic_traffic_label->setVisible(true);
|
||||||
|
}
|
||||||
emu_speed_label->setVisible(true);
|
emu_speed_label->setVisible(true);
|
||||||
game_fps_label->setVisible(true);
|
game_fps_label->setVisible(true);
|
||||||
emu_frametime_label->setVisible(true);
|
emu_frametime_label->setVisible(true);
|
||||||
|
@ -2736,6 +2827,7 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||||
|
|
||||||
QString title, message;
|
QString title, message;
|
||||||
QMessageBox::Icon error_severity_icon;
|
QMessageBox::Icon error_severity_icon;
|
||||||
|
bool can_continue = true;
|
||||||
if (result == Core::System::ResultStatus::ErrorSystemFiles) {
|
if (result == Core::System::ResultStatus::ErrorSystemFiles) {
|
||||||
const QString common_message =
|
const QString common_message =
|
||||||
tr("%1 is missing. Please <a "
|
tr("%1 is missing. Please <a "
|
||||||
|
@ -2756,6 +2848,11 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||||
title = tr("Save/load Error");
|
title = tr("Save/load Error");
|
||||||
message = QString::fromStdString(details);
|
message = QString::fromStdString(details);
|
||||||
error_severity_icon = QMessageBox::Icon::Warning;
|
error_severity_icon = QMessageBox::Icon::Warning;
|
||||||
|
} else if (result == Core::System::ResultStatus::ErrorArticDisconnected) {
|
||||||
|
title = tr("Artic Base Server");
|
||||||
|
message = tr("A communication error has occurred. The game will quit.");
|
||||||
|
error_severity_icon = QMessageBox::Icon::Critical;
|
||||||
|
can_continue = false;
|
||||||
} else {
|
} else {
|
||||||
title = tr("Fatal Error");
|
title = tr("Fatal Error");
|
||||||
message =
|
message =
|
||||||
|
@ -2772,12 +2869,14 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
|
||||||
message_box.setText(message);
|
message_box.setText(message);
|
||||||
message_box.setIcon(error_severity_icon);
|
message_box.setIcon(error_severity_icon);
|
||||||
if (error_severity_icon == QMessageBox::Icon::Critical) {
|
if (error_severity_icon == QMessageBox::Icon::Critical) {
|
||||||
message_box.addButton(tr("Continue"), QMessageBox::RejectRole);
|
if (can_continue) {
|
||||||
|
message_box.addButton(tr("Continue"), QMessageBox::RejectRole);
|
||||||
|
}
|
||||||
QPushButton* abort_button = message_box.addButton(tr("Quit Game"), QMessageBox::AcceptRole);
|
QPushButton* abort_button = message_box.addButton(tr("Quit Game"), QMessageBox::AcceptRole);
|
||||||
if (result != Core::System::ResultStatus::ShutdownRequested)
|
if (result != Core::System::ResultStatus::ShutdownRequested)
|
||||||
message_box.exec();
|
message_box.exec();
|
||||||
|
|
||||||
if (result == Core::System::ResultStatus::ShutdownRequested ||
|
if (!can_continue || result == Core::System::ResultStatus::ShutdownRequested ||
|
||||||
message_box.clickedButton() == abort_button) {
|
message_box.clickedButton() == abort_button) {
|
||||||
if (emu_thread) {
|
if (emu_thread) {
|
||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
|
|
|
@ -216,6 +216,7 @@ private slots:
|
||||||
void OnConfigurePerGame();
|
void OnConfigurePerGame();
|
||||||
void OnMenuLoadFile();
|
void OnMenuLoadFile();
|
||||||
void OnMenuInstallCIA();
|
void OnMenuInstallCIA();
|
||||||
|
void OnMenuConnectArticBase();
|
||||||
void OnMenuBootHomeMenu(u32 region);
|
void OnMenuBootHomeMenu(u32 region);
|
||||||
void OnUpdateProgress(std::size_t written, std::size_t total);
|
void OnUpdateProgress(std::size_t written, std::size_t total);
|
||||||
void OnCIAInstallReport(Service::AM::InstallStatus status, QString filepath);
|
void OnCIAInstallReport(Service::AM::InstallStatus status, QString filepath);
|
||||||
|
@ -302,6 +303,8 @@ private:
|
||||||
// Status bar elements
|
// Status bar elements
|
||||||
QProgressBar* progress_bar = nullptr;
|
QProgressBar* progress_bar = nullptr;
|
||||||
QLabel* message_label = nullptr;
|
QLabel* message_label = nullptr;
|
||||||
|
bool show_artic_label = false;
|
||||||
|
QLabel* artic_traffic_label = nullptr;
|
||||||
QLabel* emu_speed_label = nullptr;
|
QLabel* emu_speed_label = nullptr;
|
||||||
QLabel* game_fps_label = nullptr;
|
QLabel* game_fps_label = nullptr;
|
||||||
QLabel* emu_frametime_label = nullptr;
|
QLabel* emu_frametime_label = nullptr;
|
||||||
|
|
|
@ -78,6 +78,7 @@
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="action_Load_File"/>
|
<addaction name="action_Load_File"/>
|
||||||
<addaction name="action_Install_CIA"/>
|
<addaction name="action_Install_CIA"/>
|
||||||
|
<addaction name="action_Connect_Artic"/>
|
||||||
<addaction name="menu_Boot_Home_Menu"/>
|
<addaction name="menu_Boot_Home_Menu"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menu_recent_files"/>
|
<addaction name="menu_recent_files"/>
|
||||||
|
@ -222,6 +223,11 @@
|
||||||
<string>Install CIA...</string>
|
<string>Install CIA...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Connect_Artic">
|
||||||
|
<property name="text">
|
||||||
|
<string>Connect to Artic Base...</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
<action name="action_Boot_Home_Menu_JPN">
|
<action name="action_Boot_Home_Menu_JPN">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>JPN</string>
|
<string>JPN</string>
|
||||||
|
|
|
@ -116,6 +116,7 @@ struct Values {
|
||||||
bool game_dir_deprecated_deepscan;
|
bool game_dir_deprecated_deepscan;
|
||||||
QVector<UISettings::GameDir> game_dirs;
|
QVector<UISettings::GameDir> game_dirs;
|
||||||
QStringList recent_files;
|
QStringList recent_files;
|
||||||
|
QString last_artic_base_addr;
|
||||||
QString language;
|
QString language;
|
||||||
|
|
||||||
QString theme;
|
QString theme;
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
//---------------------------------------------------------------------------//
|
//---------------------------------------------------------------------------//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
|
@ -40,6 +40,8 @@ add_library(citra_core STATIC
|
||||||
dumping/backend.h
|
dumping/backend.h
|
||||||
dumping/ffmpeg_backend.cpp
|
dumping/ffmpeg_backend.cpp
|
||||||
dumping/ffmpeg_backend.h
|
dumping/ffmpeg_backend.h
|
||||||
|
file_sys/archive_artic.cpp
|
||||||
|
file_sys/archive_artic.h
|
||||||
file_sys/archive_backend.cpp
|
file_sys/archive_backend.cpp
|
||||||
file_sys/archive_backend.h
|
file_sys/archive_backend.h
|
||||||
file_sys/archive_extsavedata.cpp
|
file_sys/archive_extsavedata.cpp
|
||||||
|
@ -60,6 +62,8 @@ add_library(citra_core STATIC
|
||||||
file_sys/archive_source_sd_savedata.h
|
file_sys/archive_source_sd_savedata.h
|
||||||
file_sys/archive_systemsavedata.cpp
|
file_sys/archive_systemsavedata.cpp
|
||||||
file_sys/archive_systemsavedata.h
|
file_sys/archive_systemsavedata.h
|
||||||
|
file_sys/artic_cache.cpp
|
||||||
|
file_sys/artic_cache.h
|
||||||
file_sys/cia_common.h
|
file_sys/cia_common.h
|
||||||
file_sys/cia_container.cpp
|
file_sys/cia_container.cpp
|
||||||
file_sys/cia_container.h
|
file_sys/cia_container.h
|
||||||
|
@ -87,6 +91,10 @@ add_library(citra_core STATIC
|
||||||
file_sys/romfs_reader.h
|
file_sys/romfs_reader.h
|
||||||
file_sys/savedata_archive.cpp
|
file_sys/savedata_archive.cpp
|
||||||
file_sys/savedata_archive.h
|
file_sys/savedata_archive.h
|
||||||
|
file_sys/secure_value_backend_artic.cpp
|
||||||
|
file_sys/secure_value_backend_artic.h
|
||||||
|
file_sys/secure_value_backend.cpp
|
||||||
|
file_sys/secure_value_backend.h
|
||||||
file_sys/seed_db.cpp
|
file_sys/seed_db.cpp
|
||||||
file_sys/seed_db.h
|
file_sys/seed_db.h
|
||||||
file_sys/ticket.cpp
|
file_sys/ticket.cpp
|
||||||
|
@ -445,6 +453,8 @@ add_library(citra_core STATIC
|
||||||
hw/y2r.h
|
hw/y2r.h
|
||||||
loader/3dsx.cpp
|
loader/3dsx.cpp
|
||||||
loader/3dsx.h
|
loader/3dsx.h
|
||||||
|
loader/artic.cpp
|
||||||
|
loader/artic.h
|
||||||
loader/elf.cpp
|
loader/elf.cpp
|
||||||
loader/elf.h
|
loader/elf.h
|
||||||
loader/loader.cpp
|
loader/loader.cpp
|
||||||
|
@ -470,7 +480,7 @@ add_library(citra_core STATIC
|
||||||
tracer/citrace.h
|
tracer/citrace.h
|
||||||
tracer/recorder.cpp
|
tracer/recorder.cpp
|
||||||
tracer/recorder.h
|
tracer/recorder.h
|
||||||
)
|
)
|
||||||
|
|
||||||
create_target_directory_groups(citra_core)
|
create_target_directory_groups(citra_core)
|
||||||
|
|
||||||
|
|
|
@ -256,7 +256,11 @@ System::ResultStatus System::SingleStep() {
|
||||||
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath,
|
||||||
Frontend::EmuWindow* secondary_window) {
|
Frontend::EmuWindow* secondary_window) {
|
||||||
FileUtil::SetCurrentRomPath(filepath);
|
FileUtil::SetCurrentRomPath(filepath);
|
||||||
app_loader = Loader::GetLoader(filepath);
|
if (early_app_loader) {
|
||||||
|
app_loader = std::move(early_app_loader);
|
||||||
|
} else {
|
||||||
|
app_loader = Loader::GetLoader(filepath);
|
||||||
|
}
|
||||||
if (!app_loader) {
|
if (!app_loader) {
|
||||||
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
|
||||||
return ResultStatus::ErrorGetLoader;
|
return ResultStatus::ErrorGetLoader;
|
||||||
|
@ -286,6 +290,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||||
case Loader::ResultStatus::ErrorGbaTitle:
|
case Loader::ResultStatus::ErrorGbaTitle:
|
||||||
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
||||||
|
case Loader::ResultStatus::ErrorArtic:
|
||||||
|
return ResultStatus::ErrorArticDisconnected;
|
||||||
default:
|
default:
|
||||||
return ResultStatus::ErrorSystemMode;
|
return ResultStatus::ErrorSystemMode;
|
||||||
}
|
}
|
||||||
|
@ -334,6 +340,8 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
|
||||||
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
return ResultStatus::ErrorLoader_ErrorInvalidFormat;
|
||||||
case Loader::ResultStatus::ErrorGbaTitle:
|
case Loader::ResultStatus::ErrorGbaTitle:
|
||||||
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
return ResultStatus::ErrorLoader_ErrorGbaTitle;
|
||||||
|
case Loader::ResultStatus::ErrorArtic:
|
||||||
|
return ResultStatus::ErrorArticDisconnected;
|
||||||
default:
|
default:
|
||||||
return ResultStatus::ErrorLoader;
|
return ResultStatus::ErrorLoader;
|
||||||
}
|
}
|
||||||
|
@ -691,6 +699,10 @@ void System::ApplySettings() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader) {
|
||||||
|
early_app_loader = std::move(loader);
|
||||||
|
}
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void System::serialize(Archive& ar, const unsigned int file_version) {
|
void System::serialize(Archive& ar, const unsigned int file_version) {
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ public:
|
||||||
///< Console
|
///< Console
|
||||||
ErrorSystemFiles, ///< Error in finding system files
|
ErrorSystemFiles, ///< Error in finding system files
|
||||||
ErrorSavestate, ///< Error saving or loading
|
ErrorSavestate, ///< Error saving or loading
|
||||||
|
ErrorArticDisconnected, ///< Error when artic base disconnects
|
||||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||||
ErrorUnknown ///< Any other error
|
ErrorUnknown ///< Any other error
|
||||||
};
|
};
|
||||||
|
@ -169,6 +170,18 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] PerfStats::Results GetAndResetPerfStats();
|
[[nodiscard]] PerfStats::Results GetAndResetPerfStats();
|
||||||
|
|
||||||
|
void ReportArticTraffic(u32 bytes) {
|
||||||
|
if (perf_stats) {
|
||||||
|
perf_stats->AddArticBaseTraffic(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportPerfArticEvent(PerfStats::PerfArticEventBits event, bool set) {
|
||||||
|
if (perf_stats) {
|
||||||
|
perf_stats->ReportPerfArticEvent(event, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] PerfStats::Results GetLastPerfStats();
|
[[nodiscard]] PerfStats::Results GetLastPerfStats();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -346,6 +359,8 @@ public:
|
||||||
/// Applies any changes to settings to this core instance.
|
/// Applies any changes to settings to this core instance.
|
||||||
void ApplySettings();
|
void ApplySettings();
|
||||||
|
|
||||||
|
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Initialize the emulated system.
|
* Initialize the emulated system.
|
||||||
|
@ -366,6 +381,9 @@ private:
|
||||||
/// AppLoader used to load the current executing application
|
/// AppLoader used to load the current executing application
|
||||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||||
|
|
||||||
|
// Temporary app loader passed from frontend
|
||||||
|
std::unique_ptr<Loader::AppLoader> early_app_loader;
|
||||||
|
|
||||||
/// ARM11 CPU core
|
/// ARM11 CPU core
|
||||||
std::vector<std::shared_ptr<ARM_Interface>> cpu_cores;
|
std::vector<std::shared_ptr<ARM_Interface>> cpu_cores;
|
||||||
ARM_Interface* running_core = nullptr;
|
ARM_Interface* running_core = nullptr;
|
||||||
|
|
535
src/core/file_sys/archive_artic.cpp
Normal file
535
src/core/file_sys/archive_artic.cpp
Normal file
|
@ -0,0 +1,535 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "archive_artic.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
std::vector<u8> ArticArchive::BuildFSPath(const Path& path) {
|
||||||
|
std::vector<u8> ret(sizeof(u32) * 2);
|
||||||
|
u32* raw_data = reinterpret_cast<u32*>(ret.data());
|
||||||
|
auto path_type = path.GetType();
|
||||||
|
auto binary = path.AsBinary();
|
||||||
|
raw_data[0] = static_cast<u32>(path_type);
|
||||||
|
raw_data[1] = static_cast<u32>(binary.size());
|
||||||
|
if (!binary.empty()) {
|
||||||
|
ret.insert(ret.end(), binary.begin(), binary.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The insert may have invalidated the pointer
|
||||||
|
raw_data = reinterpret_cast<u32*>(ret.data());
|
||||||
|
if (path_type != LowPathType::Binary && path_type != LowPathType::Invalid) {
|
||||||
|
if (path_type == LowPathType::Wchar) {
|
||||||
|
raw_data[1] += 2;
|
||||||
|
ret.push_back(0);
|
||||||
|
ret.push_back(0);
|
||||||
|
} else {
|
||||||
|
raw_data[1] += 1;
|
||||||
|
ret.push_back(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::RespResult(const std::optional<Network::ArticBase::Client::Response>& resp) {
|
||||||
|
if (!resp.has_value() || !resp->Succeeded()) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
return Result(static_cast<u32>(resp->GetMethodResult()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticArchive::~ArticArchive() {
|
||||||
|
if (clear_cache_on_close) {
|
||||||
|
cache_provider->ClearAllCache();
|
||||||
|
}
|
||||||
|
if (archive_handle != -1) {
|
||||||
|
auto req = client->NewRequest("FSUSER_CloseArchive");
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
client->Send(req);
|
||||||
|
if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArticArchive::Open(
|
||||||
|
std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id,
|
||||||
|
const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event,
|
||||||
|
ArticCacheProvider& cache_provider, bool clear_cache_on_close) {
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_OpenArchive");
|
||||||
|
|
||||||
|
req.AddParameterS32(static_cast<s32>(archive_id));
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded()) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
Result res(static_cast<u32>(resp->GetMethodResult()));
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto handle_opt = resp->GetResponseS64(0);
|
||||||
|
if (!handle_opt.has_value()) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<ArticArchive>(client, *handle_opt, report_artic_event, cache_provider,
|
||||||
|
path, clear_cache_on_close);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticArchive::Close() {
|
||||||
|
if (clear_cache_on_close) {
|
||||||
|
cache_provider->ClearAllCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_CloseArchive");
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
if (RespResult(client->Send(req)).IsSuccess()) {
|
||||||
|
archive_handle = -1;
|
||||||
|
if (report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ArticArchive::GetName() const {
|
||||||
|
return "ArticArchive";
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> ArticArchive::OpenFile(const Path& path, const Mode& mode,
|
||||||
|
u32 attributes) {
|
||||||
|
if (mode.create_flag) {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto req = client->NewRequest("FSUSER_OpenFile");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
req.AddParameterU32(mode.hex);
|
||||||
|
req.AddParameterU32(attributes);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto handle_opt = resp->GetResponseS32(0);
|
||||||
|
if (!handle_opt.has_value())
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
auto size_opt = resp->GetResponseU64(1);
|
||||||
|
if (size_opt.has_value()) {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, path), true);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->ForceSetSize(static_cast<size_t>(*size_opt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open_reporter->open_files++ == 0 &&
|
||||||
|
report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<ArticFileBackend>(client, *handle_opt, open_reporter, archive_path,
|
||||||
|
*cache_provider, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::DeleteFile(const Path& path) const {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_DeleteFile");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, src_path), false);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->Clear();
|
||||||
|
}
|
||||||
|
cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, dest_path), false);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_RenameFile");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto src_path_buf = BuildFSPath(src_path);
|
||||||
|
req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size());
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto dest_path_buf = BuildFSPath(dest_path);
|
||||||
|
req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size());
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::DeleteDirectory(const Path& path) const {
|
||||||
|
cache_provider->ClearAllCache();
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_DeleteDirectory");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
|
cache_provider->ClearAllCache();
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_DeleteDirectoryRec");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, path), false);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
cache->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_CreateFile");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
req.AddParameterU32(attributes);
|
||||||
|
req.AddParameterU64(size);
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||||
|
auto req = client->NewRequest("FSUSER_CreateDirectory");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
req.AddParameterU32(attributes);
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
|
||||||
|
cache_provider->ClearAllCache();
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_RenameDirectory");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto src_path_buf = BuildFSPath(src_path);
|
||||||
|
req.AddParameterBuffer(src_path_buf.data(), src_path_buf.size());
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto dest_path_buf = BuildFSPath(dest_path);
|
||||||
|
req.AddParameterBuffer(dest_path_buf.data(), dest_path_buf.size());
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> ArticArchive::OpenDirectory(const Path& path) {
|
||||||
|
auto req = client->NewRequest("FSUSER_OpenDirectory");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
auto path_buf = BuildFSPath(path);
|
||||||
|
req.AddParameterBuffer(path_buf.data(), path_buf.size());
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto handle_opt = resp->GetResponseS32(0);
|
||||||
|
if (!handle_opt.has_value())
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
if (open_reporter->open_files++ == 0 &&
|
||||||
|
report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event) | (1ULL << 32));
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<ArticDirectoryBackend>(client, *handle_opt, archive_path,
|
||||||
|
open_reporter);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 ArticArchive::GetFreeBytes() const {
|
||||||
|
auto req = client->NewRequest("FSUSER_GetFreeBytes");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = RespResult(resp);
|
||||||
|
if (res.IsError()) // TODO(PabloMK7): Return error code and not u64
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto free_bytes_opt = resp->GetResponseS64(0);
|
||||||
|
return free_bytes_opt.has_value() ? static_cast<u64>(*free_bytes_opt) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) {
|
||||||
|
auto req = client->NewRequest("FSUSER_ControlArchive");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
req.AddParameterU32(action);
|
||||||
|
req.AddParameterBuffer(input, input_size);
|
||||||
|
req.AddParameterU32(static_cast<u32>(output_size));
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto output_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!output_buf.has_value())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (output_buf->second != output_size)
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
memcpy(output, output_buf->first, output_buf->second);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticArchive::SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) {
|
||||||
|
auto req = client->NewRequest("FSUSER_SetSaveDataSecureValue");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
req.AddParameterU64(secure_value);
|
||||||
|
req.AddParameterS8(flush != 0);
|
||||||
|
|
||||||
|
return RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> ArticArchive::GetSaveDataSecureValue(u32 secure_value_slot) {
|
||||||
|
auto req = client->NewRequest("FSUSER_GetSaveDataSecureValue");
|
||||||
|
|
||||||
|
req.AddParameterS64(archive_handle);
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool exists;
|
||||||
|
bool isGamecard;
|
||||||
|
u64 secure_value;
|
||||||
|
} secure_value_result;
|
||||||
|
static_assert(sizeof(secure_value_result) == 0x10);
|
||||||
|
|
||||||
|
auto output_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!output_buf.has_value())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (output_buf->second != sizeof(secure_value_result))
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
memcpy(&secure_value_result, output_buf->first, output_buf->second);
|
||||||
|
return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard,
|
||||||
|
secure_value_result.secure_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticArchive::OpenFileReporter::OnFileClosed() {
|
||||||
|
if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticArchive::OpenFileReporter::OnDirectoryClosed() {
|
||||||
|
if (--open_files == 0 && report_artic_event != Core::PerfStats::PerfArticEventBits::NONE) {
|
||||||
|
client->ReportArticEvent(static_cast<u64>(report_artic_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticFileBackend::~ArticFileBackend() {
|
||||||
|
if (file_handle != -1) {
|
||||||
|
auto req = client->NewRequest("FSFILE_Close");
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
client->Send(req);
|
||||||
|
open_reporter->OnFileClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> ArticFileBackend::Read(u64 offset, std::size_t length, u8* buffer) const {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||||
|
|
||||||
|
if (cache != nullptr) {
|
||||||
|
return cache->Read(file_handle, offset, length, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSFILE_Read");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
req.AddParameterU64(offset);
|
||||||
|
req.AddParameterU32(static_cast<u32>(length));
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto read_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!read_buf || read_buf->second > length) {
|
||||||
|
return std::size_t(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buffer, read_buf->first, read_buf->second);
|
||||||
|
return read_buf->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> ArticFileBackend::Write(u64 offset, std::size_t length, bool flush,
|
||||||
|
bool update_timestamp, const u8* buffer) {
|
||||||
|
u32 flags = (flush ? 1 : 0) | (update_timestamp ? (1 << 8) : 0);
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
return cache->Write(file_handle, offset, length, buffer, flags);
|
||||||
|
} else {
|
||||||
|
auto req = client->NewRequest("FSFILE_Write");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
req.AddParameterU64(offset);
|
||||||
|
req.AddParameterU32(static_cast<u32>(length));
|
||||||
|
req.AddParameterU32(flags);
|
||||||
|
req.AddParameterBuffer(buffer, length);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto writen_buf = resp->GetResponseS32(0);
|
||||||
|
if (!writen_buf) {
|
||||||
|
return std::size_t(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::size_t(*writen_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 ArticFileBackend::GetSize() const {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||||
|
if (cache != nullptr) {
|
||||||
|
auto res = cache->GetSize(file_handle);
|
||||||
|
if (res.Failed())
|
||||||
|
return 0;
|
||||||
|
return res.Unwrap();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSFILE_GetSize");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto size_buf = resp->GetResponseS64(0);
|
||||||
|
if (!size_buf) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return *size_buf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticFileBackend::SetSize(u64 size) const {
|
||||||
|
auto req = client->NewRequest("FSFILE_SetSize");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
req.AddParameterU64(size);
|
||||||
|
|
||||||
|
return ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticFileBackend::Close() {
|
||||||
|
auto req = client->NewRequest("FSFILE_Close");
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||||
|
if (ret) {
|
||||||
|
file_handle = -1;
|
||||||
|
open_reporter->OnFileClosed();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticFileBackend::Flush() const {
|
||||||
|
auto req = client->NewRequest("FSFILE_Flush");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
|
||||||
|
client->Send(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticDirectoryBackend::~ArticDirectoryBackend() {
|
||||||
|
if (dir_handle != -1) {
|
||||||
|
auto req = client->NewRequest("FSDIR_Close");
|
||||||
|
req.AddParameterS32(dir_handle);
|
||||||
|
client->Send(req);
|
||||||
|
open_reporter->OnDirectoryClosed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 ArticDirectoryBackend::Read(const u32 count, Entry* entries) {
|
||||||
|
auto req = client->NewRequest("FSDIR_Read");
|
||||||
|
|
||||||
|
req.AddParameterS32(dir_handle);
|
||||||
|
req.AddParameterU32(count);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto entry_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!entry_buf) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u32 ret_count = static_cast<u32>(entry_buf->second / sizeof(Entry));
|
||||||
|
|
||||||
|
memcpy(entries, entry_buf->first, ret_count * sizeof(Entry));
|
||||||
|
return ret_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticDirectoryBackend::Close() {
|
||||||
|
auto req = client->NewRequest("FSDIR_Close");
|
||||||
|
req.AddParameterS32(dir_handle);
|
||||||
|
bool ret = ArticArchive::RespResult(client->Send(req)).IsSuccess();
|
||||||
|
if (ret) {
|
||||||
|
dir_handle = -1;
|
||||||
|
open_reporter->OnDirectoryClosed();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} // namespace FileSys
|
268
src/core/file_sys/archive_artic.h
Normal file
268
src/core/file_sys/archive_artic.h
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "atomic"
|
||||||
|
|
||||||
|
#include <boost/serialization/unique_ptr.hpp>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/artic_cache.h"
|
||||||
|
#include "core/file_sys/directory_backend.h"
|
||||||
|
#include "core/file_sys/file_backend.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
#include "core/perf_stats.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
class ArticArchive : public ArchiveBackend {
|
||||||
|
public:
|
||||||
|
static std::vector<u8> BuildFSPath(const Path& path);
|
||||||
|
static Result RespResult(const std::optional<Network::ArticBase::Client::Response>& resp);
|
||||||
|
|
||||||
|
explicit ArticArchive(std::shared_ptr<Network::ArticBase::Client>& _client, s64 _archive_handle,
|
||||||
|
Core::PerfStats::PerfArticEventBits _report_artic_event,
|
||||||
|
ArticCacheProvider& _cache_provider, const Path& _archive_path,
|
||||||
|
bool _clear_cache_on_close)
|
||||||
|
: client(_client), archive_handle(_archive_handle), report_artic_event(_report_artic_event),
|
||||||
|
cache_provider(&_cache_provider), archive_path(_archive_path),
|
||||||
|
clear_cache_on_close(_clear_cache_on_close) {
|
||||||
|
open_reporter = std::make_shared<OpenFileReporter>(_client, _report_artic_event);
|
||||||
|
}
|
||||||
|
~ArticArchive() override;
|
||||||
|
|
||||||
|
static ResultVal<std::unique_ptr<ArchiveBackend>> Open(
|
||||||
|
std::shared_ptr<Network::ArticBase::Client>& client, Service::FS::ArchiveIdCode archive_id,
|
||||||
|
const Path& path, Core::PerfStats::PerfArticEventBits report_artic_event,
|
||||||
|
ArticCacheProvider& cache_provider, bool clear_cache_on_close);
|
||||||
|
|
||||||
|
void Close() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.)
|
||||||
|
*/
|
||||||
|
std::string GetName() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file specified by its path, using the specified mode
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @param mode Mode to open the file with
|
||||||
|
* @return Opened file, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
|
u32 attributes) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a file specified by its path
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result DeleteFile(const Path& path) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a File specified by its path
|
||||||
|
* @param src_path Source path relative to the archive
|
||||||
|
* @param dest_path Destination path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a directory specified by its path
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result DeleteDirectory(const Path& path) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a directory specified by its path and anything under it
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file specified by its path
|
||||||
|
* @param path Path relative to the Archive
|
||||||
|
* @param size The size of the new file, filled with zeroes
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a directory specified by its path
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a Directory specified by its path
|
||||||
|
* @param src_path Source path relative to the archive
|
||||||
|
* @param dest_path Destination path relative to the archive
|
||||||
|
* @return Result of the operation
|
||||||
|
*/
|
||||||
|
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a directory specified by its path
|
||||||
|
* @param path Path relative to the archive
|
||||||
|
* @return Opened directory, or error code
|
||||||
|
*/
|
||||||
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the free space
|
||||||
|
* @return The number of free bytes in the archive
|
||||||
|
*/
|
||||||
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
|
Result Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) override;
|
||||||
|
|
||||||
|
Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) override;
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Path& GetArchivePath() {
|
||||||
|
return archive_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ArticArchive() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ArticFileBackend;
|
||||||
|
friend class ArticDirectoryBackend;
|
||||||
|
class OpenFileReporter {
|
||||||
|
public:
|
||||||
|
OpenFileReporter(const std::shared_ptr<Network::ArticBase::Client>& cli,
|
||||||
|
Core::PerfStats::PerfArticEventBits _report_artic_event)
|
||||||
|
: client(cli), report_artic_event(_report_artic_event) {}
|
||||||
|
|
||||||
|
void OnFileClosed();
|
||||||
|
|
||||||
|
void OnDirectoryClosed();
|
||||||
|
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
Core::PerfStats::PerfArticEventBits report_artic_event =
|
||||||
|
Core::PerfStats::PerfArticEventBits::NONE;
|
||||||
|
std::atomic<u32> open_files = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
s64 archive_handle;
|
||||||
|
std::shared_ptr<OpenFileReporter> open_reporter;
|
||||||
|
Core::PerfStats::PerfArticEventBits report_artic_event =
|
||||||
|
Core::PerfStats::PerfArticEventBits::NONE;
|
||||||
|
ArticCacheProvider* cache_provider = nullptr;
|
||||||
|
Path archive_path;
|
||||||
|
bool clear_cache_on_close;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<ArchiveBackend>(*this);
|
||||||
|
ar& archive_handle;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArticFileBackend : public FileBackend {
|
||||||
|
public:
|
||||||
|
explicit ArticFileBackend(std::shared_ptr<Network::ArticBase::Client>& _client,
|
||||||
|
s32 _file_handle,
|
||||||
|
const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter,
|
||||||
|
const Path& _archive_path, ArticCacheProvider& _cache_provider,
|
||||||
|
const Path& _file_path)
|
||||||
|
: client(_client), file_handle(_file_handle), open_reporter(_open_reporter),
|
||||||
|
archive_path(_archive_path), cache_provider(&_cache_provider), file_path(_file_path) {}
|
||||||
|
~ArticFileBackend() override;
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
|
const u8* buffer) override;
|
||||||
|
|
||||||
|
u64 GetSize() const override;
|
||||||
|
|
||||||
|
bool SetSize(u64 size) const override;
|
||||||
|
|
||||||
|
bool Close() override;
|
||||||
|
|
||||||
|
void Flush() const override;
|
||||||
|
|
||||||
|
bool AllowsCachedReads() const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
||||||
|
auto cache = cache_provider->ProvideCache(
|
||||||
|
client, cache_provider->PathsToVector(archive_path, file_path), true);
|
||||||
|
if (cache == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return cache->CacheReady(file_offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ArticFileBackend() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
s32 file_handle;
|
||||||
|
std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter;
|
||||||
|
Path archive_path;
|
||||||
|
ArticCacheProvider* cache_provider = nullptr;
|
||||||
|
Path file_path;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<FileBackend>(*this);
|
||||||
|
ar& file_handle;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArticDirectoryBackend : public DirectoryBackend {
|
||||||
|
public:
|
||||||
|
explicit ArticDirectoryBackend(
|
||||||
|
std::shared_ptr<Network::ArticBase::Client>& _client, s32 _dir_handle,
|
||||||
|
const Path& _archive_path,
|
||||||
|
const std::shared_ptr<ArticArchive::OpenFileReporter>& _open_reporter)
|
||||||
|
: client(_client), dir_handle(_dir_handle), archive_path(_archive_path),
|
||||||
|
open_reporter(_open_reporter) {}
|
||||||
|
~ArticDirectoryBackend() override;
|
||||||
|
|
||||||
|
u32 Read(const u32 count, Entry* entries) override;
|
||||||
|
bool Close() override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ArticDirectoryBackend() = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
s32 dir_handle;
|
||||||
|
Path archive_path;
|
||||||
|
std::shared_ptr<ArticArchive::OpenFileReporter> open_reporter;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<DirectoryBackend>(*this);
|
||||||
|
ar& dir_handle;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
} // namespace FileSys
|
||||||
|
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticArchive)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticFileBackend)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticDirectoryBackend)
|
|
@ -105,8 +105,7 @@ std::vector<u8> Path::AsBinary() const {
|
||||||
std::vector<u8> to_return(u16str.size() * 2);
|
std::vector<u8> to_return(u16str.size() * 2);
|
||||||
for (std::size_t i = 0; i < u16str.size(); ++i) {
|
for (std::size_t i = 0; i < u16str.size(); ++i) {
|
||||||
u16 tmp_char = u16str.at(i);
|
u16 tmp_char = u16str.at(i);
|
||||||
to_return[i * 2] = (tmp_char & 0xFF00) >> 8;
|
*reinterpret_cast<u16*>(to_return.data() + i * 2) = tmp_char;
|
||||||
to_return[i * 2 + 1] = (tmp_char & 0x00FF);
|
|
||||||
}
|
}
|
||||||
return to_return;
|
return to_return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ struct ArchiveFormatInfo {
|
||||||
u8 duplicate_data; ///< Whether the archive should duplicate the data.
|
u8 duplicate_data; ///< Whether the archive should duplicate the data.
|
||||||
};
|
};
|
||||||
static_assert(std::is_trivial_v<ArchiveFormatInfo>, "ArchiveFormatInfo is not POD");
|
static_assert(std::is_trivial_v<ArchiveFormatInfo>, "ArchiveFormatInfo is not POD");
|
||||||
|
static_assert(sizeof(ArchiveFormatInfo) == 16, "Invalid ArchiveFormatInfo size");
|
||||||
|
|
||||||
class ArchiveBackend : NonCopyable {
|
class ArchiveBackend : NonCopyable {
|
||||||
public:
|
public:
|
||||||
|
@ -119,8 +120,8 @@ public:
|
||||||
* @param mode Mode to open the file with
|
* @param mode Mode to open the file with
|
||||||
* @return Opened file, or error code
|
* @return Opened file, or error code
|
||||||
*/
|
*/
|
||||||
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const = 0;
|
u32 attributes = 0) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a file specified by its path
|
* Delete a file specified by its path
|
||||||
|
@ -157,14 +158,14 @@ public:
|
||||||
* @param size The size of the new file, filled with zeroes
|
* @param size The size of the new file, filled with zeroes
|
||||||
* @return Result of the operation
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual Result CreateFile(const Path& path, u64 size) const = 0;
|
virtual Result CreateFile(const Path& path, u64 size, u32 attributes = 0) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a directory specified by its path
|
* Create a directory specified by its path
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Result of the operation
|
* @return Result of the operation
|
||||||
*/
|
*/
|
||||||
virtual Result CreateDirectory(const Path& path) const = 0;
|
virtual Result CreateDirectory(const Path& path, u32 attributes = 0) const = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a Directory specified by its path
|
* Rename a Directory specified by its path
|
||||||
|
@ -179,7 +180,7 @@ public:
|
||||||
* @param path Path relative to the archive
|
* @param path Path relative to the archive
|
||||||
* @return Opened directory, or error code
|
* @return Opened directory, or error code
|
||||||
*/
|
*/
|
||||||
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
|
virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the free space
|
* Get the free space
|
||||||
|
@ -187,6 +188,20 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual u64 GetFreeBytes() const = 0;
|
virtual u64 GetFreeBytes() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the archive
|
||||||
|
*/
|
||||||
|
virtual void Close() {}
|
||||||
|
|
||||||
|
virtual Result Control(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) {
|
||||||
|
LOG_WARNING(Service_FS,
|
||||||
|
"(STUBBED) called, archive={}, action={:08X}, input_size={:08X}, "
|
||||||
|
"output_size={:08X}",
|
||||||
|
GetName(), action, input_size, output_size);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
u64 GetOpenDelayNs() {
|
u64 GetOpenDelayNs() {
|
||||||
if (delay_generator != nullptr) {
|
if (delay_generator != nullptr) {
|
||||||
return delay_generator->GetOpenDelayNs();
|
return delay_generator->GetOpenDelayNs();
|
||||||
|
@ -196,6 +211,31 @@ public:
|
||||||
return delay_generator->GetOpenDelayNs();
|
return delay_generator->GetOpenDelayNs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual Result SetSaveDataSecureValue(u32 secure_value_slot, u64 secure_value, bool flush) {
|
||||||
|
|
||||||
|
// TODO: Generate and Save the Secure Value
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS,
|
||||||
|
"(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} "
|
||||||
|
"flush={}",
|
||||||
|
secure_value, secure_value_slot, flush);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(u32 secure_value_slot) {
|
||||||
|
|
||||||
|
// TODO: Implement Secure Value Lookup & Generation
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot);
|
||||||
|
|
||||||
|
return std::make_tuple<bool, bool, u64>(false, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool IsSlow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<DelayGenerator> delay_generator;
|
std::unique_ptr<DelayGenerator> delay_generator;
|
||||||
|
|
||||||
|
@ -232,7 +272,7 @@ public:
|
||||||
* @return Result of the operation, 0 on success
|
* @return Result of the operation, 0 on success
|
||||||
*/
|
*/
|
||||||
virtual Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
virtual Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) = 0;
|
u64 program_id, u32 directory_buckets, u32 file_buckets) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the format info about the archive with the specified path
|
* Retrieves the format info about the archive with the specified path
|
||||||
|
@ -242,6 +282,10 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const = 0;
|
virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const = 0;
|
||||||
|
|
||||||
|
virtual bool IsSlow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {}
|
void serialize(Archive& ar, const unsigned int) {}
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/file_sys/archive_artic.h"
|
||||||
#include "core/file_sys/archive_extsavedata.h"
|
#include "core/file_sys/archive_extsavedata.h"
|
||||||
#include "core/file_sys/disk_archive.h"
|
#include "core/file_sys/disk_archive.h"
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
|
@ -37,7 +38,7 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override {
|
const u8* buffer) override {
|
||||||
if (offset > size) {
|
if (offset > size) {
|
||||||
return ResultWriteBeyondEnd;
|
return ResultWriteBeyondEnd;
|
||||||
|
@ -49,7 +50,7 @@ public:
|
||||||
length = size - offset;
|
length = size - offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DiskFile::Write(offset, length, flush, buffer);
|
return DiskFile::Write(offset, length, flush, update_timestamp, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -100,8 +101,8 @@ public:
|
||||||
return "ExtSaveDataArchive: " + mount_point;
|
return "ExtSaveDataArchive: " + mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override {
|
u32 attributes) override {
|
||||||
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
||||||
|
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
@ -234,69 +235,187 @@ Path ArchiveFactory_ExtSaveData::GetCorrectedPath(const Path& path) {
|
||||||
return {binary_data};
|
return {binary_data};
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
|
static Service::FS::ArchiveIdCode ExtSaveDataTypeToArchiveID(ExtSaveDataType type) {
|
||||||
u64 program_id) {
|
switch (type) {
|
||||||
const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/";
|
case FileSys::ExtSaveDataType::Normal:
|
||||||
const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory;
|
return Service::FS::ArchiveIdCode::ExtSaveData;
|
||||||
if (!FileUtil::Exists(fullpath)) {
|
case FileSys::ExtSaveDataType::Shared:
|
||||||
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
|
return Service::FS::ArchiveIdCode::SharedExtSaveData;
|
||||||
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
case FileSys::ExtSaveDataType::Boss:
|
||||||
if (type != ExtSaveDataType::Shared) {
|
return Service::FS::ArchiveIdCode::BossExtSaveData;
|
||||||
return ResultNotFoundInvalidState;
|
default:
|
||||||
} else {
|
return Service::FS::ArchiveIdCode::ExtSaveData;
|
||||||
return ResultNotFormatted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<ExtSaveDataDelayGenerator>();
|
|
||||||
return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_ExtSaveData::Format(const Path& path,
|
static Core::PerfStats::PerfArticEventBits ExtSaveDataTypeToPerfArtic(ExtSaveDataType type) {
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
switch (type) {
|
||||||
u64 program_id) {
|
case FileSys::ExtSaveDataType::Normal:
|
||||||
auto corrected_path = GetCorrectedPath(path);
|
return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA;
|
||||||
|
case FileSys::ExtSaveDataType::Shared:
|
||||||
// These folders are always created with the ExtSaveData
|
return Core::PerfStats::PerfArticEventBits::ARTIC_SHARED_EXT_DATA;
|
||||||
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
|
case FileSys::ExtSaveDataType::Boss:
|
||||||
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
|
return Core::PerfStats::PerfArticEventBits::ARTIC_BOSS_EXT_DATA;
|
||||||
FileUtil::CreateFullPath(user_path);
|
default:
|
||||||
FileUtil::CreateFullPath(boss_path);
|
return Core::PerfStats::PerfArticEventBits::ARTIC_EXT_DATA;
|
||||||
|
|
||||||
// Write the format metadata
|
|
||||||
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata";
|
|
||||||
FileUtil::IOFile file(metadata_path, "wb");
|
|
||||||
|
|
||||||
if (!file.IsOpen()) {
|
|
||||||
// TODO(Subv): Find the correct error code
|
|
||||||
return ResultUnknown;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
file.WriteBytes(&format_info, sizeof(format_info));
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path,
|
||||||
return ResultSuccess;
|
u64 program_id) {
|
||||||
|
if (IsUsingArtic()) {
|
||||||
|
EnsureCacheCreated();
|
||||||
|
return ArticArchive::Open(artic_client, ExtSaveDataTypeToArchiveID(type), path,
|
||||||
|
ExtSaveDataTypeToPerfArtic(type), *this,
|
||||||
|
type != FileSys::ExtSaveDataType::Normal);
|
||||||
|
} else {
|
||||||
|
const auto directory = type == ExtSaveDataType::Boss ? "boss/" : "user/";
|
||||||
|
const auto fullpath = GetExtSaveDataPath(mount_point, GetCorrectedPath(path)) + directory;
|
||||||
|
if (!FileUtil::Exists(fullpath)) {
|
||||||
|
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
|
||||||
|
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
||||||
|
if (type != ExtSaveDataType::Shared) {
|
||||||
|
return ResultNotFoundInvalidState;
|
||||||
|
} else {
|
||||||
|
return ResultNotFormatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::unique_ptr<DelayGenerator> delay_generator =
|
||||||
|
std::make_unique<ExtSaveDataDelayGenerator>();
|
||||||
|
return std::make_unique<ExtSaveDataArchive>(fullpath, std::move(delay_generator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArchiveFactory_ExtSaveData::FormatAsExtData(const Path& path,
|
||||||
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
|
u8 unknown, u64 program_id, u64 total_size,
|
||||||
|
std::span<const u8> icon) {
|
||||||
|
if (IsUsingArtic()) {
|
||||||
|
ExtSaveDataArchivePath path_data;
|
||||||
|
std::memcpy(&path_data, path.AsBinary().data(), sizeof(path_data));
|
||||||
|
|
||||||
|
Service::FS::ExtSaveDataInfo artic_extdata_path;
|
||||||
|
|
||||||
|
artic_extdata_path.media_type = static_cast<u8>(path_data.media_type);
|
||||||
|
artic_extdata_path.unknown = unknown;
|
||||||
|
artic_extdata_path.save_id_low = path_data.save_low;
|
||||||
|
artic_extdata_path.save_id_high = path_data.save_high;
|
||||||
|
|
||||||
|
auto req = artic_client->NewRequest("FSUSER_CreateExtSaveData");
|
||||||
|
|
||||||
|
req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path));
|
||||||
|
req.AddParameterU32(format_info.number_directories);
|
||||||
|
req.AddParameterU32(format_info.number_files);
|
||||||
|
req.AddParameterU64(total_size);
|
||||||
|
req.AddParameterBuffer(icon.data(), icon.size());
|
||||||
|
|
||||||
|
return ArticArchive::RespResult(artic_client->Send(req));
|
||||||
|
} else {
|
||||||
|
auto corrected_path = GetCorrectedPath(path);
|
||||||
|
|
||||||
|
// These folders are always created with the ExtSaveData
|
||||||
|
std::string user_path = GetExtSaveDataPath(mount_point, corrected_path) + "user/";
|
||||||
|
std::string boss_path = GetExtSaveDataPath(mount_point, corrected_path) + "boss/";
|
||||||
|
FileUtil::CreateFullPath(user_path);
|
||||||
|
FileUtil::CreateFullPath(boss_path);
|
||||||
|
|
||||||
|
// Write the format metadata
|
||||||
|
std::string metadata_path = GetExtSaveDataPath(mount_point, corrected_path) + "metadata";
|
||||||
|
FileUtil::IOFile file(metadata_path, "wb");
|
||||||
|
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
// TODO(Subv): Find the correct error code
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.WriteBytes(&format_info, sizeof(format_info));
|
||||||
|
|
||||||
|
FileUtil::IOFile icon_file(FileSys::GetExtSaveDataPath(GetMountPoint(), path) + "icon",
|
||||||
|
"wb");
|
||||||
|
icon_file.WriteBytes(icon.data(), icon.size());
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArchiveFactory_ExtSaveData::DeleteExtData(Service::FS::MediaType media_type, u8 unknown,
|
||||||
|
u32 high, u32 low) {
|
||||||
|
if (IsUsingArtic()) {
|
||||||
|
Service::FS::ExtSaveDataInfo artic_extdata_path;
|
||||||
|
|
||||||
|
artic_extdata_path.media_type = static_cast<u8>(media_type);
|
||||||
|
artic_extdata_path.unknown = unknown;
|
||||||
|
artic_extdata_path.save_id_low = low;
|
||||||
|
artic_extdata_path.save_id_high = high;
|
||||||
|
|
||||||
|
auto req = artic_client->NewRequest("FSUSER_DeleteExtSaveData");
|
||||||
|
|
||||||
|
req.AddParameterBuffer(&artic_extdata_path, sizeof(artic_extdata_path));
|
||||||
|
|
||||||
|
return ArticArchive::RespResult(artic_client->Send(req));
|
||||||
|
} else {
|
||||||
|
// Construct the binary path to the archive first
|
||||||
|
FileSys::Path path =
|
||||||
|
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low);
|
||||||
|
|
||||||
|
std::string media_type_directory;
|
||||||
|
if (media_type == Service::FS::MediaType::NAND) {
|
||||||
|
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
||||||
|
} else if (media_type == Service::FS::MediaType::SDMC) {
|
||||||
|
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Service_FS, "Unsupported media type {}", media_type);
|
||||||
|
return ResultUnknown; // TODO(Subv): Find the right error code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all directories (/user, /boss) and the icon file.
|
||||||
|
std::string base_path = FileSys::GetExtDataContainerPath(
|
||||||
|
media_type_directory, media_type == Service::FS::MediaType::NAND);
|
||||||
|
std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path);
|
||||||
|
if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path))
|
||||||
|
return ResultUnknown; // TODO(Subv): Find the right error code
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path,
|
||||||
u64 program_id) const {
|
u64 program_id) const {
|
||||||
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
|
if (IsUsingArtic()) {
|
||||||
FileUtil::IOFile file(metadata_path, "rb");
|
auto req = artic_client->NewRequest("FSUSER_GetFormatInfo");
|
||||||
|
|
||||||
if (!file.IsOpen()) {
|
req.AddParameterS32(static_cast<u32>(ExtSaveDataTypeToArchiveID(type)));
|
||||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
auto path_artic = ArticArchive::BuildFSPath(path);
|
||||||
// TODO(Subv): Verify error code
|
req.AddParameterBuffer(path_artic.data(), path_artic.size());
|
||||||
return ResultNotFormatted;
|
|
||||||
|
auto resp = artic_client->Send(req);
|
||||||
|
Result res = ArticArchive::RespResult(resp);
|
||||||
|
if (R_FAILED(res)) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveFormatInfo info;
|
||||||
|
memcpy(&info, info_buf->first, sizeof(info));
|
||||||
|
return info;
|
||||||
|
} else {
|
||||||
|
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata";
|
||||||
|
FileUtil::IOFile file(metadata_path, "rb");
|
||||||
|
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
||||||
|
// TODO(Subv): Verify error code
|
||||||
|
return ResultNotFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveFormatInfo info = {};
|
||||||
|
file.ReadBytes(&info, sizeof(info));
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArchiveFormatInfo info = {};
|
|
||||||
file.ReadBytes(&info, sizeof(info));
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, std::span<const u8> icon) {
|
|
||||||
std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path);
|
|
||||||
FileUtil::IOFile icon_file(game_path + "icon", "wb");
|
|
||||||
icon_file.WriteBytes(icon.data(), icon.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
||||||
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator)
|
SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator)
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
#include <boost/serialization/string.hpp>
|
#include <boost/serialization/string.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/file_sys/archive_backend.h"
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/artic_cache.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
|
@ -22,7 +25,7 @@ enum class ExtSaveDataType {
|
||||||
};
|
};
|
||||||
|
|
||||||
/// File system interface to the ExtSaveData archive
|
/// File system interface to the ExtSaveData archive
|
||||||
class ArchiveFactory_ExtSaveData final : public ArchiveFactory {
|
class ArchiveFactory_ExtSaveData final : public ArchiveFactory, public ArticCacheProvider {
|
||||||
public:
|
public:
|
||||||
ArchiveFactory_ExtSaveData(const std::string& mount_point, ExtSaveDataType type_);
|
ArchiveFactory_ExtSaveData(const std::string& mount_point, ExtSaveDataType type_);
|
||||||
|
|
||||||
|
@ -31,21 +34,34 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
|
||||||
u64 program_id) override;
|
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return IsUsingArtic();
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& GetMountPoint() const {
|
const std::string& GetMountPoint() const {
|
||||||
return mount_point;
|
return mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
* Writes the SMDH icon of the ExtSaveData to file
|
u32 directory_buckets, u32 file_buckets) override {
|
||||||
* @param path Path of this ExtSaveData
|
return UnimplementedFunction(ErrorModule::FS);
|
||||||
* @param icon_data Binary data of the icon
|
};
|
||||||
* @param icon_size Size of the icon data
|
|
||||||
*/
|
Result FormatAsExtData(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
void WriteIcon(const Path& path, std::span<const u8> icon);
|
u8 unknown, u64 program_id, u64 total_size, std::span<const u8> icon);
|
||||||
|
|
||||||
|
Result DeleteExtData(Service::FS::MediaType media_type, u8 unknown, u32 high, u32 low);
|
||||||
|
|
||||||
|
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
artic_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUsingArtic() const {
|
||||||
|
return artic_client.get() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Type of ext save data archive being accessed.
|
/// Type of ext save data archive being accessed.
|
||||||
|
@ -61,10 +77,13 @@ private:
|
||||||
/// Returns a path with the correct SaveIdHigh value for Shared extdata paths.
|
/// Returns a path with the correct SaveIdHigh value for Shared extdata paths.
|
||||||
Path GetCorrectedPath(const Path& path);
|
Path GetCorrectedPath(const Path& path);
|
||||||
|
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||||
|
|
||||||
ArchiveFactory_ExtSaveData() = default;
|
ArchiveFactory_ExtSaveData() = default;
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||||
|
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||||
ar& type;
|
ar& type;
|
||||||
ar& mount_point;
|
ar& mount_point;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/archive_artic.h"
|
||||||
#include "core/file_sys/archive_ncch.h"
|
#include "core/file_sys/archive_ncch.h"
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
#include "core/file_sys/ivfc_archive.h"
|
#include "core/file_sys/ivfc_archive.h"
|
||||||
|
@ -69,8 +70,9 @@ Path MakeNCCHFilePath(NCCHFileOpenType open_type, u32 content_index, NCCHFilePat
|
||||||
return FileSys::Path(std::move(file));
|
return FileSys::Path(std::move(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const {
|
u32 attributes) {
|
||||||
|
|
||||||
if (path.GetType() != LowPathType::Binary) {
|
if (path.GetType() != LowPathType::Binary) {
|
||||||
LOG_ERROR(Service_FS, "Path need to be Binary");
|
LOG_ERROR(Service_FS, "Path need to be Binary");
|
||||||
return ResultInvalidPath;
|
return ResultInvalidPath;
|
||||||
|
@ -207,14 +209,14 @@ Result NCCHArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result NCCHArchive::CreateFile(const Path& path, u64 size) const {
|
Result NCCHArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an NCCH archive ({}).", GetName());
|
LOG_CRITICAL(Service_FS, "Attempted to create a file in an NCCH archive ({}).", GetName());
|
||||||
// TODO: Verify error code
|
// TODO: Verify error code
|
||||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
ErrorLevel::Permanent);
|
ErrorLevel::Permanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result NCCHArchive::CreateDirectory(const Path& path) const {
|
Result NCCHArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an NCCH archive ({}).", GetName());
|
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an NCCH archive ({}).", GetName());
|
||||||
// TODO(wwylele): Use correct error code
|
// TODO(wwylele): Use correct error code
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
|
@ -226,7 +228,7 @@ Result NCCHArchive::RenameDirectory(const Path& src_path, const Path& dest_path)
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> NCCHArchive::OpenDirectory(const Path& path) const {
|
ResultVal<std::unique_ptr<DirectoryBackend>> NCCHArchive::OpenDirectory(const Path& path) {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to open a directory within an NCCH archive ({}).",
|
LOG_CRITICAL(Service_FS, "Attempted to open a directory within an NCCH archive ({}).",
|
||||||
GetName().c_str());
|
GetName().c_str());
|
||||||
// TODO(shinyquagsire23): Use correct error code
|
// TODO(shinyquagsire23): Use correct error code
|
||||||
|
@ -255,7 +257,7 @@ ResultVal<std::size_t> NCCHFile::Read(const u64 offset, const std::size_t length
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> NCCHFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
ResultVal<std::size_t> NCCHFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||||
const u8* buffer) {
|
const bool update_timestamp, const u8* buffer) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to write to NCCH file");
|
LOG_ERROR(Service_FS, "Attempted to write to NCCH file");
|
||||||
// TODO(shinyquagsire23): Find error code
|
// TODO(shinyquagsire23): Find error code
|
||||||
return 0ULL;
|
return 0ULL;
|
||||||
|
@ -274,6 +276,13 @@ ArchiveFactory_NCCH::ArchiveFactory_NCCH() {}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path,
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path,
|
||||||
u64 program_id) {
|
u64 program_id) {
|
||||||
|
|
||||||
|
if (IsUsingArtic()) {
|
||||||
|
EnsureCacheCreated();
|
||||||
|
return ArticArchive::Open(artic_client, Service::FS::ArchiveIdCode::NCCH, path,
|
||||||
|
Core::PerfStats::PerfArticEventBits::NONE, *this, false);
|
||||||
|
}
|
||||||
|
|
||||||
if (path.GetType() != LowPathType::Binary) {
|
if (path.GetType() != LowPathType::Binary) {
|
||||||
LOG_ERROR(Service_FS, "Path need to be Binary");
|
LOG_ERROR(Service_FS, "Path need to be Binary");
|
||||||
return ResultInvalidPath;
|
return ResultInvalidPath;
|
||||||
|
@ -293,7 +302,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path&
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_NCCH::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result ArchiveFactory_NCCH::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
|
LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
|
||||||
// TODO: Verify error code
|
// TODO: Verify error code
|
||||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
#include <boost/serialization/export.hpp>
|
#include <boost/serialization/export.hpp>
|
||||||
#include <boost/serialization/vector.hpp>
|
#include <boost/serialization/vector.hpp>
|
||||||
#include "core/file_sys/archive_backend.h"
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/artic_cache.h"
|
||||||
#include "core/file_sys/file_backend.h"
|
#include "core/file_sys/file_backend.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
namespace Service::FS {
|
namespace Service::FS {
|
||||||
enum class MediaType : u32;
|
enum class MediaType : u32;
|
||||||
|
@ -48,16 +50,16 @@ public:
|
||||||
return "NCCHArchive";
|
return "NCCHArchive";
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override;
|
u32 attributes) override;
|
||||||
Result DeleteFile(const Path& path) const override;
|
Result DeleteFile(const Path& path) const override;
|
||||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
Result DeleteDirectory(const Path& path) const override;
|
Result DeleteDirectory(const Path& path) const override;
|
||||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
Result CreateFile(const Path& path, u64 size) const override;
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||||
Result CreateDirectory(const Path& path) const override;
|
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
u64 GetFreeBytes() const override;
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -82,11 +84,11 @@ public:
|
||||||
explicit NCCHFile(std::vector<u8> buffer, std::unique_ptr<DelayGenerator> delay_generator_);
|
explicit NCCHFile(std::vector<u8> buffer, std::unique_ptr<DelayGenerator> delay_generator_);
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Flush() const override {}
|
void Flush() const override {}
|
||||||
|
@ -105,7 +107,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
/// File system interface to the NCCH archive
|
/// File system interface to the NCCH archive
|
||||||
class ArchiveFactory_NCCH final : public ArchiveFactory {
|
class ArchiveFactory_NCCH final : public ArchiveFactory, public ArticCacheProvider {
|
||||||
public:
|
public:
|
||||||
explicit ArchiveFactory_NCCH();
|
explicit ArchiveFactory_NCCH();
|
||||||
|
|
||||||
|
@ -114,14 +116,29 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return IsUsingArtic();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
artic_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUsingArtic() const {
|
||||||
|
return artic_client.get() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
ar& boost::serialization::base_object<ArchiveFactory>(*this);
|
||||||
|
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||||
}
|
}
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
|
@ -75,12 +75,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataPermitted
|
||||||
return ResultGamecardNotInserted;
|
return ResultGamecardNotInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd_savedata_source->Open(program_id);
|
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path,
|
||||||
|
program_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_OtherSaveDataPermitted::Format(const Path& path,
|
Result ArchiveFactory_OtherSaveDataPermitted::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets,
|
||||||
|
u32 file_buckets) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive.");
|
LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive.");
|
||||||
return ResultInvalidPath;
|
return ResultInvalidPath;
|
||||||
}
|
}
|
||||||
|
@ -96,7 +98,8 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataPermitted::GetFormatInf
|
||||||
return ResultGamecardNotInserted;
|
return ResultGamecardNotInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd_savedata_source->GetFormatInfo(program_id);
|
return sd_savedata_source->GetFormatInfo(
|
||||||
|
program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral(
|
ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral(
|
||||||
|
@ -114,12 +117,14 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataGeneral::
|
||||||
return ResultGamecardNotInserted;
|
return ResultGamecardNotInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd_savedata_source->Open(program_id);
|
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::OtherSaveDataGeneral, path,
|
||||||
|
program_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path,
|
Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 /*client_program_id*/) {
|
u64 /*client_program_id*/, u32 directory_buckets,
|
||||||
|
u32 file_buckets) {
|
||||||
MediaType media_type;
|
MediaType media_type;
|
||||||
u64 program_id;
|
u64 program_id;
|
||||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path));
|
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path));
|
||||||
|
@ -129,7 +134,9 @@ Result ArchiveFactory_OtherSaveDataGeneral::Format(const Path& path,
|
||||||
return ResultGamecardNotInserted;
|
return ResultGamecardNotInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd_savedata_source->Format(program_id, format_info);
|
return sd_savedata_source->Format(program_id, format_info,
|
||||||
|
Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path,
|
||||||
|
directory_buckets, file_buckets);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo(
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo(
|
||||||
|
@ -143,7 +150,8 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo(
|
||||||
return ResultGamecardNotInserted;
|
return ResultGamecardNotInserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd_savedata_source->GetFormatInfo(program_id);
|
return sd_savedata_source->GetFormatInfo(
|
||||||
|
program_id, Service::FS::ArchiveIdCode::OtherSaveDataPermitted, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
|
@ -22,10 +22,14 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return sd_savedata_source->IsUsingArtic();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
||||||
|
|
||||||
|
@ -49,8 +53,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -18,18 +18,20 @@ ArchiveFactory_SaveData::ArchiveFactory_SaveData(
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path,
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path,
|
||||||
u64 program_id) {
|
u64 program_id) {
|
||||||
return sd_savedata_source->Open(program_id);
|
return sd_savedata_source->Open(Service::FS::ArchiveIdCode::SaveData, path, program_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_SaveData::Format(const Path& path,
|
Result ArchiveFactory_SaveData::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||||
return sd_savedata_source->Format(program_id, format_info);
|
return sd_savedata_source->Format(program_id, format_info, Service::FS::ArchiveIdCode::SaveData,
|
||||||
|
path, directory_buckets, file_buckets);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path,
|
ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path,
|
||||||
u64 program_id) const {
|
u64 program_id) const {
|
||||||
return sd_savedata_source->GetFormatInfo(program_id);
|
return sd_savedata_source->GetFormatInfo(program_id, Service::FS::ArchiveIdCode::SaveData,
|
||||||
|
path);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
|
@ -20,11 +20,15 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
|
bool IsSlow() override {
|
||||||
|
return sd_savedata_source->IsUsingArtic();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,8 @@ public:
|
||||||
SERIALIZE_DELAY_GENERATOR
|
SERIALIZE_DELAY_GENERATOR
|
||||||
};
|
};
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const {
|
u32 attributes) {
|
||||||
Mode modified_mode;
|
Mode modified_mode;
|
||||||
modified_mode.hex = mode.hex;
|
modified_mode.hex = mode.hex;
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ Result SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const {
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
@ -267,7 +267,7 @@ Result SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
||||||
ErrorLevel::Info);
|
ErrorLevel::Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SDMCArchive::CreateDirectory(const Path& path) const {
|
Result SDMCArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
@ -331,7 +331,7 @@ Result SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path)
|
||||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const {
|
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) {
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
@ -392,7 +392,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path&
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_SDMC::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result ArchiveFactory_SDMC::Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||||
// This is kind of an undesirable operation, so let's just ignore it. :)
|
// This is kind of an undesirable operation, so let's just ignore it. :)
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,16 +27,16 @@ public:
|
||||||
return "SDMCArchive: " + mount_point;
|
return "SDMCArchive: " + mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override;
|
u32 attributes) override;
|
||||||
Result DeleteFile(const Path& path) const override;
|
Result DeleteFile(const Path& path) const override;
|
||||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
Result DeleteDirectory(const Path& path) const override;
|
Result DeleteDirectory(const Path& path) const override;
|
||||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
Result CreateFile(const Path& path, u64 size) const override;
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||||
Result CreateDirectory(const Path& path) const override;
|
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
u64 GetFreeBytes() const override;
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -68,8 +68,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -41,7 +41,8 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
|
||||||
const Mode& mode) const {
|
const Mode& mode,
|
||||||
|
u32 attributes) {
|
||||||
if (mode.read_flag) {
|
if (mode.read_flag) {
|
||||||
LOG_ERROR(Service_FS, "Read flag is not supported");
|
LOG_ERROR(Service_FS, "Read flag is not supported");
|
||||||
return ResultInvalidReadFlag;
|
return ResultInvalidReadFlag;
|
||||||
|
@ -49,8 +50,7 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Pat
|
||||||
return SDMCArchive::OpenFileBase(path, mode);
|
return SDMCArchive::OpenFileBase(path, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(
|
ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(const Path& path) {
|
||||||
const Path& path) const {
|
|
||||||
LOG_ERROR(Service_FS, "Not supported");
|
LOG_ERROR(Service_FS, "Not supported");
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(co
|
||||||
|
|
||||||
Result ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
|
Result ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets,
|
||||||
|
u32 file_buckets) {
|
||||||
// TODO(wwylele): hwtest this
|
// TODO(wwylele): hwtest this
|
||||||
LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
|
LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
|
|
|
@ -24,10 +24,10 @@ public:
|
||||||
return "SDMCWriteOnlyArchive: " + mount_point;
|
return "SDMCWriteOnlyArchive: " + mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override;
|
u32 attributes) override;
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDMCWriteOnlyArchive() = default;
|
SDMCWriteOnlyArchive() = default;
|
||||||
|
@ -54,8 +54,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -51,7 +51,7 @@ public:
|
||||||
return data->size();
|
return data->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override {
|
const u8* buffer) override {
|
||||||
LOG_ERROR(Service_FS, "The file is read-only!");
|
LOG_ERROR(Service_FS, "The file is read-only!");
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
|
@ -65,7 +65,7 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,8 @@ public:
|
||||||
return "SelfNCCHArchive";
|
return "SelfNCCHArchive";
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override {
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&,
|
||||||
|
u32 attributes) override {
|
||||||
// Note: SelfNCCHArchive doesn't check the open mode.
|
// Note: SelfNCCHArchive doesn't check the open mode.
|
||||||
|
|
||||||
if (path.GetType() != LowPathType::Binary) {
|
if (path.GetType() != LowPathType::Binary) {
|
||||||
|
@ -154,12 +155,12 @@ public:
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result CreateFile(const Path& path, u64 size) const override {
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override {
|
||||||
LOG_ERROR(Service_FS, "Unsupported");
|
LOG_ERROR(Service_FS, "Unsupported");
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result CreateDirectory(const Path& path) const override {
|
Result CreateDirectory(const Path& path, u32 attributes) const override {
|
||||||
LOG_ERROR(Service_FS, "Unsupported");
|
LOG_ERROR(Service_FS, "Unsupported");
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
@ -169,7 +170,7 @@ public:
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override {
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override {
|
||||||
LOG_ERROR(Service_FS, "Unsupported");
|
LOG_ERROR(Service_FS, "Unsupported");
|
||||||
return ResultUnsupportedOpenFlags;
|
return ResultUnsupportedOpenFlags;
|
||||||
}
|
}
|
||||||
|
@ -297,7 +298,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const P
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&,
|
Result ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets, u32 file_buckets) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive.");
|
LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive.");
|
||||||
return ResultInvalidPath;
|
return ResultInvalidPath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ public:
|
||||||
return "SelfNCCH";
|
return "SelfNCCH";
|
||||||
}
|
}
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/file_sys/archive_artic.h"
|
||||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
#include "core/file_sys/savedata_archive.h"
|
#include "core/file_sys/savedata_archive.h"
|
||||||
|
@ -40,49 +41,101 @@ ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_direc
|
||||||
LOG_DEBUG(Service_FS, "Directory {} set as SaveData.", mount_point);
|
LOG_DEBUG(Service_FS, "Directory {} set as SaveData.", mount_point);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) {
|
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(
|
||||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
Service::FS::ArchiveIdCode archive_id, const Path& path, u64 program_id) {
|
||||||
if (!FileUtil::Exists(concrete_mount_point)) {
|
if (IsUsingArtic()) {
|
||||||
// When a SaveData archive is created for the first time, it is not yet formatted and the
|
EnsureCacheCreated();
|
||||||
// save file/directory structure expected by the game has not yet been initialized.
|
return ArticArchive::Open(artic_client, archive_id, path,
|
||||||
// Returning the NotFormatted error code will signal the game to provision the SaveData
|
Core::PerfStats::PerfArticEventBits::ARTIC_SAVE_DATA, *this,
|
||||||
// archive with the files and folders that it expects.
|
archive_id != Service::FS::ArchiveIdCode::SaveData);
|
||||||
return ResultNotFormatted;
|
} else {
|
||||||
}
|
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
||||||
|
if (!FileUtil::Exists(concrete_mount_point)) {
|
||||||
|
// When a SaveData archive is created for the first time, it is not yet formatted and
|
||||||
|
// the save file/directory structure expected by the game has not yet been initialized.
|
||||||
|
// Returning the NotFormatted error code will signal the game to provision the SaveData
|
||||||
|
// archive with the files and folders that it expects.
|
||||||
|
return ResultNotFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
return std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
|
return std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveSource_SDSaveData::Format(u64 program_id,
|
Result ArchiveSource_SDSaveData::Format(u64 program_id,
|
||||||
const FileSys::ArchiveFormatInfo& format_info) {
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
Service::FS::ArchiveIdCode archive_id, const Path& path,
|
||||||
FileUtil::DeleteDirRecursively(concrete_mount_point);
|
u32 directory_buckets, u32 file_buckets) {
|
||||||
FileUtil::CreateFullPath(concrete_mount_point);
|
if (IsUsingArtic()) {
|
||||||
|
ClearAllCache();
|
||||||
|
auto req = artic_client->NewRequest("FSUSER_FormatSaveData");
|
||||||
|
|
||||||
// Write the format metadata
|
req.AddParameterS32(static_cast<u32>(archive_id));
|
||||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
auto artic_path = ArticArchive::BuildFSPath(path);
|
||||||
FileUtil::IOFile file(metadata_path, "wb");
|
req.AddParameterBuffer(artic_path.data(), artic_path.size());
|
||||||
|
req.AddParameterU32(format_info.total_size / 512);
|
||||||
|
req.AddParameterU32(format_info.number_directories);
|
||||||
|
req.AddParameterU32(format_info.number_files);
|
||||||
|
req.AddParameterU32(directory_buckets);
|
||||||
|
req.AddParameterU32(file_buckets);
|
||||||
|
req.AddParameterU8(format_info.duplicate_data);
|
||||||
|
|
||||||
if (file.IsOpen()) {
|
auto resp = artic_client->Send(req);
|
||||||
file.WriteBytes(&format_info, sizeof(format_info));
|
return ArticArchive::RespResult(resp);
|
||||||
|
} else {
|
||||||
|
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id);
|
||||||
|
FileUtil::DeleteDirRecursively(concrete_mount_point);
|
||||||
|
FileUtil::CreateFullPath(concrete_mount_point);
|
||||||
|
|
||||||
|
// Write the format metadata
|
||||||
|
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
||||||
|
FileUtil::IOFile file(metadata_path, "wb");
|
||||||
|
|
||||||
|
if (file.IsOpen()) {
|
||||||
|
file.WriteBytes(&format_info, sizeof(format_info));
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const {
|
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(
|
||||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
u64 program_id, Service::FS::ArchiveIdCode archive_id, const Path& path) const {
|
||||||
FileUtil::IOFile file(metadata_path, "rb");
|
if (IsUsingArtic()) {
|
||||||
|
auto req = artic_client->NewRequest("FSUSER_GetFormatInfo");
|
||||||
|
|
||||||
if (!file.IsOpen()) {
|
req.AddParameterS32(static_cast<u32>(archive_id));
|
||||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
auto path_artic = ArticArchive::BuildFSPath(path);
|
||||||
// TODO(Subv): Verify error code
|
req.AddParameterBuffer(path_artic.data(), path_artic.size());
|
||||||
return ResultNotFormatted;
|
|
||||||
|
auto resp = artic_client->Send(req);
|
||||||
|
Result res = ArticArchive::RespResult(resp);
|
||||||
|
if (R_FAILED(res)) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto info_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!info_buf.has_value() || info_buf->second != sizeof(ArchiveFormatInfo)) {
|
||||||
|
return ResultUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveFormatInfo info;
|
||||||
|
memcpy(&info, info_buf->first, sizeof(info));
|
||||||
|
return info;
|
||||||
|
} else {
|
||||||
|
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id);
|
||||||
|
FileUtil::IOFile file(metadata_path, "rb");
|
||||||
|
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Service_FS, "Could not open metadata information for archive");
|
||||||
|
// TODO(Subv): Verify error code
|
||||||
|
return ResultNotFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArchiveFormatInfo info = {};
|
||||||
|
file.ReadBytes(&info, sizeof(info));
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArchiveFormatInfo info = {};
|
|
||||||
file.ReadBytes(&info, sizeof(info));
|
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point,
|
std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point,
|
||||||
|
|
|
@ -9,27 +9,48 @@
|
||||||
#include <boost/serialization/export.hpp>
|
#include <boost/serialization/export.hpp>
|
||||||
#include <boost/serialization/string.hpp>
|
#include <boost/serialization/string.hpp>
|
||||||
#include "core/file_sys/archive_backend.h"
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/artic_cache.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace Service::FS {
|
||||||
|
enum class ArchiveIdCode : u32;
|
||||||
|
} // namespace Service::FS
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
/// A common source of SD save data archive
|
/// A common source of SD save data archive
|
||||||
class ArchiveSource_SDSaveData {
|
class ArchiveSource_SDSaveData : public ArticCacheProvider {
|
||||||
public:
|
public:
|
||||||
explicit ArchiveSource_SDSaveData(const std::string& mount_point);
|
explicit ArchiveSource_SDSaveData(const std::string& mount_point);
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(u64 program_id);
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(Service::FS::ArchiveIdCode archive_id,
|
||||||
Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info);
|
const Path& path, u64 program_id);
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const;
|
Result Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
|
Service::FS::ArchiveIdCode archive_id, const Path& path, u32 directory_buckets,
|
||||||
|
u32 file_buckets);
|
||||||
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id,
|
||||||
|
Service::FS::ArchiveIdCode archive_id,
|
||||||
|
const Path& path) const;
|
||||||
|
|
||||||
static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id);
|
static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id);
|
||||||
|
|
||||||
|
void RegisterArtic(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
artic_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsUsingArtic() const {
|
||||||
|
return artic_client.get() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string mount_point;
|
std::string mount_point;
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> artic_client = nullptr;
|
||||||
|
|
||||||
ArchiveSource_SDSaveData() = default;
|
ArchiveSource_SDSaveData() = default;
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<ArticCacheProvider>(*this);
|
||||||
ar& mount_point;
|
ar& mount_point;
|
||||||
}
|
}
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
|
|
|
@ -64,7 +64,8 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
|
||||||
|
|
||||||
Result ArchiveFactory_SystemSaveData::Format(const Path& path,
|
Result ArchiveFactory_SystemSaveData::Format(const Path& path,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u32 directory_buckets,
|
||||||
|
u32 file_buckets) {
|
||||||
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
std::string fullpath = GetSystemSaveDataPath(base_path, path);
|
||||||
FileUtil::DeleteDirRecursively(fullpath);
|
FileUtil::DeleteDirRecursively(fullpath);
|
||||||
FileUtil::CreateFullPath(fullpath);
|
FileUtil::CreateFullPath(fullpath);
|
||||||
|
|
|
@ -20,8 +20,8 @@ public:
|
||||||
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point);
|
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point);
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path, u64 program_id) override;
|
||||||
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
|
Result Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
u64 program_id) override;
|
u32 directory_buckets, u32 file_buckets) override;
|
||||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
|
||||||
|
|
||||||
std::string GetName() const override {
|
std::string GetName() const override {
|
||||||
|
|
235
src/core/file_sys/artic_cache.cpp
Normal file
235
src/core/file_sys/artic_cache.cpp
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "artic_cache.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
ResultVal<std::size_t> ArticCache::Read(s32 file_handle, std::size_t offset, std::size_t length,
|
||||||
|
u8* buffer) {
|
||||||
|
if (length == 0)
|
||||||
|
return size_t();
|
||||||
|
|
||||||
|
const auto segments = BreakupRead(offset, length);
|
||||||
|
std::size_t read_progress = 0;
|
||||||
|
|
||||||
|
// Skip cache if the read is too big
|
||||||
|
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||||
|
if (segments[0].second < big_cache_skip) {
|
||||||
|
std::unique_lock big_read_guard(big_cache_mutex);
|
||||||
|
auto big_cache_entry = big_cache.request(std::make_pair(offset, length));
|
||||||
|
if (!big_cache_entry.first) {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache BMISS: offset={}, length={}", offset, length);
|
||||||
|
big_cache_entry.second.clear();
|
||||||
|
big_cache_entry.second.resize(length);
|
||||||
|
auto res =
|
||||||
|
ReadFromArtic(file_handle, reinterpret_cast<u8*>(big_cache_entry.second.data()),
|
||||||
|
length, offset);
|
||||||
|
if (res.Failed())
|
||||||
|
return res;
|
||||||
|
length = res.Unwrap();
|
||||||
|
} else {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache BHIT: offset={}, length={}", offset, length);
|
||||||
|
}
|
||||||
|
memcpy(buffer, big_cache_entry.second.data(), length);
|
||||||
|
} else {
|
||||||
|
if (segments[0].second < very_big_cache_skip) {
|
||||||
|
std::unique_lock very_big_read_guard(very_big_cache_mutex);
|
||||||
|
auto very_big_cache_entry = very_big_cache.request(std::make_pair(offset, length));
|
||||||
|
if (!very_big_cache_entry.first) {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache VBMISS: offset={}, length={}", offset,
|
||||||
|
length);
|
||||||
|
very_big_cache_entry.second.clear();
|
||||||
|
very_big_cache_entry.second.resize(length);
|
||||||
|
auto res = ReadFromArtic(
|
||||||
|
file_handle, reinterpret_cast<u8*>(very_big_cache_entry.second.data()),
|
||||||
|
length, offset);
|
||||||
|
if (res.Failed())
|
||||||
|
return res;
|
||||||
|
length = res.Unwrap();
|
||||||
|
} else {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache VBHIT: offset={}, length={}", offset, length);
|
||||||
|
}
|
||||||
|
memcpy(buffer, very_big_cache_entry.second.data(), length);
|
||||||
|
} else {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache SKIP: offset={}, length={}", offset, length);
|
||||||
|
|
||||||
|
auto res = ReadFromArtic(file_handle, buffer, length, offset);
|
||||||
|
if (res.Failed())
|
||||||
|
return res;
|
||||||
|
length = res.Unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
||||||
|
std::unique_lock read_guard(cache_mutex);
|
||||||
|
for (const auto& seg : segments) {
|
||||||
|
std::size_t read_size = cache_line_size;
|
||||||
|
std::size_t page = OffsetToPage(seg.first);
|
||||||
|
// Check if segment is in cache
|
||||||
|
auto cache_entry = cache.request(page);
|
||||||
|
if (!cache_entry.first) {
|
||||||
|
// If not found, read from artic and cache the data
|
||||||
|
auto res = ReadFromArtic(file_handle, cache_entry.second.data(), read_size, page);
|
||||||
|
if (res.Failed())
|
||||||
|
return res;
|
||||||
|
read_size = res.Unwrap();
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache MISS: page={}, length={}, into={}", page, seg.second,
|
||||||
|
(seg.first - page));
|
||||||
|
} else {
|
||||||
|
LOG_TRACE(Service_FS, "ArticCache HIT: page={}, length={}, into={}", page, seg.second,
|
||||||
|
(seg.first - page));
|
||||||
|
}
|
||||||
|
std::size_t copy_amount =
|
||||||
|
(read_size > (seg.first - page))
|
||||||
|
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
||||||
|
: 0;
|
||||||
|
std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page),
|
||||||
|
copy_amount);
|
||||||
|
read_progress += copy_amount;
|
||||||
|
}
|
||||||
|
return read_progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticCache::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||||
|
auto segments = BreakupRead(file_offset, length);
|
||||||
|
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
std::shared_lock read_guard(cache_mutex);
|
||||||
|
for (auto it = segments.begin(); it != segments.end(); it++) {
|
||||||
|
if (!cache.contains(OffsetToPage(it->first)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticCache::Clear() {
|
||||||
|
std::unique_lock l1(cache_mutex), l2(big_cache_mutex), l3(very_big_cache_mutex);
|
||||||
|
cache.clear();
|
||||||
|
big_cache.clear();
|
||||||
|
very_big_cache.clear();
|
||||||
|
data_size = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<size_t> ArticCache::Write(s32 file_handle, std::size_t offset, std::size_t length,
|
||||||
|
const u8* buffer, u32 flags) {
|
||||||
|
// Can probably do better, but write operations are usually done at the end, so it doesn't
|
||||||
|
// matter much
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
size_t written_amount = 0;
|
||||||
|
while (written_amount != length) {
|
||||||
|
size_t to_write =
|
||||||
|
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, length - written_amount);
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSFILE_Write");
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
req.AddParameterS64(static_cast<s64>(offset + written_amount));
|
||||||
|
req.AddParameterS32(static_cast<s32>(to_write));
|
||||||
|
req.AddParameterS32(static_cast<s32>(flags));
|
||||||
|
req.AddParameterBuffer(buffer + written_amount, to_write);
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded())
|
||||||
|
return Result(-1);
|
||||||
|
|
||||||
|
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto actually_written_opt = resp->GetResponseS32(0);
|
||||||
|
if (!actually_written_opt.has_value())
|
||||||
|
return Result(-1);
|
||||||
|
|
||||||
|
size_t actually_written = static_cast<size_t>(actually_written_opt.value());
|
||||||
|
|
||||||
|
written_amount += actually_written;
|
||||||
|
if (actually_written != to_write)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return written_amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<size_t> ArticCache::GetSize(s32 file_handle) {
|
||||||
|
std::unique_lock l1(cache_mutex);
|
||||||
|
|
||||||
|
if (data_size.has_value())
|
||||||
|
return data_size.value();
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSFILE_GetSize");
|
||||||
|
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded())
|
||||||
|
return Result(-1);
|
||||||
|
|
||||||
|
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto size_buf = resp->GetResponseS64(0);
|
||||||
|
if (!size_buf) {
|
||||||
|
return Result(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_size = static_cast<size_t>(*size_buf);
|
||||||
|
return data_size.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<size_t> ArticCache::ReadFromArtic(s32 file_handle, u8* buffer, size_t len,
|
||||||
|
size_t offset) {
|
||||||
|
size_t read_amount = 0;
|
||||||
|
while (read_amount != len) {
|
||||||
|
size_t to_read =
|
||||||
|
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, len - read_amount);
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSFILE_Read");
|
||||||
|
req.AddParameterS32(file_handle);
|
||||||
|
req.AddParameterS64(static_cast<s64>(offset + read_amount));
|
||||||
|
req.AddParameterS32(static_cast<s32>(to_read));
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded())
|
||||||
|
return Result(-1);
|
||||||
|
|
||||||
|
auto res = Result(static_cast<u32>(resp->GetMethodResult()));
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto read_buff = resp->GetResponseBuffer(0);
|
||||||
|
if (!read_buff.has_value())
|
||||||
|
return Result(-1);
|
||||||
|
size_t actually_read = read_buff->second;
|
||||||
|
|
||||||
|
memcpy(buffer + read_amount, read_buff->first, actually_read);
|
||||||
|
read_amount += actually_read;
|
||||||
|
if (actually_read != to_read)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return read_amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>> ArticCache::BreakupRead(std::size_t offset,
|
||||||
|
std::size_t length) {
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>> ret;
|
||||||
|
|
||||||
|
// Reads bigger than the cache line size will probably never hit again
|
||||||
|
if (length > max_breakup_size) {
|
||||||
|
ret.push_back(std::make_pair(offset, length));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t curr_offset = offset;
|
||||||
|
while (length) {
|
||||||
|
std::size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
||||||
|
std::size_t curr_page_len = std::min(length, next_page - curr_offset);
|
||||||
|
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
||||||
|
curr_offset = next_page;
|
||||||
|
length -= curr_page_len;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
} // namespace FileSys
|
154
src/core/file_sys/artic_cache.h
Normal file
154
src/core/file_sys/artic_cache.h
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include "vector"
|
||||||
|
|
||||||
|
#include <boost/serialization/array.hpp>
|
||||||
|
#include <boost/serialization/base_object.hpp>
|
||||||
|
#include <boost/serialization/export.hpp>
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/static_lru_cache.h"
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
class ArticCache {
|
||||||
|
public:
|
||||||
|
ArticCache() = default;
|
||||||
|
|
||||||
|
ArticCache(const std::shared_ptr<Network::ArticBase::Client>& cli) : client(cli) {}
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Read(s32 file_handle, std::size_t offset, std::size_t length,
|
||||||
|
u8* buffer);
|
||||||
|
|
||||||
|
bool CacheReady(std::size_t file_offset, std::size_t length);
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
|
||||||
|
ResultVal<std::size_t> Write(s32 file_handle, std::size_t offset, std::size_t length,
|
||||||
|
const u8* buffer, u32 flags);
|
||||||
|
|
||||||
|
ResultVal<size_t> GetSize(s32 file_handle);
|
||||||
|
|
||||||
|
void ForceSetSize(const std::optional<size_t>& size) {
|
||||||
|
data_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
std::optional<size_t> data_size;
|
||||||
|
|
||||||
|
// Total cache size: 32MB small, 512MB big (worst case), 160MB very big (worst case).
|
||||||
|
// The worst case values are unrealistic, they will never happen in any real game.
|
||||||
|
static constexpr std::size_t cache_line_size = 4 * 1024;
|
||||||
|
static constexpr std::size_t cache_line_count = 256;
|
||||||
|
static constexpr std::size_t max_breakup_size = 8 * 1024;
|
||||||
|
|
||||||
|
static constexpr std::size_t big_cache_skip = 1 * 1024 * 1024;
|
||||||
|
static constexpr std::size_t big_cache_lines = 1024;
|
||||||
|
|
||||||
|
static constexpr std::size_t very_big_cache_skip = 10 * 1024 * 1024;
|
||||||
|
static constexpr std::size_t very_big_cache_lines = 24;
|
||||||
|
|
||||||
|
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
||||||
|
std::shared_mutex cache_mutex;
|
||||||
|
|
||||||
|
struct NoInitChar {
|
||||||
|
u8 value;
|
||||||
|
NoInitChar() noexcept {
|
||||||
|
// do nothing
|
||||||
|
static_assert(sizeof *this == sizeof value, "invalid size");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>,
|
||||||
|
big_cache_lines>
|
||||||
|
big_cache;
|
||||||
|
std::shared_mutex big_cache_mutex;
|
||||||
|
Common::StaticLRUCache<std::pair<std::size_t, std::size_t>, std::vector<NoInitChar>,
|
||||||
|
very_big_cache_lines>
|
||||||
|
very_big_cache;
|
||||||
|
std::shared_mutex very_big_cache_mutex;
|
||||||
|
|
||||||
|
ResultVal<std::size_t> ReadFromArtic(s32 file_handle, u8* buffer, size_t len, size_t offset);
|
||||||
|
|
||||||
|
std::size_t OffsetToPage(std::size_t offset) {
|
||||||
|
return Common::AlignDown<std::size_t>(offset, cache_line_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset,
|
||||||
|
std::size_t length);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArticCacheProvider {
|
||||||
|
public:
|
||||||
|
virtual ~ArticCacheProvider() {}
|
||||||
|
|
||||||
|
std::vector<u8> PathsToVector(const Path& archive_path, const Path& file_path) {
|
||||||
|
auto archive_path_binary = archive_path.AsBinary();
|
||||||
|
auto file_path_binary = file_path.AsBinary();
|
||||||
|
|
||||||
|
std::vector<u8> ret;
|
||||||
|
ret.push_back(static_cast<u8>(file_path.GetType()));
|
||||||
|
ret.insert(ret.end(), archive_path_binary.begin(), archive_path_binary.end());
|
||||||
|
ret.push_back(static_cast<u8>(archive_path.GetType()));
|
||||||
|
ret.insert(ret.end(), file_path_binary.begin(), file_path_binary.end());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::shared_ptr<ArticCache> ProvideCache(
|
||||||
|
const std::shared_ptr<Network::ArticBase::Client>& cli, const std::vector<u8>& path,
|
||||||
|
bool create) {
|
||||||
|
if (file_caches == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
auto it = file_caches->find(path);
|
||||||
|
if (it == file_caches->end()) {
|
||||||
|
if (!create) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto res = std::make_shared<ArticCache>(cli);
|
||||||
|
file_caches->insert({path, res});
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void ClearAllCache() {
|
||||||
|
if (file_caches != nullptr) {
|
||||||
|
file_caches->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void EnsureCacheCreated() {
|
||||||
|
if (file_caches == nullptr) {
|
||||||
|
file_caches =
|
||||||
|
std::make_unique<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<std::map<std::vector<u8>, std::shared_ptr<ArticCache>>> file_caches = nullptr;
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace FileSys
|
||||||
|
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticCache)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticCacheProvider)
|
|
@ -49,7 +49,11 @@ public:
|
||||||
* Close the directory
|
* Close the directory
|
||||||
* @return true if the directory closed correctly
|
* @return true if the directory closed correctly
|
||||||
*/
|
*/
|
||||||
virtual bool Close() const = 0;
|
virtual bool Close() = 0;
|
||||||
|
|
||||||
|
virtual bool IsSlow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
|
|
|
@ -26,7 +26,7 @@ ResultVal<std::size_t> DiskFile::Read(const u64 offset, const std::size_t length
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> DiskFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
ResultVal<std::size_t> DiskFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||||
const u8* buffer) {
|
const bool update_timestamp, const u8* buffer) {
|
||||||
if (!mode.write_flag)
|
if (!mode.write_flag)
|
||||||
return ResultInvalidOpenFlags;
|
return ResultInvalidOpenFlags;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ bool DiskFile::SetSize(const u64 size) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DiskFile::Close() const {
|
bool DiskFile::Close() {
|
||||||
return file->Close();
|
return file->Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,11 +30,11 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override;
|
bool Close() override;
|
||||||
|
|
||||||
void Flush() const override {
|
void Flush() const override {
|
||||||
file->Flush();
|
file->Flush();
|
||||||
|
@ -66,7 +66,7 @@ public:
|
||||||
|
|
||||||
u32 Read(u32 count, Entry* entries) override;
|
u32 Read(u32 count, Entry* entries) override;
|
||||||
|
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
* @return Number of bytes written, or error code
|
* @return Number of bytes written, or error code
|
||||||
*/
|
*/
|
||||||
virtual ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
virtual ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
||||||
const u8* buffer) = 0;
|
bool update_timestamp, const u8* buffer) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the amount of time a 3ds needs to read those data
|
* Get the amount of time a 3ds needs to read those data
|
||||||
|
@ -79,7 +79,7 @@ public:
|
||||||
* Close the file
|
* Close the file
|
||||||
* @return true if the file closed correctly
|
* @return true if the file closed correctly
|
||||||
*/
|
*/
|
||||||
virtual bool Close() const = 0;
|
virtual bool Close() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes the file
|
* Flushes the file
|
||||||
|
|
|
@ -28,8 +28,8 @@ std::string IVFCArchive::GetName() const {
|
||||||
return "IVFC";
|
return "IVFC";
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const {
|
u32 attributes) {
|
||||||
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<IVFCDelayGenerator>();
|
std::unique_ptr<DelayGenerator> delay_generator = std::make_unique<IVFCDelayGenerator>();
|
||||||
return std::make_unique<IVFCFile>(romfs_file, std::move(delay_generator));
|
return std::make_unique<IVFCFile>(romfs_file, std::move(delay_generator));
|
||||||
}
|
}
|
||||||
|
@ -61,14 +61,14 @@ Result IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IVFCArchive::CreateFile(const Path& path, u64 size) const {
|
Result IVFCArchive::CreateFile(const Path& path, u64 size, u32 attributes) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive ({}).", GetName());
|
LOG_CRITICAL(Service_FS, "Attempted to create a file in an IVFC archive ({}).", GetName());
|
||||||
// TODO: Verify error code
|
// TODO: Verify error code
|
||||||
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
return Result(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
|
||||||
ErrorLevel::Permanent);
|
ErrorLevel::Permanent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result IVFCArchive::CreateDirectory(const Path& path) const {
|
Result IVFCArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||||
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive ({}).", GetName());
|
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive ({}).", GetName());
|
||||||
// TODO(wwylele): Use correct error code
|
// TODO(wwylele): Use correct error code
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
|
@ -80,7 +80,7 @@ Result IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path)
|
||||||
return ResultUnknown;
|
return ResultUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
|
ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) {
|
||||||
return std::make_unique<IVFCDirectory>();
|
return std::make_unique<IVFCDirectory>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ ResultVal<std::size_t> IVFCFile::Read(const u64 offset, const std::size_t length
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> IVFCFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
ResultVal<std::size_t> IVFCFile::Write(const u64 offset, const std::size_t length, const bool flush,
|
||||||
const u8* buffer) {
|
const bool update_timestamp, const u8* buffer) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
||||||
// TODO(Subv): Find error code
|
// TODO(Subv): Find error code
|
||||||
return 0ULL;
|
return 0ULL;
|
||||||
|
@ -133,7 +133,8 @@ ResultVal<std::size_t> IVFCFileInMemory::Read(const u64 offset, const std::size_
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> IVFCFileInMemory::Write(const u64 offset, const std::size_t length,
|
ResultVal<std::size_t> IVFCFileInMemory::Write(const u64 offset, const std::size_t length,
|
||||||
const bool flush, const u8* buffer) {
|
const bool flush, const bool update_timestamp,
|
||||||
|
const u8* buffer) {
|
||||||
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
LOG_ERROR(Service_FS, "Attempted to write to IVFC file");
|
||||||
// TODO(Subv): Find error code
|
// TODO(Subv): Find error code
|
||||||
return 0ULL;
|
return 0ULL;
|
||||||
|
|
|
@ -101,16 +101,16 @@ public:
|
||||||
|
|
||||||
std::string GetName() const override;
|
std::string GetName() const override;
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override;
|
u32 attributes) override;
|
||||||
Result DeleteFile(const Path& path) const override;
|
Result DeleteFile(const Path& path) const override;
|
||||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
Result DeleteDirectory(const Path& path) const override;
|
Result DeleteDirectory(const Path& path) const override;
|
||||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
Result CreateFile(const Path& path, u64 size) const override;
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||||
Result CreateDirectory(const Path& path) const override;
|
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
u64 GetFreeBytes() const override;
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -122,11 +122,11 @@ public:
|
||||||
IVFCFile(std::shared_ptr<RomFSReader> file, std::unique_ptr<DelayGenerator> delay_generator_);
|
IVFCFile(std::shared_ptr<RomFSReader> file, std::unique_ptr<DelayGenerator> delay_generator_);
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Flush() const override {}
|
void Flush() const override {}
|
||||||
|
@ -157,7 +157,7 @@ public:
|
||||||
u32 Read(const u32 count, Entry* entries) override {
|
u32 Read(const u32 count, Entry* entries) override {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -168,11 +168,11 @@ public:
|
||||||
std::unique_ptr<DelayGenerator> delay_generator_);
|
std::unique_ptr<DelayGenerator> delay_generator_);
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Flush() const override {}
|
void Flush() const override {}
|
||||||
|
|
|
@ -4,7 +4,11 @@
|
||||||
#include <cryptopp/modes.h>
|
#include <cryptopp/modes.h>
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/file_sys/archive_artic.h"
|
||||||
|
#include "core/file_sys/archive_backend.h"
|
||||||
#include "core/file_sys/romfs_reader.h"
|
#include "core/file_sys/romfs_reader.h"
|
||||||
|
#include "core/hle/service/fs/fs_user.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
|
||||||
SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
||||||
|
|
||||||
|
@ -109,4 +113,102 @@ std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead(
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArticRomFSReader::ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli,
|
||||||
|
bool is_update_romfs)
|
||||||
|
: client(cli), cache(cli) {
|
||||||
|
auto req = client->NewRequest("FSUSER_OpenFileDirectly");
|
||||||
|
|
||||||
|
FileSys::Path archive(FileSys::LowPathType::Empty, {});
|
||||||
|
std::vector<u8> fileVec(0xC);
|
||||||
|
fileVec[0] = static_cast<u8>(is_update_romfs ? 5 : 0);
|
||||||
|
FileSys::Path file(FileSys::LowPathType::Binary, fileVec);
|
||||||
|
|
||||||
|
req.AddParameterS32(static_cast<s32>(Service::FS::ArchiveIdCode::SelfNCCH));
|
||||||
|
|
||||||
|
auto archive_buf = ArticArchive::BuildFSPath(archive);
|
||||||
|
req.AddParameterBuffer(archive_buf.data(), archive_buf.size());
|
||||||
|
auto file_buf = ArticArchive::BuildFSPath(file);
|
||||||
|
req.AddParameterBuffer(file_buf.data(), file_buf.size());
|
||||||
|
|
||||||
|
req.AddParameterS32(1);
|
||||||
|
req.AddParameterS32(0);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
|
||||||
|
if (!resp.has_value() || !resp->Succeeded()) {
|
||||||
|
load_status = Loader::ResultStatus::Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resp->GetMethodResult() != 0) {
|
||||||
|
load_status = Loader::ResultStatus::ErrorNotUsed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handle_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!handle_buf.has_value() || handle_buf->second != sizeof(s32)) {
|
||||||
|
load_status = Loader::ResultStatus::Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
romfs_handle = *reinterpret_cast<s32*>(handle_buf->first);
|
||||||
|
|
||||||
|
req = client->NewRequest("FSFILE_GetSize");
|
||||||
|
|
||||||
|
req.AddParameterS32(romfs_handle);
|
||||||
|
|
||||||
|
resp = client->Send(req);
|
||||||
|
|
||||||
|
if (!resp.has_value() || !resp->Succeeded()) {
|
||||||
|
load_status = Loader::ResultStatus::Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resp->GetMethodResult() != 0) {
|
||||||
|
load_status = Loader::ResultStatus::ErrorNotUsed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto size_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!size_buf.has_value() || size_buf->second != sizeof(u64)) {
|
||||||
|
load_status = Loader::ResultStatus::Error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_size = static_cast<size_t>(*reinterpret_cast<u64*>(size_buf->first));
|
||||||
|
load_status = Loader::ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticRomFSReader::~ArticRomFSReader() {
|
||||||
|
if (romfs_handle != -1) {
|
||||||
|
auto req = client->NewRequest("FSFILE_Close");
|
||||||
|
req.AddParameterS32(romfs_handle);
|
||||||
|
client->Send(req);
|
||||||
|
romfs_handle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ArticRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||||
|
length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||||
|
auto res = cache.Read(romfs_handle, offset, length, buffer);
|
||||||
|
if (res.Failed())
|
||||||
|
return 0;
|
||||||
|
return res.Unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticRomFSReader::AllowsCachedReads() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArticRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) {
|
||||||
|
return cache.CacheReady(file_offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArticRomFSReader::CloseFile() {
|
||||||
|
if (romfs_handle != -1) {
|
||||||
|
auto req = client->NewRequest("FSFILE_Close");
|
||||||
|
req.AddParameterS32(romfs_handle);
|
||||||
|
client->Send(req);
|
||||||
|
romfs_handle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/static_lru_cache.h"
|
#include "common/static_lru_cache.h"
|
||||||
|
#include "core/file_sys/artic_cache.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
enum class ResultStatus;
|
||||||
|
}
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
|
@ -97,6 +103,53 @@ private:
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A RomFS reader that reads from an artic base server.
|
||||||
|
*/
|
||||||
|
class ArticRomFSReader : public RomFSReader {
|
||||||
|
public:
|
||||||
|
ArticRomFSReader() = default;
|
||||||
|
ArticRomFSReader(std::shared_ptr<Network::ArticBase::Client>& cli, bool is_update_romfs);
|
||||||
|
|
||||||
|
~ArticRomFSReader() override;
|
||||||
|
|
||||||
|
std::size_t GetSize() const override {
|
||||||
|
return data_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||||
|
|
||||||
|
bool AllowsCachedReads() const override;
|
||||||
|
|
||||||
|
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
||||||
|
|
||||||
|
Loader::ResultStatus OpenStatus() {
|
||||||
|
return load_status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearCache() {
|
||||||
|
cache.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseFile();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
size_t data_size = 0;
|
||||||
|
s32 romfs_handle = -1;
|
||||||
|
Loader::ResultStatus load_status;
|
||||||
|
|
||||||
|
ArticCache cache;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||||
|
ar& data_size;
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
|
||||||
BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader)
|
BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader)
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticRomFSReader)
|
||||||
|
|
|
@ -36,7 +36,8 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
|
||||||
const Mode& mode) const {
|
const Mode& mode,
|
||||||
|
u32 attributes) {
|
||||||
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
LOG_DEBUG(Service_FS, "called path={} mode={:01X}", path.DebugStr(), mode.hex);
|
||||||
|
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
@ -203,7 +204,7 @@ Result SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
|
||||||
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size, u32 attributes) const {
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
@ -253,7 +254,7 @@ Result SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
|
||||||
ErrorLevel::Info);
|
ErrorLevel::Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result SaveDataArchive::CreateDirectory(const Path& path) const {
|
Result SaveDataArchive::CreateDirectory(const Path& path, u32 attributes) const {
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
@ -319,8 +320,7 @@ Result SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_p
|
||||||
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
ErrorSummary::NothingHappened, ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
|
ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(const Path& path) {
|
||||||
const Path& path) const {
|
|
||||||
const PathParser path_parser(path);
|
const PathParser path_parser(path);
|
||||||
|
|
||||||
if (!path_parser.IsValid()) {
|
if (!path_parser.IsValid()) {
|
||||||
|
|
|
@ -22,16 +22,16 @@ public:
|
||||||
return "SaveDataArchive: " + mount_point;
|
return "SaveDataArchive: " + mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
|
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode& mode,
|
||||||
const Mode& mode) const override;
|
u32 attributes) override;
|
||||||
Result DeleteFile(const Path& path) const override;
|
Result DeleteFile(const Path& path) const override;
|
||||||
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
Result RenameFile(const Path& src_path, const Path& dest_path) const override;
|
||||||
Result DeleteDirectory(const Path& path) const override;
|
Result DeleteDirectory(const Path& path) const override;
|
||||||
Result DeleteDirectoryRecursively(const Path& path) const override;
|
Result DeleteDirectoryRecursively(const Path& path) const override;
|
||||||
Result CreateFile(const Path& path, u64 size) const override;
|
Result CreateFile(const Path& path, u64 size, u32 attributes) const override;
|
||||||
Result CreateDirectory(const Path& path) const override;
|
Result CreateDirectory(const Path& path, u32 attributes) const override;
|
||||||
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
Result RenameDirectory(const Path& src_path, const Path& dest_path) const override;
|
||||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
|
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) override;
|
||||||
u64 GetFreeBytes() const override;
|
u64 GetFreeBytes() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
74
src/core/file_sys/secure_value_backend.cpp
Normal file
74
src/core/file_sys/secure_value_backend.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/archives.h"
|
||||||
|
#include "secure_value_backend.h"
|
||||||
|
|
||||||
|
SERIALIZE_EXPORT_IMPL(FileSys::DefaultSecureValueBackend)
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
|
||||||
|
Result DefaultSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation,
|
||||||
|
u32 secure_value_slot,
|
||||||
|
u64 secure_value) {
|
||||||
|
|
||||||
|
// TODO: Generate and Save the Secure Value
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS,
|
||||||
|
"(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X} "
|
||||||
|
"unqiue_id=0x{:08X} title_variation=0x{:02X}",
|
||||||
|
secure_value, secure_value_slot, unique_id, title_variation);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, u64>> DefaultSecureValueBackend::ObsoletedGetSaveDataSecureValue(
|
||||||
|
u32 unique_id, u8 title_variation, u32 secure_value_slot) {
|
||||||
|
|
||||||
|
// TODO: Implement Secure Value Lookup & Generation
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS,
|
||||||
|
"(STUBBED) called, secure_value_slot=0x{:08X} "
|
||||||
|
"unqiue_id=0x{:08X} title_variation=0x{:02X}",
|
||||||
|
secure_value_slot, unique_id, title_variation);
|
||||||
|
|
||||||
|
return std::make_tuple<bool, u64>(false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DefaultSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size,
|
||||||
|
u8* output, size_t output_size) {
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS,
|
||||||
|
"(STUBBED) called, action=0x{:08X} "
|
||||||
|
"input_size=0x{:016X} output_size=0x{:016X}",
|
||||||
|
action, input_size, output_size);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result DefaultSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot,
|
||||||
|
u64 secure_value) {
|
||||||
|
// TODO: Generate and Save the Secure Value
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS, "(STUBBED) called, secure_value=0x{:016x} secure_value_slot=0x{:08X}",
|
||||||
|
secure_value, secure_value_slot);
|
||||||
|
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> DefaultSecureValueBackend::GetThisSaveDataSecureValue(
|
||||||
|
u32 secure_value_slot) {
|
||||||
|
|
||||||
|
// TODO: Implement Secure Value Lookup & Generation
|
||||||
|
|
||||||
|
LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot);
|
||||||
|
|
||||||
|
return std::make_tuple<bool, bool, u64>(false, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void FileSys::DefaultSecureValueBackend::serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<SecureValueBackend>(*this);
|
||||||
|
}
|
||||||
|
} // namespace FileSys
|
65
src/core/file_sys/secure_value_backend.h
Normal file
65
src/core/file_sys/secure_value_backend.h
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tuple"
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
class SecureValueBackend : NonCopyable {
|
||||||
|
public:
|
||||||
|
virtual ~SecureValueBackend(){};
|
||||||
|
|
||||||
|
virtual Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation,
|
||||||
|
u32 secure_value_slot, u64 secure_value) = 0;
|
||||||
|
|
||||||
|
virtual ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue(
|
||||||
|
u32 unique_id, u8 title_variation, u32 secure_value_slot) = 0;
|
||||||
|
|
||||||
|
virtual Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) = 0;
|
||||||
|
|
||||||
|
virtual Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) = 0;
|
||||||
|
|
||||||
|
virtual ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue(
|
||||||
|
u32 secure_value_slot) = 0;
|
||||||
|
|
||||||
|
virtual bool BackendIsSlow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DefaultSecureValueBackend : public SecureValueBackend {
|
||||||
|
public:
|
||||||
|
Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot,
|
||||||
|
u64 secure_value) override;
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue(
|
||||||
|
u32 unique_id, u8 title_variation, u32 secure_value_slot) override;
|
||||||
|
|
||||||
|
Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) override;
|
||||||
|
|
||||||
|
Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override;
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue(
|
||||||
|
u32 secure_value_slot) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int);
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
};
|
||||||
|
} // namespace FileSys
|
||||||
|
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::DefaultSecureValueBackend)
|
119
src/core/file_sys/secure_value_backend_artic.cpp
Normal file
119
src/core/file_sys/secure_value_backend_artic.cpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/archives.h"
|
||||||
|
#include "core/file_sys/archive_artic.h"
|
||||||
|
#include "core/file_sys/secure_value_backend_artic.h"
|
||||||
|
|
||||||
|
SERIALIZE_EXPORT_IMPL(FileSys::ArticSecureValueBackend)
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
Result ArticSecureValueBackend::ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation,
|
||||||
|
u32 secure_value_slot,
|
||||||
|
u64 secure_value) {
|
||||||
|
auto req = client->NewRequest("FSUSER_ObsSetSaveDataSecureVal");
|
||||||
|
|
||||||
|
req.AddParameterU64(secure_value);
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
req.AddParameterU32(unique_id);
|
||||||
|
req.AddParameterU8(title_variation);
|
||||||
|
|
||||||
|
return ArticArchive::RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, u64>> ArticSecureValueBackend::ObsoletedGetSaveDataSecureValue(
|
||||||
|
u32 unique_id, u8 title_variation, u32 secure_value_slot) {
|
||||||
|
|
||||||
|
auto req = client->NewRequest("FSUSER_ObsGetSaveDataSecureVal");
|
||||||
|
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
req.AddParameterU32(unique_id);
|
||||||
|
req.AddParameterU8(title_variation);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool exists;
|
||||||
|
u64 secure_value;
|
||||||
|
} secure_value_result;
|
||||||
|
static_assert(sizeof(secure_value_result) == 0x10);
|
||||||
|
|
||||||
|
auto output_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!output_buf.has_value())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (output_buf->second != sizeof(secure_value_result))
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
memcpy(&secure_value_result, output_buf->first, output_buf->second);
|
||||||
|
return std::make_tuple(secure_value_result.exists, secure_value_result.secure_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticSecureValueBackend::ControlSecureSave(u32 action, u8* input, size_t input_size,
|
||||||
|
u8* output, size_t output_size) {
|
||||||
|
auto req = client->NewRequest("FSUSER_ControlSecureSave");
|
||||||
|
|
||||||
|
req.AddParameterU32(action);
|
||||||
|
req.AddParameterBuffer(input, input_size);
|
||||||
|
req.AddParameterU32(static_cast<u32>(output_size));
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
auto output_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!output_buf.has_value())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (output_buf->second != output_size)
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
memcpy(output, output_buf->first, output_buf->second);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArticSecureValueBackend::SetThisSaveDataSecureValue(u32 secure_value_slot,
|
||||||
|
u64 secure_value) {
|
||||||
|
auto req = client->NewRequest("FSUSER_SetThisSaveDataSecVal");
|
||||||
|
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
req.AddParameterU64(secure_value);
|
||||||
|
|
||||||
|
return ArticArchive::RespResult(client->Send(req));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> ArticSecureValueBackend::GetThisSaveDataSecureValue(
|
||||||
|
u32 secure_value_slot) {
|
||||||
|
auto req = client->NewRequest("FSUSER_GetThisSaveDataSecVal");
|
||||||
|
|
||||||
|
req.AddParameterU32(secure_value_slot);
|
||||||
|
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
auto res = ArticArchive::RespResult(resp);
|
||||||
|
if (res.IsError())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool exists;
|
||||||
|
bool isGamecard;
|
||||||
|
u64 secure_value;
|
||||||
|
} secure_value_result;
|
||||||
|
static_assert(sizeof(secure_value_result) == 0x10);
|
||||||
|
|
||||||
|
auto output_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!output_buf.has_value())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (output_buf->second != sizeof(secure_value_result))
|
||||||
|
return ResultUnknown;
|
||||||
|
|
||||||
|
memcpy(&secure_value_result, output_buf->first, output_buf->second);
|
||||||
|
return std::make_tuple(secure_value_result.exists, secure_value_result.isGamecard,
|
||||||
|
secure_value_result.secure_value);
|
||||||
|
}
|
||||||
|
} // namespace FileSys
|
53
src/core/file_sys/secure_value_backend_artic.h
Normal file
53
src/core/file_sys/secure_value_backend_artic.h
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tuple"
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/file_sys/secure_value_backend.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace FileSys {
|
||||||
|
class ArticSecureValueBackend : public SecureValueBackend {
|
||||||
|
public:
|
||||||
|
ArticSecureValueBackend(const std::shared_ptr<Network::ArticBase::Client>& _client)
|
||||||
|
: client(_client) {}
|
||||||
|
|
||||||
|
Result ObsoletedSetSaveDataSecureValue(u32 unique_id, u8 title_variation, u32 secure_value_slot,
|
||||||
|
u64 secure_value) override;
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, u64>> ObsoletedGetSaveDataSecureValue(
|
||||||
|
u32 unique_id, u8 title_variation, u32 secure_value_slot) override;
|
||||||
|
|
||||||
|
Result ControlSecureSave(u32 action, u8* input, size_t input_size, u8* output,
|
||||||
|
size_t output_size) override;
|
||||||
|
|
||||||
|
Result SetThisSaveDataSecureValue(u32 secure_value_slot, u64 secure_value) override;
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> GetThisSaveDataSecureValue(
|
||||||
|
u32 secure_value_slot) override;
|
||||||
|
|
||||||
|
bool BackendIsSlow() override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ArticSecureValueBackend() = default;
|
||||||
|
|
||||||
|
template <class Archive>
|
||||||
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
|
ar& boost::serialization::base_object<SecureValueBackend>(*this);
|
||||||
|
}
|
||||||
|
friend class boost::serialization::access;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
};
|
||||||
|
} // namespace FileSys
|
||||||
|
|
||||||
|
BOOST_CLASS_EXPORT_KEY(FileSys::ArticSecureValueBackend)
|
|
@ -58,6 +58,11 @@ public:
|
||||||
: RequestBuilder(
|
: RequestBuilder(
|
||||||
context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {}
|
context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {}
|
||||||
|
|
||||||
|
RequestBuilder(Kernel::HLERequestContext& context, unsigned normal_params_size,
|
||||||
|
unsigned translate_params_size)
|
||||||
|
: RequestBuilder(context, Header{MakeHeader(context.CommandID(), normal_params_size,
|
||||||
|
translate_params_size)}) {}
|
||||||
|
|
||||||
// Validate on destruction, as there shouldn't be any case where we don't want it
|
// Validate on destruction, as there shouldn't be any case where we don't want it
|
||||||
~RequestBuilder() {
|
~RequestBuilder() {
|
||||||
ValidateHeader();
|
ValidateHeader();
|
||||||
|
|
|
@ -206,6 +206,11 @@ public:
|
||||||
return {cmd_buf[0]};
|
return {cmd_buf[0]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the Command ID from the IPC command buffer.
|
||||||
|
u16 CommandID() const {
|
||||||
|
return static_cast<u16>(CommandHeader().command_id.Value());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the session through which this request was made. This can be used as a map key to
|
* Returns the session through which this request was made. This can be used as a map key to
|
||||||
* access per-client data on services.
|
* access per-client data on services.
|
||||||
|
|
|
@ -266,7 +266,7 @@ ResultVal<std::size_t> CIAFile::WriteContentData(u64 offset, std::size_t length,
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> CIAFile::Write(u64 offset, std::size_t length, bool flush,
|
||||||
const u8* buffer) {
|
bool update_timestamp, const u8* buffer) {
|
||||||
written += length;
|
written += length;
|
||||||
|
|
||||||
// TODO(shinyquagsire23): Can we assume that things will only be written in sequence?
|
// TODO(shinyquagsire23): Can we assume that things will only be written in sequence?
|
||||||
|
@ -347,7 +347,7 @@ bool CIAFile::SetSize(u64 size) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CIAFile::Close() const {
|
bool CIAFile::Close() {
|
||||||
bool complete =
|
bool complete =
|
||||||
install_state >= CIAInstallState::TMDLoaded &&
|
install_state >= CIAInstallState::TMDLoaded &&
|
||||||
content_written.size() == container.GetTitleMetadata().GetContentCount() &&
|
content_written.size() == container.GetTitleMetadata().GetContentCount() &&
|
||||||
|
@ -419,7 +419,7 @@ ResultVal<std::size_t> TicketFile::Read(u64 offset, std::size_t length, u8* buff
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> TicketFile::Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> TicketFile::Write(u64 offset, std::size_t length, bool flush,
|
||||||
const u8* buffer) {
|
bool update_timestamp, const u8* buffer) {
|
||||||
written += length;
|
written += length;
|
||||||
data.resize(written);
|
data.resize(written);
|
||||||
std::memcpy(data.data() + offset, buffer, length);
|
std::memcpy(data.data() + offset, buffer, length);
|
||||||
|
@ -434,7 +434,7 @@ bool TicketFile::SetSize(u64 size) const {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TicketFile::Close() const {
|
bool TicketFile::Close() {
|
||||||
FileSys::Ticket ticket;
|
FileSys::Ticket ticket;
|
||||||
if (ticket.Load(data, 0) == Loader::ResultStatus::Success) {
|
if (ticket.Load(data, 0) == Loader::ResultStatus::Success) {
|
||||||
LOG_WARNING(Service_AM, "Discarding ticket for {:#016X}.", ticket.GetTitleID());
|
LOG_WARNING(Service_AM, "Discarding ticket for {:#016X}.", ticket.GetTitleID());
|
||||||
|
@ -480,7 +480,7 @@ InstallStatus InstallCIA(const std::string& path,
|
||||||
while (total_bytes_read != file_size) {
|
while (total_bytes_read != file_size) {
|
||||||
std::size_t bytes_read = file.ReadBytes(buffer.data(), buffer.size());
|
std::size_t bytes_read = file.ReadBytes(buffer.data(), buffer.size());
|
||||||
auto result = installFile.Write(static_cast<u64>(total_bytes_read), bytes_read, true,
|
auto result = installFile.Write(static_cast<u64>(total_bytes_read), bytes_read, true,
|
||||||
static_cast<u8*>(buffer.data()));
|
false, static_cast<u8*>(buffer.data()));
|
||||||
|
|
||||||
if (update_callback) {
|
if (update_callback) {
|
||||||
update_callback(total_bytes_read, file_size);
|
update_callback(total_bytes_read, file_size);
|
||||||
|
@ -590,7 +590,8 @@ InstallStatus InstallFromNus(u64 title_id, int version) {
|
||||||
const u64 offset =
|
const u64 offset =
|
||||||
Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT);
|
Common::AlignUp(current_offset + data.size(), FileSys::CIA_SECTION_ALIGNMENT);
|
||||||
data.resize(offset - current_offset, 0);
|
data.resize(offset - current_offset, 0);
|
||||||
const auto result = install_file.Write(current_offset, data.size(), true, data.data());
|
const auto result =
|
||||||
|
install_file.Write(current_offset, data.size(), true, false, data.data());
|
||||||
if (result.Failed()) {
|
if (result.Failed()) {
|
||||||
LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}",
|
LOG_ERROR(Service_AM, "CIA file installation aborted with error code {:08x}",
|
||||||
result.Code().raw);
|
result.Code().raw);
|
||||||
|
@ -1464,9 +1465,9 @@ public:
|
||||||
return file->backend->Read(offset + file_offset, length, buffer);
|
return file->backend->Read(offset + file_offset, length, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override {
|
const u8* buffer) override {
|
||||||
return file->backend->Write(offset + file_offset, length, flush, buffer);
|
return file->backend->Write(offset + file_offset, length, flush, update_timestamp, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GetSize() const override {
|
u64 GetSize() const override {
|
||||||
|
@ -1475,7 +1476,7 @@ public:
|
||||||
bool SetSize(u64 size) const override {
|
bool SetSize(u64 size) const override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
bool Close() const override {
|
bool Close() override {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Flush() const override {}
|
void Flush() const override {}
|
||||||
|
|
|
@ -111,11 +111,11 @@ public:
|
||||||
Result WriteTicket();
|
Result WriteTicket();
|
||||||
Result WriteTitleMetadata();
|
Result WriteTitleMetadata();
|
||||||
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
|
ResultVal<std::size_t> WriteContentData(u64 offset, std::size_t length, const u8* buffer);
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override;
|
bool Close() override;
|
||||||
void Flush() const override;
|
void Flush() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -146,11 +146,11 @@ public:
|
||||||
~TicketFile();
|
~TicketFile();
|
||||||
|
|
||||||
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
ResultVal<std::size_t> Read(u64 offset, std::size_t length, u8* buffer) const override;
|
||||||
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush,
|
ResultVal<std::size_t> Write(u64 offset, std::size_t length, bool flush, bool update_timestamp,
|
||||||
const u8* buffer) override;
|
const u8* buffer) override;
|
||||||
u64 GetSize() const override;
|
u64 GetSize() const override;
|
||||||
bool SetSize(u64 size) const override;
|
bool SetSize(u64 size) const override;
|
||||||
bool Close() const override;
|
bool Close() override;
|
||||||
void Flush() const override;
|
void Flush() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -217,7 +217,7 @@ bool Module::LoadSharedFont() {
|
||||||
const FileSys::Path file_path(std::vector<u8>(20, 0));
|
const FileSys::Path file_path(std::vector<u8>(20, 0));
|
||||||
FileSys::Mode open_mode = {};
|
FileSys::Mode open_mode = {};
|
||||||
open_mode.read_flag.Assign(1);
|
open_mode.read_flag.Assign(1);
|
||||||
auto file_result = archive.OpenFile(file_path, open_mode);
|
auto file_result = archive.OpenFile(file_path, open_mode, 0);
|
||||||
if (file_result.Failed())
|
if (file_result.Failed())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ Result OnlineService::InitializeSession(u64 init_program_id) {
|
||||||
boss_system_save_data_archive = std::move(archive_result).Unwrap();
|
boss_system_save_data_archive = std::move(archive_result).Unwrap();
|
||||||
} else if (archive_result.Code() == FileSys::ResultNotFound) {
|
} else if (archive_result.Code() == FileSys::ResultNotFound) {
|
||||||
// If the archive didn't exist, create the files inside
|
// If the archive didn't exist, create the files inside
|
||||||
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
|
|
||||||
// Open it again to get a valid archive now that the folder exists
|
// Open it again to get a valid archive now that the folder exists
|
||||||
auto create_archive_result = systemsavedata_factory.Open(archive_path, 0);
|
auto create_archive_result = systemsavedata_factory.Open(archive_path, 0);
|
||||||
|
|
|
@ -116,7 +116,7 @@ void Module::Interface::Open(Kernel::HLERequestContext& ctx) {
|
||||||
std::vector<u8> program_id(8);
|
std::vector<u8> program_id(8);
|
||||||
u64_le le_program_id = cecd->system.Kernel().GetCurrentProcess()->codeset->program_id;
|
u64_le le_program_id = cecd->system.Kernel().GetCurrentProcess()->codeset->program_id;
|
||||||
std::memcpy(program_id.data(), &le_program_id, sizeof(u64));
|
std::memcpy(program_id.data(), &le_program_id, sizeof(u64));
|
||||||
session_data->file->Write(0, sizeof(u64), true, program_id.data());
|
session_data->file->Write(0, sizeof(u64), true, false, program_id.data());
|
||||||
session_data->file->Close();
|
session_data->file->Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ void Module::Interface::Write(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] const u32 bytes_written = static_cast<u32>(
|
[[maybe_unused]] const u32 bytes_written = static_cast<u32>(
|
||||||
session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
|
session_data->file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap());
|
||||||
session_data->file->Close();
|
session_data->file->Close();
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
@ -435,7 +435,7 @@ void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) {
|
||||||
msg_header.forward_count, msg_header.user_data);
|
msg_header.forward_count, msg_header.user_data);
|
||||||
|
|
||||||
[[maybe_unused]] const u32 bytes_written =
|
[[maybe_unused]] const u32 bytes_written =
|
||||||
static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
|
static_cast<u32>(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap());
|
||||||
message->Close();
|
message->Close();
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
@ -522,7 +522,7 @@ void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) {
|
||||||
std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size);
|
std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size);
|
||||||
|
|
||||||
[[maybe_unused]] const u32 bytes_written =
|
[[maybe_unused]] const u32 bytes_written =
|
||||||
static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
|
static_cast<u32>(message->Write(0, buffer_size, true, false, buffer.data()).Unwrap());
|
||||||
message->Close();
|
message->Close();
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
@ -607,7 +607,7 @@ void Module::Interface::SetData(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer);
|
cecd->CheckAndUpdateFile(CecDataPathType::OutboxIndex, ncch_program_id, buffer);
|
||||||
|
|
||||||
file->Write(0, buffer.size(), true, buffer.data());
|
file->Write(0, buffer.size(), true, false, buffer.data());
|
||||||
file->Close();
|
file->Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -764,8 +764,8 @@ void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) {
|
||||||
cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer);
|
cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] const u32 bytes_written =
|
[[maybe_unused]] const u32 bytes_written = static_cast<u32>(
|
||||||
static_cast<u32>(file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
|
file->Write(0, buffer.size(), true, false, buffer.data()).Unwrap());
|
||||||
file->Close();
|
file->Close();
|
||||||
|
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
|
@ -1409,7 +1409,7 @@ Module::Module(Core::System& system) : system(system) {
|
||||||
cecd_system_save_data_archive = std::move(archive_result).Unwrap();
|
cecd_system_save_data_archive = std::move(archive_result).Unwrap();
|
||||||
} else {
|
} else {
|
||||||
// Format the archive to create the directories
|
// Format the archive to create the directories
|
||||||
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
|
|
||||||
// Open it again to get a valid archive now that the folder exists
|
// Open it again to get a valid archive now that the folder exists
|
||||||
cecd_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
cecd_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
@ -1442,7 +1442,7 @@ Module::Module(Core::System& system) : system(system) {
|
||||||
eventlog_buffer[1] = 0x41;
|
eventlog_buffer[1] = 0x41;
|
||||||
eventlog_buffer[2] = 0x12;
|
eventlog_buffer[2] = 0x12;
|
||||||
|
|
||||||
eventlog->Write(0, eventlog_size, true, eventlog_buffer.data());
|
eventlog->Write(0, eventlog_size, true, false, eventlog_buffer.data());
|
||||||
eventlog->Close();
|
eventlog->Close();
|
||||||
|
|
||||||
/// MBoxList____ resides within the root CEC/ directory.
|
/// MBoxList____ resides within the root CEC/ directory.
|
||||||
|
@ -1464,7 +1464,7 @@ Module::Module(Core::System& system) : system(system) {
|
||||||
// mboxlist_buffer[2-3] are already zeroed
|
// mboxlist_buffer[2-3] are already zeroed
|
||||||
mboxlist_buffer[4] = 0x01;
|
mboxlist_buffer[4] = 0x01;
|
||||||
|
|
||||||
mboxlist->Write(0, mboxlist_size, true, mboxlist_buffer.data());
|
mboxlist->Write(0, mboxlist_size, true, false, mboxlist_buffer.data());
|
||||||
mboxlist->Close();
|
mboxlist->Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -565,7 +565,7 @@ Result Module::UpdateConfigNANDSavegame() {
|
||||||
ASSERT_MSG(config_result.Succeeded(), "could not open file");
|
ASSERT_MSG(config_result.Succeeded(), "could not open file");
|
||||||
|
|
||||||
auto config = std::move(config_result).Unwrap();
|
auto config = std::move(config_result).Unwrap();
|
||||||
config->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data());
|
config->Write(0, CONFIG_SAVEFILE_SIZE, true, false, cfg_config_file_buffer.data());
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
@ -625,7 +625,7 @@ Result Module::LoadConfigNANDSaveFile() {
|
||||||
// If the archive didn't exist, create the files inside
|
// If the archive didn't exist, create the files inside
|
||||||
if (archive_result.Code() == FileSys::ResultNotFound) {
|
if (archive_result.Code() == FileSys::ResultNotFound) {
|
||||||
// Format the archive to create the directories
|
// Format the archive to create the directories
|
||||||
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
|
|
||||||
// Open it again to get a valid archive now that the folder exists
|
// Open it again to get a valid archive now that the folder exists
|
||||||
cfg_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
cfg_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
|
|
@ -67,10 +67,24 @@ ResultVal<ArchiveHandle> ArchiveManager::OpenArchive(ArchiveIdCode id_code,
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::CloseArchive(ArchiveHandle handle) {
|
Result ArchiveManager::CloseArchive(ArchiveHandle handle) {
|
||||||
if (handle_map.erase(handle) == 0)
|
auto itr = handle_map.find(handle);
|
||||||
|
if (itr != handle_map.end()) {
|
||||||
|
itr->second->Close();
|
||||||
|
} else {
|
||||||
return FileSys::ResultInvalidArchiveHandle;
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
else
|
}
|
||||||
return ResultSuccess;
|
handle_map.erase(itr);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ArchiveManager::ControlArchive(ArchiveHandle handle, u32 action, u8* input,
|
||||||
|
size_t input_size, u8* output, size_t output_size) {
|
||||||
|
auto itr = handle_map.find(handle);
|
||||||
|
if (itr != handle_map.end()) {
|
||||||
|
return itr->second->Control(action, input, input_size, output, output_size);
|
||||||
|
} else {
|
||||||
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in
|
// TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in
|
||||||
|
@ -90,14 +104,14 @@ Result ArchiveManager::RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFacto
|
||||||
|
|
||||||
std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds>
|
std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds>
|
||||||
ArchiveManager::OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
ArchiveManager::OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
||||||
const FileSys::Mode mode) {
|
const FileSys::Mode mode, u32 attributes) {
|
||||||
ArchiveBackend* archive = GetArchive(archive_handle);
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
if (archive == nullptr) {
|
if (archive == nullptr) {
|
||||||
return std::make_pair(FileSys::ResultInvalidArchiveHandle, std::chrono::nanoseconds{0});
|
return std::make_pair(FileSys::ResultInvalidArchiveHandle, std::chrono::nanoseconds{0});
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::chrono::nanoseconds open_timeout_ns{archive->GetOpenDelayNs()};
|
const std::chrono::nanoseconds open_timeout_ns{archive->GetOpenDelayNs()};
|
||||||
auto backend = archive->OpenFile(path, mode);
|
auto backend = archive->OpenFile(path, mode, attributes);
|
||||||
if (backend.Failed()) {
|
if (backend.Failed()) {
|
||||||
return std::make_pair(backend.Code(), open_timeout_ns);
|
return std::make_pair(backend.Code(), open_timeout_ns);
|
||||||
}
|
}
|
||||||
|
@ -151,21 +165,21 @@ Result ArchiveManager::DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archi
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
Result ArchiveManager::CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
||||||
u64 file_size) {
|
u64 file_size, u32 attributes) {
|
||||||
ArchiveBackend* archive = GetArchive(archive_handle);
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return FileSys::ResultInvalidArchiveHandle;
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
|
|
||||||
return archive->CreateFile(path, file_size);
|
return archive->CreateFile(path, file_size, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::CreateDirectoryFromArchive(ArchiveHandle archive_handle,
|
Result ArchiveManager::CreateDirectoryFromArchive(ArchiveHandle archive_handle,
|
||||||
const FileSys::Path& path) {
|
const FileSys::Path& path, u32 attributes) {
|
||||||
ArchiveBackend* archive = GetArchive(archive_handle);
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
if (archive == nullptr)
|
if (archive == nullptr)
|
||||||
return FileSys::ResultInvalidArchiveHandle;
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
|
|
||||||
return archive->CreateDirectory(path);
|
return archive->CreateDirectory(path, attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
|
Result ArchiveManager::RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
|
||||||
|
@ -210,13 +224,15 @@ ResultVal<u64> ArchiveManager::GetFreeBytesInArchive(ArchiveHandle archive_handl
|
||||||
|
|
||||||
Result ArchiveManager::FormatArchive(ArchiveIdCode id_code,
|
Result ArchiveManager::FormatArchive(ArchiveIdCode id_code,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
const FileSys::Path& path, u64 program_id) {
|
const FileSys::Path& path, u64 program_id,
|
||||||
|
u32 directory_buckets, u32 file_buckets) {
|
||||||
auto archive_itr = id_code_map.find(id_code);
|
auto archive_itr = id_code_map.find(id_code);
|
||||||
if (archive_itr == id_code_map.end()) {
|
if (archive_itr == id_code_map.end()) {
|
||||||
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
||||||
}
|
}
|
||||||
|
|
||||||
return archive_itr->second->Format(path, format_info, program_id);
|
return archive_itr->second->Format(path, format_info, program_id, directory_buckets,
|
||||||
|
file_buckets);
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultVal<FileSys::ArchiveFormatInfo> ArchiveManager::GetArchiveFormatInfo(
|
ResultVal<FileSys::ArchiveFormatInfo> ArchiveManager::GetArchiveFormatInfo(
|
||||||
|
@ -229,10 +245,10 @@ ResultVal<FileSys::ArchiveFormatInfo> ArchiveManager::GetArchiveFormatInfo(
|
||||||
return archive->second->GetFormatInfo(archive_path, program_id);
|
return archive->second->GetFormatInfo(archive_path, program_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low,
|
Result ArchiveManager::CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low,
|
||||||
std::span<const u8> smdh_icon,
|
std::span<const u8> smdh_icon,
|
||||||
const FileSys::ArchiveFormatInfo& format_info,
|
const FileSys::ArchiveFormatInfo& format_info,
|
||||||
u64 program_id) {
|
u64 program_id, u64 total_size) {
|
||||||
// Construct the binary path to the archive first
|
// Construct the binary path to the archive first
|
||||||
FileSys::Path path =
|
FileSys::Path path =
|
||||||
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low);
|
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low);
|
||||||
|
@ -246,37 +262,26 @@ Result ArchiveManager::CreateExtSaveData(MediaType media_type, u32 high, u32 low
|
||||||
|
|
||||||
auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get());
|
auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get());
|
||||||
|
|
||||||
Result result = ext_savedata->Format(path, format_info, program_id);
|
Result result = ext_savedata->FormatAsExtData(path, format_info, unknown, program_id,
|
||||||
|
total_size, smdh_icon);
|
||||||
if (result.IsError()) {
|
if (result.IsError()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
ext_savedata->WriteIcon(path, smdh_icon);
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u32 high, u32 low) {
|
Result ArchiveManager::DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low) {
|
||||||
// Construct the binary path to the archive first
|
auto archive = id_code_map.find(media_type == MediaType::NAND ? ArchiveIdCode::SharedExtSaveData
|
||||||
FileSys::Path path =
|
: ArchiveIdCode::ExtSaveData);
|
||||||
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low);
|
|
||||||
|
|
||||||
std::string media_type_directory;
|
if (archive == id_code_map.end()) {
|
||||||
if (media_type == MediaType::NAND) {
|
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
||||||
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
|
|
||||||
} else if (media_type == MediaType::SDMC) {
|
|
||||||
media_type_directory = FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir);
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(Service_FS, "Unsupported media type {}", media_type);
|
|
||||||
return ResultUnknown; // TODO(Subv): Find the right error code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all directories (/user, /boss) and the icon file.
|
auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get());
|
||||||
std::string base_path =
|
|
||||||
FileSys::GetExtDataContainerPath(media_type_directory, media_type == MediaType::NAND);
|
return ext_savedata->DeleteExtData(media_type, unknown, high, low);
|
||||||
std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path);
|
|
||||||
if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path))
|
|
||||||
return ResultUnknown; // TODO(Subv): Find the right error code
|
|
||||||
return ResultSuccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) {
|
Result ArchiveManager::DeleteSystemSaveData(u32 high, u32 low) {
|
||||||
|
@ -317,6 +322,24 @@ ResultVal<ArchiveResource> ArchiveManager::GetArchiveResource(MediaType media_ty
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result ArchiveManager::SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot,
|
||||||
|
u64 secure_value, bool flush) {
|
||||||
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
|
if (archive == nullptr) {
|
||||||
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
|
}
|
||||||
|
return archive->SetSaveDataSecureValue(secure_value_slot, secure_value, flush);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> ArchiveManager::GetSaveDataSecureValue(
|
||||||
|
ArchiveHandle archive_handle, u32 secure_value_slot) {
|
||||||
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
|
if (archive == nullptr) {
|
||||||
|
return FileSys::ResultInvalidArchiveHandle;
|
||||||
|
}
|
||||||
|
return archive->GetSaveDataSecureValue(secure_value_slot);
|
||||||
|
}
|
||||||
|
|
||||||
void ArchiveManager::RegisterArchiveTypes() {
|
void ArchiveManager::RegisterArchiveTypes() {
|
||||||
// TODO(Subv): Add the other archive types (see here for the known types:
|
// TODO(Subv): Add the other archive types (see here for the known types:
|
||||||
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
|
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
|
||||||
|
@ -337,7 +360,7 @@ void ArchiveManager::RegisterArchiveTypes() {
|
||||||
sdmc_directory);
|
sdmc_directory);
|
||||||
|
|
||||||
// Create the SaveData archive
|
// Create the SaveData archive
|
||||||
auto sd_savedata_source = std::make_shared<FileSys::ArchiveSource_SDSaveData>(sdmc_directory);
|
sd_savedata_source = std::make_shared<FileSys::ArchiveSource_SDSaveData>(sdmc_directory);
|
||||||
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sd_savedata_source);
|
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sd_savedata_source);
|
||||||
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
|
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
|
||||||
auto other_savedata_permitted_factory =
|
auto other_savedata_permitted_factory =
|
||||||
|
@ -373,6 +396,23 @@ void ArchiveManager::RegisterArchiveTypes() {
|
||||||
RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH);
|
RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ArchiveManager::ArchiveIsSlow(ArchiveIdCode archive_id) {
|
||||||
|
auto itr = id_code_map.find(archive_id);
|
||||||
|
if (itr == id_code_map.end() || itr->second.get() == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itr->second->IsSlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ArchiveManager::ArchiveIsSlow(ArchiveHandle archive_handle) {
|
||||||
|
ArchiveBackend* archive = GetArchive(archive_handle);
|
||||||
|
if (archive == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return archive->IsSlow();
|
||||||
|
}
|
||||||
|
|
||||||
void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) {
|
void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) {
|
||||||
auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH);
|
auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH);
|
||||||
if (itr == id_code_map.end()) {
|
if (itr == id_code_map.end()) {
|
||||||
|
@ -385,6 +425,35 @@ void ArchiveManager::RegisterSelfNCCH(Loader::AppLoader& app_loader) {
|
||||||
factory->Register(app_loader);
|
factory->Register(app_loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ArchiveManager::RegisterArticSaveDataSource(
|
||||||
|
std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
if (!sd_savedata_source.get()) {
|
||||||
|
LOG_ERROR(Service_FS, "Could not register artic save data source.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sd_savedata_source->RegisterArtic(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArchiveManager::RegisterArticExtData(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
for (auto it : {ArchiveIdCode::ExtSaveData, ArchiveIdCode::SharedExtSaveData,
|
||||||
|
ArchiveIdCode::BossExtSaveData}) {
|
||||||
|
auto itr = id_code_map.find(it);
|
||||||
|
if (itr == id_code_map.end() || itr->second.get() == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
reinterpret_cast<FileSys::ArchiveFactory_ExtSaveData*>(itr->second.get())
|
||||||
|
->RegisterArtic(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ArchiveManager::RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client) {
|
||||||
|
auto itr = id_code_map.find(ArchiveIdCode::NCCH);
|
||||||
|
if (itr == id_code_map.end() || itr->second.get() == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reinterpret_cast<FileSys::ArchiveFactory_NCCH*>(itr->second.get())->RegisterArtic(client);
|
||||||
|
}
|
||||||
|
|
||||||
ArchiveManager::ArchiveManager(Core::System& system) : system(system) {
|
ArchiveManager::ArchiveManager(Core::System& system) : system(system) {
|
||||||
RegisterArchiveTypes();
|
RegisterArchiveTypes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,11 @@
|
||||||
#include <boost/serialization/unordered_map.hpp>
|
#include <boost/serialization/unordered_map.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/file_sys/archive_backend.h"
|
#include "core/file_sys/archive_backend.h"
|
||||||
|
#include "core/file_sys/archive_source_sd_savedata.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
#include "core/hle/service/fs/directory.h"
|
#include "core/hle/service/fs/directory.h"
|
||||||
#include "core/hle/service/fs/file.h"
|
#include "core/hle/service/fs/file.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
/// The unique system identifier hash, also known as ID0
|
/// The unique system identifier hash, also known as ID0
|
||||||
static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"};
|
static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"};
|
||||||
|
@ -67,6 +69,19 @@ struct ArchiveResource {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(ArchiveResource) == 0x10, "ArchiveResource has incorrect size");
|
static_assert(sizeof(ArchiveResource) == 0x10, "ArchiveResource has incorrect size");
|
||||||
|
|
||||||
|
struct ExtSaveDataInfo {
|
||||||
|
u8 media_type;
|
||||||
|
u8 unknown;
|
||||||
|
u16 reserved1;
|
||||||
|
u32 save_id_low;
|
||||||
|
u32 save_id_high;
|
||||||
|
u32 reserved2;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ExtSaveDataInfo) == 0x10, "ExtSaveDataInfo struct has incorrect size");
|
||||||
|
static_assert(std::is_trivial<ExtSaveDataInfo>(), "ExtSaveDataInfo should be trivial");
|
||||||
|
static_assert(std::is_trivially_copyable<ExtSaveDataInfo>(),
|
||||||
|
"ExtSaveDataInfo should be trivially copyable");
|
||||||
|
|
||||||
using FileSys::ArchiveBackend;
|
using FileSys::ArchiveBackend;
|
||||||
using FileSys::ArchiveFactory;
|
using FileSys::ArchiveFactory;
|
||||||
|
|
||||||
|
@ -90,6 +105,9 @@ public:
|
||||||
*/
|
*/
|
||||||
Result CloseArchive(ArchiveHandle handle);
|
Result CloseArchive(ArchiveHandle handle);
|
||||||
|
|
||||||
|
Result ControlArchive(ArchiveHandle handle, u32 action, u8* input, size_t input_size,
|
||||||
|
u8* output, size_t output_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a File from an Archive
|
* Open a File from an Archive
|
||||||
* @param archive_handle Handle to an open Archive object
|
* @param archive_handle Handle to an open Archive object
|
||||||
|
@ -98,7 +116,8 @@ public:
|
||||||
* @return Pair containing the opened File object and the open delay
|
* @return Pair containing the opened File object and the open delay
|
||||||
*/
|
*/
|
||||||
std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds> OpenFileFromArchive(
|
std::pair<ResultVal<std::shared_ptr<File>>, std::chrono::nanoseconds> OpenFileFromArchive(
|
||||||
ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode);
|
ArchiveHandle archive_handle, const FileSys::Path& path, FileSys::Mode mode,
|
||||||
|
u32 attributes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a File from an Archive
|
* Delete a File from an Archive
|
||||||
|
@ -146,7 +165,7 @@ public:
|
||||||
* @return File creation result code
|
* @return File creation result code
|
||||||
*/
|
*/
|
||||||
Result CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
Result CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
||||||
u64 file_size);
|
u64 file_size, u32 attributes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Directory from an Archive
|
* Create a Directory from an Archive
|
||||||
|
@ -154,7 +173,8 @@ public:
|
||||||
* @param path Path to the Directory inside of the Archive
|
* @param path Path to the Directory inside of the Archive
|
||||||
* @return Whether creation of directory succeeded
|
* @return Whether creation of directory succeeded
|
||||||
*/
|
*/
|
||||||
Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path);
|
Result CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
|
||||||
|
u32 attributes);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rename a Directory between two Archives
|
* Rename a Directory between two Archives
|
||||||
|
@ -195,7 +215,8 @@ public:
|
||||||
* @return Result 0 on success or the corresponding code on error
|
* @return Result 0 on success or the corresponding code on error
|
||||||
*/
|
*/
|
||||||
Result FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info,
|
Result FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info,
|
||||||
const FileSys::Path& path, u64 program_id);
|
const FileSys::Path& path, u64 program_id, u32 directory_buckets,
|
||||||
|
u32 file_buckets);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the format info about the archive of the specified type and path.
|
* Retrieves the format info about the archive of the specified type and path.
|
||||||
|
@ -219,8 +240,10 @@ public:
|
||||||
* @param program_id the program ID of the client that requests the operation
|
* @param program_id the program ID of the client that requests the operation
|
||||||
* @return Result 0 on success or the corresponding code on error
|
* @return Result 0 on success or the corresponding code on error
|
||||||
*/
|
*/
|
||||||
Result CreateExtSaveData(MediaType media_type, u32 high, u32 low, std::span<const u8> smdh_icon,
|
Result CreateExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low,
|
||||||
const FileSys::ArchiveFormatInfo& format_info, u64 program_id);
|
std::span<const u8> smdh_icon,
|
||||||
|
const FileSys::ArchiveFormatInfo& format_info, u64 program_id,
|
||||||
|
u64 total_size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the SharedExtSaveData archive for the specified extdata ID
|
* Deletes the SharedExtSaveData archive for the specified extdata ID
|
||||||
|
@ -229,7 +252,7 @@ public:
|
||||||
* @param low The low word of the extdata id to delete
|
* @param low The low word of the extdata id to delete
|
||||||
* @return Result 0 on success or the corresponding code on error
|
* @return Result 0 on success or the corresponding code on error
|
||||||
*/
|
*/
|
||||||
Result DeleteExtSaveData(MediaType media_type, u32 high, u32 low);
|
Result DeleteExtSaveData(MediaType media_type, u8 unknown, u32 high, u32 low);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the SystemSaveData archive folder for the specified save data id
|
* Deletes the SystemSaveData archive folder for the specified save data id
|
||||||
|
@ -254,9 +277,25 @@ public:
|
||||||
*/
|
*/
|
||||||
ResultVal<ArchiveResource> GetArchiveResource(MediaType media_type) const;
|
ResultVal<ArchiveResource> GetArchiveResource(MediaType media_type) const;
|
||||||
|
|
||||||
|
Result SetSaveDataSecureValue(ArchiveHandle archive_handle, u32 secure_value_slot,
|
||||||
|
u64 secure_value, bool flush);
|
||||||
|
|
||||||
|
ResultVal<std::tuple<bool, bool, u64>> GetSaveDataSecureValue(ArchiveHandle archive_handle,
|
||||||
|
u32 secure_value_slot);
|
||||||
|
|
||||||
|
bool ArchiveIsSlow(ArchiveIdCode archive_id);
|
||||||
|
|
||||||
|
bool ArchiveIsSlow(ArchiveHandle archive_handle);
|
||||||
|
|
||||||
/// Registers a new NCCH file with the SelfNCCH archive factory
|
/// Registers a new NCCH file with the SelfNCCH archive factory
|
||||||
void RegisterSelfNCCH(Loader::AppLoader& app_loader);
|
void RegisterSelfNCCH(Loader::AppLoader& app_loader);
|
||||||
|
|
||||||
|
void RegisterArticSaveDataSource(std::shared_ptr<Network::ArticBase::Client>& client);
|
||||||
|
|
||||||
|
void RegisterArticExtData(std::shared_ptr<Network::ArticBase::Client>& client);
|
||||||
|
|
||||||
|
void RegisterArticNCCH(std::shared_ptr<Network::ArticBase::Client>& client);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|
||||||
|
@ -285,11 +324,17 @@ private:
|
||||||
std::unordered_map<ArchiveHandle, std::unique_ptr<ArchiveBackend>> handle_map;
|
std::unordered_map<ArchiveHandle, std::unique_ptr<ArchiveBackend>> handle_map;
|
||||||
ArchiveHandle next_handle = 1;
|
ArchiveHandle next_handle = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Savedata source
|
||||||
|
*/
|
||||||
|
std::shared_ptr<FileSys::ArchiveSource_SDSaveData> sd_savedata_source;
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar& id_code_map;
|
ar& id_code_map;
|
||||||
ar& handle_map;
|
ar& handle_map;
|
||||||
ar& next_handle;
|
ar& next_handle;
|
||||||
|
ar& sd_savedata_source;
|
||||||
}
|
}
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
|
@ -169,10 +169,9 @@ void File::Write(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u64 offset = rp.Pop<u64>();
|
u64 offset = rp.Pop<u64>();
|
||||||
u32 length = rp.Pop<u32>();
|
u32 length = rp.Pop<u32>();
|
||||||
u32 flush = rp.Pop<u32>();
|
u32 flags = rp.Pop<u32>();
|
||||||
auto& buffer = rp.PopMappedBuffer();
|
LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flags=0x{:x}", GetName(), offset,
|
||||||
LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset,
|
length, flags);
|
||||||
length, flush);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
|
|
||||||
|
@ -182,25 +181,75 @@ void File::Write(Kernel::HLERequestContext& ctx) {
|
||||||
if (file->subfile) {
|
if (file->subfile) {
|
||||||
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
||||||
rb.Push<u32>(0);
|
rb.Push<u32>(0);
|
||||||
|
rb.PushMappedBuffer(rp.PopMappedBuffer());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool flush = (flags & 0xFF) != 0, update_timestamp = (flags & 0xFF00) != 0;
|
||||||
|
|
||||||
|
if (!backend->AllowsCachedReads()) {
|
||||||
|
std::vector<u8> data(length);
|
||||||
|
auto& buffer = rp.PopMappedBuffer();
|
||||||
|
buffer.Read(data.data(), 0, data.size());
|
||||||
|
ResultVal<std::size_t> written =
|
||||||
|
backend->Write(offset, data.size(), flush, update_timestamp, data.data());
|
||||||
|
|
||||||
|
// Update file size
|
||||||
|
file->size = backend->GetSize();
|
||||||
|
|
||||||
|
if (written.Failed()) {
|
||||||
|
rb.Push(written.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
} else {
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(*written));
|
||||||
|
}
|
||||||
rb.PushMappedBuffer(buffer);
|
rb.PushMappedBuffer(buffer);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> data(length);
|
struct AsyncData {
|
||||||
buffer.Read(data.data(), 0, data.size());
|
// Input
|
||||||
ResultVal<std::size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
|
u32 length;
|
||||||
|
u64 offset;
|
||||||
|
bool flush;
|
||||||
|
bool update_timestamp;
|
||||||
|
Kernel::MappedBuffer* buffer;
|
||||||
|
FileSessionSlot* file;
|
||||||
|
|
||||||
// Update file size
|
// Output
|
||||||
file->size = backend->GetSize();
|
ResultVal<std::size_t> written;
|
||||||
|
};
|
||||||
|
auto async_data = std::make_shared<AsyncData>();
|
||||||
|
async_data->length = length;
|
||||||
|
async_data->offset = offset;
|
||||||
|
async_data->flush = flush;
|
||||||
|
async_data->update_timestamp = update_timestamp;
|
||||||
|
async_data->buffer = &rp.PopMappedBuffer();
|
||||||
|
async_data->file = file;
|
||||||
|
|
||||||
if (written.Failed()) {
|
ctx.RunAsync(
|
||||||
rb.Push(written.Code());
|
[this, async_data](Kernel::HLERequestContext& ctx) {
|
||||||
rb.Push<u32>(0);
|
std::vector<u8> data(async_data->length);
|
||||||
} else {
|
async_data->buffer->Read(data.data(), 0, data.size());
|
||||||
rb.Push(ResultSuccess);
|
async_data->written = backend->Write(async_data->offset, data.size(), async_data->flush,
|
||||||
rb.Push<u32>(static_cast<u32>(*written));
|
async_data->update_timestamp, data.data());
|
||||||
}
|
|
||||||
rb.PushMappedBuffer(buffer);
|
// Update file size
|
||||||
|
async_data->file->size = backend->GetSize();
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
[async_data](Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestBuilder rb(ctx, 2, 2);
|
||||||
|
if (async_data->written.Failed()) {
|
||||||
|
rb.Push(async_data->written.Code());
|
||||||
|
rb.Push<u32>(0);
|
||||||
|
} else {
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push<u32>(static_cast<u32>(*async_data->written));
|
||||||
|
}
|
||||||
|
rb.PushMappedBuffer(*async_data->buffer);
|
||||||
|
},
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::GetSize(Kernel::HLERequestContext& ctx) {
|
void File::GetSize(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -219,17 +268,32 @@ void File::SetSize(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
FileSessionSlot* file = GetSessionData(ctx.Session());
|
FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
||||||
|
|
||||||
// SetSize can not be called on subfiles.
|
// SetSize can not be called on subfiles.
|
||||||
if (file->subfile) {
|
if (file->subfile) {
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
file->size = size;
|
if (!backend->AllowsCachedReads()) {
|
||||||
backend->SetSize(size);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(ResultSuccess);
|
file->size = size;
|
||||||
|
backend->SetSize(size);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RunAsync(
|
||||||
|
[file, size, this](Kernel::HLERequestContext& ctx) {
|
||||||
|
file->size = size;
|
||||||
|
backend->SetSize(size);
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
[](Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestBuilder rb(ctx, 1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
},
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Close(Kernel::HLERequestContext& ctx) {
|
void File::Close(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -240,26 +304,53 @@ void File::Close(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected",
|
LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected",
|
||||||
connected_sessions.size());
|
connected_sessions.size());
|
||||||
|
|
||||||
backend->Close();
|
if (!backend->AllowsCachedReads()) {
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
backend->Close();
|
||||||
rb.Push(ResultSuccess);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RunAsync(
|
||||||
|
[this](Kernel::HLERequestContext& ctx) {
|
||||||
|
backend->Close();
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
[](Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestBuilder rb(ctx, 1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
},
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Flush(Kernel::HLERequestContext& ctx) {
|
void File::Flush(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
||||||
|
|
||||||
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||||
|
|
||||||
// Subfiles can not be flushed.
|
// Subfiles can not be flushed.
|
||||||
if (file->subfile) {
|
if (file->subfile) {
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
rb.Push(FileSys::ResultUnsupportedOpenFlags);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
backend->Flush();
|
if (!backend->AllowsCachedReads()) {
|
||||||
rb.Push(ResultSuccess);
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||||
|
backend->Flush();
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.RunAsync(
|
||||||
|
[this](Kernel::HLERequestContext& ctx) {
|
||||||
|
backend->Flush();
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
[](Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestBuilder rb(ctx, 1, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
},
|
||||||
|
true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::SetPriority(Kernel::HLERequestContext& ctx) {
|
void File::SetPriority(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@
|
||||||
#include <boost/serialization/base_object.hpp>
|
#include <boost/serialization/base_object.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
|
#include "core/file_sys/secure_value_backend.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
|
@ -77,6 +78,10 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RegisterSecureValueBackend(const std::shared_ptr<FileSys::SecureValueBackend>& backend) {
|
||||||
|
secure_value_backend = backend;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Initialize(Kernel::HLERequestContext& ctx);
|
void Initialize(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
@ -657,6 +662,21 @@ private:
|
||||||
*/
|
*/
|
||||||
void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FS_User::ControlSecureSave service function
|
||||||
|
* Inputs:
|
||||||
|
* 1 : Action
|
||||||
|
* 2 : Input Size
|
||||||
|
* 3 : Output Size
|
||||||
|
* 4 : (Input Size << 4) | 0xA
|
||||||
|
* 5 : Input Pointer
|
||||||
|
* 6 : (Output Size << 4) | 0xC
|
||||||
|
* 7 : Output Pointer
|
||||||
|
* Outputs:
|
||||||
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
|
*/
|
||||||
|
void ControlSecureSave(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FS_User::SetThisSaveDataSecureValue service function.
|
* FS_User::SetThisSaveDataSecureValue service function.
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
@ -722,11 +742,10 @@ private:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
ArchiveManager& archives;
|
ArchiveManager& archives;
|
||||||
|
|
||||||
|
std::shared_ptr<FileSys::SecureValueBackend> secure_value_backend;
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int);
|
||||||
ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
|
|
||||||
ar& priority;
|
|
||||||
}
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1969,7 +1969,7 @@ void HTTP_C::DecryptClCertA() {
|
||||||
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::RomFS, exefs_filepath);
|
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::RomFS, exefs_filepath);
|
||||||
FileSys::Mode open_mode = {};
|
FileSys::Mode open_mode = {};
|
||||||
open_mode.read_flag.Assign(1);
|
open_mode.read_flag.Assign(1);
|
||||||
auto file_result = archive.OpenFile(file_path, open_mode);
|
auto file_result = archive.OpenFile(file_path, open_mode, 0);
|
||||||
if (file_result.Failed()) {
|
if (file_result.Failed()) {
|
||||||
LOG_ERROR(Service_HTTP, "ClCertA file missing");
|
LOG_ERROR(Service_HTTP, "ClCertA file missing");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -151,7 +151,7 @@ void Module::Interface::ResetNotifications(Kernel::HLERequestContext& ctx) {
|
||||||
FileSys::Path archive_path(news_system_savedata_id);
|
FileSys::Path archive_path(news_system_savedata_id);
|
||||||
|
|
||||||
// Format the SystemSaveData archive 0x00010035
|
// Format the SystemSaveData archive 0x00010035
|
||||||
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
|
|
||||||
news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
news->news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
|
||||||
|
@ -655,7 +655,7 @@ Result Module::LoadNewsDBSavedata() {
|
||||||
// If the archive didn't exist, create the files inside
|
// If the archive didn't exist, create the files inside
|
||||||
if (archive_result.Code() == FileSys::ResultNotFound) {
|
if (archive_result.Code() == FileSys::ResultNotFound) {
|
||||||
// Format the archive to create the directories
|
// Format the archive to create the directories
|
||||||
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
systemsavedata_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
|
|
||||||
// Open it again to get a valid archive now that the folder exists
|
// Open it again to get a valid archive now that the folder exists
|
||||||
news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
news_system_save_data_archive = systemsavedata_factory.Open(archive_path, 0).Unwrap();
|
||||||
|
@ -722,7 +722,7 @@ Result Module::SaveFileToSavedata(std::string filename, std::span<const u8> buff
|
||||||
ASSERT_MSG(result.Succeeded(), "could not open file");
|
ASSERT_MSG(result.Succeeded(), "could not open file");
|
||||||
|
|
||||||
auto file = std::move(result).Unwrap();
|
auto file = std::move(result).Unwrap();
|
||||||
file->Write(0, buffer.size(), 1, buffer.data());
|
file->Write(0, buffer.size(), true, false, buffer.data());
|
||||||
file->Close();
|
file->Close();
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
|
|
|
@ -158,7 +158,7 @@ static void WriteGameCoinData(GameCoin gamecoin_data) {
|
||||||
// If the archive didn't exist, create the files inside
|
// If the archive didn't exist, create the files inside
|
||||||
if (archive_result.Code() == FileSys::ResultNotFormatted) {
|
if (archive_result.Code() == FileSys::ResultNotFormatted) {
|
||||||
// Format the archive to create the directories
|
// Format the archive to create the directories
|
||||||
extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0);
|
extdata_archive_factory.Format(archive_path, FileSys::ArchiveFormatInfo(), 0, 0, 0);
|
||||||
// Open it again to get a valid archive now that the folder exists
|
// Open it again to get a valid archive now that the folder exists
|
||||||
archive = extdata_archive_factory.Open(archive_path, 0).Unwrap();
|
archive = extdata_archive_factory.Open(archive_path, 0).Unwrap();
|
||||||
// Create the game coin file
|
// Create the game coin file
|
||||||
|
@ -174,7 +174,8 @@ static void WriteGameCoinData(GameCoin gamecoin_data) {
|
||||||
auto gamecoin_result = archive->OpenFile(gamecoin_path, open_mode);
|
auto gamecoin_result = archive->OpenFile(gamecoin_path, open_mode);
|
||||||
if (gamecoin_result.Succeeded()) {
|
if (gamecoin_result.Succeeded()) {
|
||||||
auto gamecoin = std::move(gamecoin_result).Unwrap();
|
auto gamecoin = std::move(gamecoin_result).Unwrap();
|
||||||
gamecoin->Write(0, sizeof(GameCoin), true, reinterpret_cast<const u8*>(&gamecoin_data));
|
gamecoin->Write(0, sizeof(GameCoin), true, false,
|
||||||
|
reinterpret_cast<const u8*>(&gamecoin_data));
|
||||||
gamecoin->Close();
|
gamecoin->Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "core/hle/kernel/shared_memory.h"
|
#include "core/hle/kernel/shared_memory.h"
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
#include "core/hle/service/soc/soc_u.h"
|
#include "core/hle/service/soc/soc_u.h"
|
||||||
|
#include "network/socket_manager.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
@ -2221,17 +2222,12 @@ SOC_U::SOC_U() : ServiceFramework("soc:U", 18) {
|
||||||
|
|
||||||
RegisterHandlers(functions);
|
RegisterHandlers(functions);
|
||||||
|
|
||||||
#ifdef _WIN32
|
Network::SocketManager::EnableSockets();
|
||||||
WSADATA data;
|
|
||||||
WSAStartup(MAKEWORD(2, 2), &data);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SOC_U::~SOC_U() {
|
SOC_U::~SOC_U() {
|
||||||
CloseAndDeleteAllSockets();
|
CloseAndDeleteAllSockets();
|
||||||
#ifdef _WIN32
|
Network::SocketManager::DisableSockets();
|
||||||
WSACleanup();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() {
|
std::optional<SOC_U::InterfaceInfo> SOC_U::GetDefaultInterfaceInfo() {
|
||||||
|
|
564
src/core/loader/artic.cpp
Normal file
564
src/core/loader/artic.cpp
Normal file
|
@ -0,0 +1,564 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/literals.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/ncch_container.h"
|
||||||
|
#include "core/file_sys/romfs_reader.h"
|
||||||
|
#include "core/file_sys/secure_value_backend_artic.h"
|
||||||
|
#include "core/file_sys/title_metadata.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
#include "core/hle/kernel/process.h"
|
||||||
|
#include "core/hle/kernel/resource_limit.h"
|
||||||
|
#include "core/hle/service/am/am.h"
|
||||||
|
#include "core/hle/service/cfg/cfg.h"
|
||||||
|
#include "core/hle/service/fs/archive.h"
|
||||||
|
#include "core/hle/service/fs/fs_user.h"
|
||||||
|
#include "core/loader/artic.h"
|
||||||
|
#include "core/loader/smdh.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
#include "core/system_titles.h"
|
||||||
|
#include "network/network.h"
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
using namespace Common::Literals;
|
||||||
|
|
||||||
|
Apploader_Artic::~Apploader_Artic() {
|
||||||
|
// TODO(PabloMK7) Find memory leak that prevents the romfs readers being destroyed
|
||||||
|
// when emulation stops. Looks like the mem leak comes from IVFCFile objects
|
||||||
|
// not being destroyed...
|
||||||
|
if (main_romfs_reader) {
|
||||||
|
static_cast<FileSys::ArticRomFSReader*>(main_romfs_reader.get())->ClearCache();
|
||||||
|
static_cast<FileSys::ArticRomFSReader*>(main_romfs_reader.get())->CloseFile();
|
||||||
|
main_romfs_reader.reset();
|
||||||
|
}
|
||||||
|
if (update_romfs_reader) {
|
||||||
|
static_cast<FileSys::ArticRomFSReader*>(update_romfs_reader.get())->ClearCache();
|
||||||
|
static_cast<FileSys::ArticRomFSReader*>(update_romfs_reader.get())->CloseFile();
|
||||||
|
update_romfs_reader.reset();
|
||||||
|
}
|
||||||
|
client->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
FileType Apploader_Artic::IdentifyType(FileUtil::IOFile& file) {
|
||||||
|
return FileType::ARTIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::optional<u32>, ResultStatus> Apploader_Artic::LoadCoreVersion() {
|
||||||
|
if (!is_loaded) {
|
||||||
|
bool success = LoadExheader();
|
||||||
|
if (!success) {
|
||||||
|
return std::make_pair(std::nullopt, ResultStatus::ErrorArtic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the core version from the exheader.
|
||||||
|
auto& ncch_caps = program_exheader.arm11_system_local_caps;
|
||||||
|
return std::make_pair(ncch_caps.core_version, ResultStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::optional<Kernel::MemoryMode>, ResultStatus> Apploader_Artic::LoadKernelMemoryMode() {
|
||||||
|
if (!is_loaded) {
|
||||||
|
bool success = LoadExheader();
|
||||||
|
if (!success) {
|
||||||
|
return std::make_pair(std::nullopt, ResultStatus::ErrorArtic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memory_mode_override.has_value()) {
|
||||||
|
return std::make_pair(memory_mode_override, ResultStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the memory mode from the exheader.
|
||||||
|
auto& ncch_caps = program_exheader.arm11_system_local_caps;
|
||||||
|
auto mode = static_cast<Kernel::MemoryMode>(ncch_caps.system_mode.Value());
|
||||||
|
return std::make_pair(mode, ResultStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus>
|
||||||
|
Apploader_Artic::LoadNew3dsHwCapabilities() {
|
||||||
|
if (!is_loaded) {
|
||||||
|
bool success = LoadExheader();
|
||||||
|
if (!success) {
|
||||||
|
return std::make_pair(std::nullopt, ResultStatus::ErrorArtic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide the capabilities from the exheader.
|
||||||
|
auto& ncch_caps = program_exheader.arm11_system_local_caps;
|
||||||
|
auto caps = Kernel::New3dsHwCapabilities{
|
||||||
|
ncch_caps.enable_l2_cache != 0,
|
||||||
|
ncch_caps.enable_804MHz_cpu != 0,
|
||||||
|
static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode),
|
||||||
|
};
|
||||||
|
return std::make_pair(std::move(caps), ResultStatus::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) {
|
||||||
|
using Kernel::CodeSet;
|
||||||
|
|
||||||
|
if (!is_loaded)
|
||||||
|
return ResultStatus::ErrorNotLoaded;
|
||||||
|
|
||||||
|
std::vector<u8> code;
|
||||||
|
u64_le program_id;
|
||||||
|
if (ResultStatus::Success == ReadCode(code) &&
|
||||||
|
ResultStatus::Success == ReadProgramId(program_id)) {
|
||||||
|
|
||||||
|
std::string process_name = Common::StringFromFixedZeroTerminatedBuffer(
|
||||||
|
(const char*)program_exheader.codeset_info.name, 8);
|
||||||
|
|
||||||
|
std::shared_ptr<CodeSet> codeset = system.Kernel().CreateCodeSet(process_name, program_id);
|
||||||
|
|
||||||
|
codeset->CodeSegment().offset = 0;
|
||||||
|
codeset->CodeSegment().addr = program_exheader.codeset_info.text.address;
|
||||||
|
codeset->CodeSegment().size =
|
||||||
|
program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||||
|
|
||||||
|
codeset->RODataSegment().offset =
|
||||||
|
codeset->CodeSegment().offset + codeset->CodeSegment().size;
|
||||||
|
codeset->RODataSegment().addr = program_exheader.codeset_info.ro.address;
|
||||||
|
codeset->RODataSegment().size =
|
||||||
|
program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||||
|
|
||||||
|
// TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
|
||||||
|
// to the regular size. Playing it safe for now.
|
||||||
|
u32 bss_page_size = (program_exheader.codeset_info.bss_size + 0xFFF) & ~0xFFF;
|
||||||
|
code.resize(code.size() + bss_page_size, 0);
|
||||||
|
|
||||||
|
codeset->DataSegment().offset =
|
||||||
|
codeset->RODataSegment().offset + codeset->RODataSegment().size;
|
||||||
|
codeset->DataSegment().addr = program_exheader.codeset_info.data.address;
|
||||||
|
codeset->DataSegment().size =
|
||||||
|
program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE +
|
||||||
|
bss_page_size;
|
||||||
|
|
||||||
|
// Apply patches now that the entire codeset (including .bss) has been allocated
|
||||||
|
// const ResultStatus patch_result = overlay_ncch->ApplyCodePatch(code);
|
||||||
|
// if (patch_result != ResultStatus::Success && patch_result != ResultStatus::ErrorNotUsed)
|
||||||
|
// return patch_result;
|
||||||
|
|
||||||
|
codeset->entrypoint = codeset->CodeSegment().addr;
|
||||||
|
codeset->memory = std::move(code);
|
||||||
|
|
||||||
|
process = system.Kernel().CreateProcess(std::move(codeset));
|
||||||
|
|
||||||
|
// Attach a resource limit to the process based on the resource limit category
|
||||||
|
const auto category = static_cast<Kernel::ResourceLimitCategory>(
|
||||||
|
program_exheader.arm11_system_local_caps.resource_limit_category);
|
||||||
|
process->resource_limit = system.Kernel().ResourceLimit().GetForCategory(category);
|
||||||
|
|
||||||
|
// When running N3DS-unaware titles pm will lie about the amount of memory available.
|
||||||
|
// This means RESLIMIT_COMMIT = APPMEMALLOC doesn't correspond to the actual size of
|
||||||
|
// APPLICATION. See:
|
||||||
|
// https://github.com/LumaTeam/Luma3DS/blob/e2778a45/sysmodules/pm/source/launch.c#L237
|
||||||
|
auto& ncch_caps = program_exheader.arm11_system_local_caps;
|
||||||
|
const auto o3ds_mode = *LoadKernelMemoryMode().first;
|
||||||
|
const auto n3ds_mode = static_cast<Kernel::New3dsMemoryMode>(ncch_caps.n3ds_mode);
|
||||||
|
const bool is_new_3ds = Settings::values.is_new_3ds.GetValue();
|
||||||
|
if (is_new_3ds && n3ds_mode == Kernel::New3dsMemoryMode::Legacy &&
|
||||||
|
category == Kernel::ResourceLimitCategory::Application) {
|
||||||
|
u64 new_limit = 0;
|
||||||
|
switch (o3ds_mode) {
|
||||||
|
case Kernel::MemoryMode::Prod:
|
||||||
|
new_limit = 64_MiB;
|
||||||
|
break;
|
||||||
|
case Kernel::MemoryMode::Dev1:
|
||||||
|
new_limit = 96_MiB;
|
||||||
|
break;
|
||||||
|
case Kernel::MemoryMode::Dev2:
|
||||||
|
new_limit = 80_MiB;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
process->resource_limit->SetLimitValue(Kernel::ResourceLimitType::Commit,
|
||||||
|
static_cast<s32>(new_limit));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the default CPU core for this process
|
||||||
|
process->ideal_processor = program_exheader.arm11_system_local_caps.ideal_processor;
|
||||||
|
|
||||||
|
// Copy data while converting endianness
|
||||||
|
using KernelCaps = std::array<u32, ExHeader_ARM11_KernelCaps::NUM_DESCRIPTORS>;
|
||||||
|
KernelCaps kernel_caps;
|
||||||
|
std::copy_n(program_exheader.arm11_kernel_caps.descriptors, kernel_caps.size(),
|
||||||
|
begin(kernel_caps));
|
||||||
|
process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size());
|
||||||
|
|
||||||
|
s32 priority = program_exheader.arm11_system_local_caps.priority;
|
||||||
|
u32 stack_size = program_exheader.codeset_info.stack_size;
|
||||||
|
|
||||||
|
// On real HW this is done with FS:Reg, but we can be lazy
|
||||||
|
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||||
|
fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id,
|
||||||
|
"articbase://");
|
||||||
|
|
||||||
|
Service::FS::FS_USER::ProductInfo product_info{};
|
||||||
|
if (LoadProductInfo(product_info) != ResultStatus::Success) {
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
}
|
||||||
|
fs_user->RegisterProductInfo(process->process_id, product_info);
|
||||||
|
|
||||||
|
process->Run(priority, stack_size);
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Apploader_Artic::ParseRegionLockoutInfo(u64 program_id) {
|
||||||
|
if (Settings::values.region_value.GetValue() != Settings::REGION_VALUE_AUTO_SELECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
preferred_regions.clear();
|
||||||
|
|
||||||
|
std::vector<u8> smdh_buffer;
|
||||||
|
if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) {
|
||||||
|
SMDH smdh;
|
||||||
|
std::memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH));
|
||||||
|
u32 region_lockout = smdh.region_lockout;
|
||||||
|
constexpr u32 REGION_COUNT = 7;
|
||||||
|
for (u32 region = 0; region < REGION_COUNT; ++region) {
|
||||||
|
if (region_lockout & 1) {
|
||||||
|
preferred_regions.push_back(region);
|
||||||
|
}
|
||||||
|
region_lockout >>= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const auto region = Core::GetSystemTitleRegion(program_id);
|
||||||
|
if (region.has_value()) {
|
||||||
|
preferred_regions.push_back(region.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Apploader_Artic::LoadExheader() {
|
||||||
|
if (program_exheader_loaded)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_GetExheader");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto exheader_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!exheader_buf.has_value())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (exheader_buf->second != sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
u8* prg_exh = reinterpret_cast<u8*>(&program_exheader);
|
||||||
|
memcpy(prg_exh, exheader_buf->first,
|
||||||
|
sizeof(ExHeader_Header) - sizeof(ExHeader_Header::access_desc));
|
||||||
|
memcpy(prg_exh + offsetof(ExHeader_Header, access_desc.arm11_system_local_caps),
|
||||||
|
reinterpret_cast<u8*>(exheader_buf->first) +
|
||||||
|
offsetof(ExHeader_Header, arm11_system_local_caps),
|
||||||
|
offsetof(ExHeader_Header, access_desc) -
|
||||||
|
offsetof(ExHeader_Header, arm11_system_local_caps));
|
||||||
|
program_exheader_loaded = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::LoadProductInfo(Service::FS::FS_USER::ProductInfo& out_product_info) {
|
||||||
|
if (cached_product_info.has_value()) {
|
||||||
|
out_product_info = *cached_product_info;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_GetProductInfo");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value())
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto pinfo_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!pinfo_buf.has_value() || pinfo_buf->second != sizeof(Service::FS::FS_USER::ProductInfo))
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
out_product_info = *reinterpret_cast<Service::FS::FS_USER::ProductInfo*>(pinfo_buf->first);
|
||||||
|
cached_product_info = out_product_info;
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::Load(std::shared_ptr<Kernel::Process>& process) {
|
||||||
|
u64_le ncch_program_id;
|
||||||
|
|
||||||
|
if (is_loaded)
|
||||||
|
return ResultStatus::ErrorAlreadyLoaded;
|
||||||
|
|
||||||
|
ResultStatus result = ReadProgramId(ncch_program_id);
|
||||||
|
if (result != ResultStatus::Success) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string program_id{fmt::format("{:016X}", ncch_program_id)};
|
||||||
|
|
||||||
|
LOG_INFO(Loader, "Program ID: {}", program_id);
|
||||||
|
|
||||||
|
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||||
|
Network::GameInfo game_info;
|
||||||
|
ReadTitle(game_info.name);
|
||||||
|
game_info.id = ncch_program_id;
|
||||||
|
room_member->SendGameInfo(game_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
is_loaded = true; // Set state to loaded
|
||||||
|
|
||||||
|
result = LoadExec(process); // Load the executable into memory for booting
|
||||||
|
if (ResultStatus::Success != result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
system.ArchiveManager().RegisterSelfNCCH(*this);
|
||||||
|
system.ArchiveManager().RegisterArticSaveDataSource(client);
|
||||||
|
system.ArchiveManager().RegisterArticExtData(client);
|
||||||
|
system.ArchiveManager().RegisterArticNCCH(client);
|
||||||
|
|
||||||
|
auto fs_user = system.ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||||
|
fs_user->RegisterSecureValueBackend(std::make_shared<FileSys::ArticSecureValueBackend>(client));
|
||||||
|
|
||||||
|
ParseRegionLockoutInfo(ncch_program_id);
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::IsExecutable(bool& out_executable) {
|
||||||
|
out_executable = true;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadCode(std::vector<u8>& buffer) {
|
||||||
|
// Code is only read once, there is no need to cache it.
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
size_t code_size = program_exheader.codeset_info.text.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||||
|
code_size += program_exheader.codeset_info.ro.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||||
|
code_size += program_exheader.codeset_info.data.num_max_pages * Memory::CITRA_PAGE_SIZE;
|
||||||
|
|
||||||
|
size_t read_amount = 0;
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
while (read_amount != code_size) {
|
||||||
|
size_t to_read =
|
||||||
|
std::min<size_t>(client->GetServerRequestMaxSize() - 0x100, code_size - read_amount);
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_ReadCode");
|
||||||
|
req.AddParameterS32(static_cast<s32>(read_amount));
|
||||||
|
req.AddParameterS32(static_cast<s32>(to_read));
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto code_buff = resp->GetResponseBuffer(0);
|
||||||
|
if (!code_buff.has_value() || code_buff->second != to_read)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
buffer.resize(read_amount + to_read);
|
||||||
|
memcpy(buffer.data() + read_amount, code_buff->first, to_read);
|
||||||
|
read_amount += to_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadIcon(std::vector<u8>& buffer) {
|
||||||
|
if (!cached_icon.empty()) {
|
||||||
|
buffer = cached_icon;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_ReadIcon");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto icon_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!icon_buf.has_value())
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
cached_icon.resize(icon_buf->second);
|
||||||
|
memcpy(cached_icon.data(), icon_buf->first, icon_buf->second);
|
||||||
|
buffer = cached_icon;
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadBanner(std::vector<u8>& buffer) {
|
||||||
|
if (!cached_banner.empty()) {
|
||||||
|
buffer = cached_banner;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_ReadBanner");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto banner_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!banner_buf.has_value())
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
cached_banner.resize(banner_buf->second);
|
||||||
|
memcpy(cached_banner.data(), banner_buf->first, banner_buf->second);
|
||||||
|
buffer = cached_banner;
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadLogo(std::vector<u8>& buffer) {
|
||||||
|
if (!cached_logo.empty()) {
|
||||||
|
buffer = cached_logo;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_ReadLogo");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value() || !resp->Succeeded() || resp->GetMethodResult() != 0)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto logo_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!logo_buf.has_value())
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
cached_logo.resize(logo_buf->second);
|
||||||
|
memcpy(cached_logo.data(), logo_buf->first, logo_buf->second);
|
||||||
|
buffer = cached_logo;
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadProgramId(u64& out_program_id) {
|
||||||
|
if (cached_title_id.has_value()) {
|
||||||
|
out_program_id = *cached_title_id;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!client_connected)
|
||||||
|
client_connected = client->Connect();
|
||||||
|
if (!client_connected)
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto req = client->NewRequest("Process_GetTitleID");
|
||||||
|
auto resp = client->Send(req);
|
||||||
|
if (!resp.has_value())
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
auto tid_buf = resp->GetResponseBuffer(0);
|
||||||
|
if (!tid_buf.has_value() || tid_buf->second != sizeof(u64))
|
||||||
|
return ResultStatus::ErrorArtic;
|
||||||
|
|
||||||
|
out_program_id = *reinterpret_cast<u64*>(tid_buf->first);
|
||||||
|
cached_title_id = out_program_id;
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadExtdataId(u64& out_extdata_id) {
|
||||||
|
if (program_exheader.arm11_system_local_caps.storage_info.other_attributes >> 1) {
|
||||||
|
// Using extended save data access
|
||||||
|
// There would be multiple possible extdata IDs in this case. The best we can do for now is
|
||||||
|
// guessing that the first one would be the main save.
|
||||||
|
const std::array<u64, 6> extdata_ids{{
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id0.Value(),
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id1.Value(),
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id2.Value(),
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id3.Value(),
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id4.Value(),
|
||||||
|
program_exheader.arm11_system_local_caps.storage_info.extdata_id5.Value(),
|
||||||
|
}};
|
||||||
|
for (u64 id : extdata_ids) {
|
||||||
|
if (id) {
|
||||||
|
// Found a non-zero ID, use it
|
||||||
|
out_extdata_id = id;
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultStatus::ErrorNotUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_extdata_id = program_exheader.arm11_system_local_caps.storage_info.ext_save_data_id;
|
||||||
|
return Loader::ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) {
|
||||||
|
main_romfs_reader = romfs_file = std::make_shared<FileSys::ArticRomFSReader>(client, false);
|
||||||
|
return static_cast<FileSys::ArticRomFSReader*>(romfs_file.get())->OpenStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) {
|
||||||
|
update_romfs_reader = romfs_file = std::make_shared<FileSys::ArticRomFSReader>(client, true);
|
||||||
|
return static_cast<FileSys::ArticRomFSReader*>(romfs_file.get())->OpenStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::DumpRomFS(const std::string& target_path) {
|
||||||
|
return ResultStatus::ErrorNotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::DumpUpdateRomFS(const std::string& target_path) {
|
||||||
|
return ResultStatus::ErrorNotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Apploader_Artic::ReadTitle(std::string& title) {
|
||||||
|
std::vector<u8> data;
|
||||||
|
Loader::SMDH smdh;
|
||||||
|
ResultStatus result = ReadIcon(data);
|
||||||
|
if (result != ResultStatus::Success) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Loader::IsValidSMDH(data)) {
|
||||||
|
return ResultStatus::ErrorInvalidFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(&smdh, data.data(), sizeof(Loader::SMDH));
|
||||||
|
|
||||||
|
const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English);
|
||||||
|
auto title_end = std::find(short_title.begin(), short_title.end(), u'\0');
|
||||||
|
title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end});
|
||||||
|
|
||||||
|
return ResultStatus::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Loader
|
135
src/core/loader/artic.h
Normal file
135
src/core/loader/artic.h
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/file_sys/ncch_container.h"
|
||||||
|
#include "core/hle/service/fs/fs_user.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
|
#include "network/artic_base/artic_base_client.h"
|
||||||
|
|
||||||
|
namespace Loader {
|
||||||
|
|
||||||
|
/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI)
|
||||||
|
class Apploader_Artic final : public AppLoader {
|
||||||
|
public:
|
||||||
|
Apploader_Artic(Core::System& system_, const std::string& server_addr, u16 server_port)
|
||||||
|
: AppLoader(system_, FileUtil::IOFile()) {
|
||||||
|
client = std::make_shared<Network::ArticBase::Client>(server_addr, server_port);
|
||||||
|
client->SetCommunicationErrorCallback([&system_]() {
|
||||||
|
system_.SetStatus(Core::System::ResultStatus::ErrorArticDisconnected);
|
||||||
|
});
|
||||||
|
client->SetArticReportTrafficCallback(
|
||||||
|
[&system_](u32 bytes) { system_.ReportArticTraffic(bytes); });
|
||||||
|
client->SetReportArticEventCallback([&system_](u64 event) {
|
||||||
|
Core::PerfStats::PerfArticEventBits ev =
|
||||||
|
static_cast<Core::PerfStats::PerfArticEventBits>(event & 0xFFFFFFFF);
|
||||||
|
bool set = (event > 32) != 0;
|
||||||
|
system_.ReportPerfArticEvent(ev, set);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
~Apploader_Artic() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the type of the file
|
||||||
|
* @param file FileUtil::IOFile open file
|
||||||
|
* @return FileType found, or FileType::Error if this loader doesn't know it
|
||||||
|
*/
|
||||||
|
static FileType IdentifyType(FileUtil::IOFile& file);
|
||||||
|
|
||||||
|
FileType GetFileType() override {
|
||||||
|
return IdentifyType(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::span<const u32> GetPreferredRegions() const override {
|
||||||
|
return preferred_regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatus Load(std::shared_ptr<Kernel::Process>& process) override;
|
||||||
|
|
||||||
|
std::pair<std::optional<u32>, ResultStatus> LoadCoreVersion() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the Exheader and returns the system mode for this application.
|
||||||
|
* @returns A pair with the optional system mode, and and the status.
|
||||||
|
*/
|
||||||
|
std::pair<std::optional<Kernel::MemoryMode>, ResultStatus> LoadKernelMemoryMode() override;
|
||||||
|
|
||||||
|
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities()
|
||||||
|
override;
|
||||||
|
|
||||||
|
ResultStatus IsExecutable(bool& out_executable) override;
|
||||||
|
|
||||||
|
ResultStatus ReadCode(std::vector<u8>& buffer) override;
|
||||||
|
|
||||||
|
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||||
|
|
||||||
|
ResultStatus ReadBanner(std::vector<u8>& buffer) override;
|
||||||
|
|
||||||
|
ResultStatus ReadLogo(std::vector<u8>& buffer) override;
|
||||||
|
|
||||||
|
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||||
|
|
||||||
|
ResultStatus ReadExtdataId(u64& out_extdata_id) override;
|
||||||
|
|
||||||
|
ResultStatus ReadRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
|
||||||
|
|
||||||
|
ResultStatus ReadUpdateRomFS(std::shared_ptr<FileSys::RomFSReader>& romfs_file) override;
|
||||||
|
|
||||||
|
ResultStatus DumpRomFS(const std::string& target_path) override;
|
||||||
|
|
||||||
|
ResultStatus DumpUpdateRomFS(const std::string& target_path) override;
|
||||||
|
|
||||||
|
ResultStatus ReadTitle(std::string& title) override;
|
||||||
|
|
||||||
|
bool SupportsSaveStates() override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SupportsMultipleInstancesForSameFile() override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Loads .code section into memory for booting
|
||||||
|
* @param process The newly created process
|
||||||
|
* @return ResultStatus result of function
|
||||||
|
*/
|
||||||
|
ResultStatus LoadExec(std::shared_ptr<Kernel::Process>& process);
|
||||||
|
|
||||||
|
/// Reads the region lockout info in the SMDH and send it to CFG service
|
||||||
|
/// If an SMDH is not present, the program ID is compared against a list
|
||||||
|
/// of known system titles to determine the region.
|
||||||
|
void ParseRegionLockoutInfo(u64 program_id);
|
||||||
|
|
||||||
|
bool LoadExheader();
|
||||||
|
|
||||||
|
ResultStatus LoadProductInfo(Service::FS::FS_USER::ProductInfo& out);
|
||||||
|
|
||||||
|
ExHeader_Header program_exheader{};
|
||||||
|
bool program_exheader_loaded = false;
|
||||||
|
|
||||||
|
std::optional<u64> cached_title_id = std::nullopt;
|
||||||
|
std::optional<Service::FS::FS_USER::ProductInfo> cached_product_info = std::nullopt;
|
||||||
|
std::vector<u8> cached_icon;
|
||||||
|
std::vector<u8> cached_banner;
|
||||||
|
std::vector<u8> cached_logo;
|
||||||
|
|
||||||
|
std::vector<u32> preferred_regions;
|
||||||
|
|
||||||
|
std::string server_address;
|
||||||
|
std::shared_ptr<Network::ArticBase::Client> client;
|
||||||
|
bool client_connected = false;
|
||||||
|
|
||||||
|
std::shared_ptr<FileSys::RomFSReader> main_romfs_reader = nullptr;
|
||||||
|
std::shared_ptr<FileSys::RomFSReader> update_romfs_reader = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Loader
|
|
@ -9,6 +9,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
#include "core/loader/3dsx.h"
|
#include "core/loader/3dsx.h"
|
||||||
|
#include "core/loader/artic.h"
|
||||||
#include "core/loader/elf.h"
|
#include "core/loader/elf.h"
|
||||||
#include "core/loader/ncch.h"
|
#include "core/loader/ncch.h"
|
||||||
|
|
||||||
|
@ -74,6 +75,8 @@ const char* GetFileTypeString(FileType type) {
|
||||||
return "ELF";
|
return "ELF";
|
||||||
case FileType::THREEDSX:
|
case FileType::THREEDSX:
|
||||||
return "3DSX";
|
return "3DSX";
|
||||||
|
case FileType::ARTIC:
|
||||||
|
return "ARTIC";
|
||||||
case FileType::Error:
|
case FileType::Error:
|
||||||
case FileType::Unknown:
|
case FileType::Unknown:
|
||||||
break;
|
break;
|
||||||
|
@ -108,12 +111,39 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileUtil::
|
||||||
case FileType::CCI:
|
case FileType::CCI:
|
||||||
return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath);
|
return std::make_unique<AppLoader_NCCH>(system, std::move(file), filepath);
|
||||||
|
|
||||||
|
case FileType::ARTIC: {
|
||||||
|
auto strToUInt = [](const std::string& str) -> int {
|
||||||
|
char* pEnd = NULL;
|
||||||
|
unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10);
|
||||||
|
if (*pEnd)
|
||||||
|
return -1;
|
||||||
|
return static_cast<int>(ul);
|
||||||
|
};
|
||||||
|
|
||||||
|
u16 port = 5543;
|
||||||
|
std::string server_addr = filename;
|
||||||
|
auto pos = server_addr.find(":");
|
||||||
|
if (pos != server_addr.npos) {
|
||||||
|
int newVal = strToUInt(server_addr.substr(pos + 1));
|
||||||
|
if (newVal >= 0 && newVal <= 0xFFFF) {
|
||||||
|
port = static_cast<u16>(newVal);
|
||||||
|
server_addr = server_addr.substr(0, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::make_unique<Apploader_Artic>(system, server_addr, port);
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
|
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
|
||||||
|
if (filename.starts_with("articbase://")) {
|
||||||
|
return GetFileLoader(Core::System::GetInstance(), FileUtil::IOFile(), FileType::ARTIC,
|
||||||
|
filename.substr(12), "");
|
||||||
|
}
|
||||||
|
|
||||||
FileUtil::IOFile file(filename, "rb");
|
FileUtil::IOFile file(filename, "rb");
|
||||||
if (!file.IsOpen()) {
|
if (!file.IsOpen()) {
|
||||||
LOG_ERROR(Loader, "Failed to load file {}", filename);
|
LOG_ERROR(Loader, "Failed to load file {}", filename);
|
||||||
|
|
|
@ -31,6 +31,7 @@ enum class FileType {
|
||||||
CIA,
|
CIA,
|
||||||
ELF,
|
ELF,
|
||||||
THREEDSX, // 3DSX
|
THREEDSX, // 3DSX
|
||||||
|
ARTIC,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,6 +74,7 @@ enum class ResultStatus {
|
||||||
ErrorMemoryAllocationFailed,
|
ErrorMemoryAllocationFailed,
|
||||||
ErrorEncrypted,
|
ErrorEncrypted,
|
||||||
ErrorGbaTitle,
|
ErrorGbaTitle,
|
||||||
|
ErrorArtic,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
constexpr u32 MakeMagic(char a, char b, char c, char d) {
|
||||||
|
@ -264,6 +266,14 @@ public:
|
||||||
return ResultStatus::ErrorNotImplemented;
|
return ResultStatus::ErrorNotImplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool SupportsSaveStates() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool SupportsMultipleInstancesForSameFile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
FileUtil::IOFile file;
|
FileUtil::IOFile file;
|
||||||
|
|
|
@ -101,6 +101,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_
|
||||||
last_stats.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
|
last_stats.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
|
||||||
static_cast<double>(system_frames);
|
static_cast<double>(system_frames);
|
||||||
last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0;
|
last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0;
|
||||||
|
last_stats.artic_transmitted = static_cast<double>(artic_transmitted) / interval;
|
||||||
|
last_stats.artic_events.raw = artic_events.raw | prev_artic_event.raw;
|
||||||
|
|
||||||
// Reset counters
|
// Reset counters
|
||||||
reset_point = now;
|
reset_point = now;
|
||||||
|
@ -108,6 +110,8 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_
|
||||||
accumulated_frametime = Clock::duration::zero();
|
accumulated_frametime = Clock::duration::zero();
|
||||||
system_frames = 0;
|
system_frames = 0;
|
||||||
game_frames = 0;
|
game_frames = 0;
|
||||||
|
artic_transmitted = 0;
|
||||||
|
prev_artic_event.raw &= artic_events.raw;
|
||||||
|
|
||||||
return last_stats;
|
return last_stats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include "common/bit_field.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
|
|
||||||
|
@ -25,6 +26,28 @@ public:
|
||||||
|
|
||||||
using Clock = std::chrono::high_resolution_clock;
|
using Clock = std::chrono::high_resolution_clock;
|
||||||
|
|
||||||
|
enum class PerfArticEventBits {
|
||||||
|
NONE = 0,
|
||||||
|
ARTIC_SAVE_DATA = (1 << 0),
|
||||||
|
ARTIC_EXT_DATA = (1 << 1),
|
||||||
|
ARTIC_BOSS_EXT_DATA = (1 << 2),
|
||||||
|
ARTIC_SHARED_EXT_DATA = (1 << 3),
|
||||||
|
};
|
||||||
|
union PerfArticEvents {
|
||||||
|
u32 raw{};
|
||||||
|
BitField<0, 1, u32> artic_save_data;
|
||||||
|
BitField<1, 1, u32> artic_ext_data;
|
||||||
|
BitField<2, 1, u32> artic_boss_ext_data;
|
||||||
|
BitField<3, 1, u32> artic_shared_ext_data;
|
||||||
|
|
||||||
|
void Set(PerfArticEventBits event, bool set) {
|
||||||
|
raw = (raw & ~static_cast<u32>(event)) | (set ? static_cast<u32>(event) : 0);
|
||||||
|
}
|
||||||
|
bool Get(PerfArticEventBits event) {
|
||||||
|
return (raw & static_cast<u32>(event)) != 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct Results {
|
struct Results {
|
||||||
/// System FPS (LCD VBlanks) in Hz
|
/// System FPS (LCD VBlanks) in Hz
|
||||||
double system_fps;
|
double system_fps;
|
||||||
|
@ -34,6 +57,10 @@ public:
|
||||||
double frametime;
|
double frametime;
|
||||||
/// Ratio of walltime / emulated time elapsed
|
/// Ratio of walltime / emulated time elapsed
|
||||||
double emulation_speed;
|
double emulation_speed;
|
||||||
|
/// Artic base bytes per second
|
||||||
|
double artic_transmitted = 0;
|
||||||
|
/// Artic base events
|
||||||
|
PerfArticEvents artic_events{};
|
||||||
};
|
};
|
||||||
|
|
||||||
void BeginSystemFrame();
|
void BeginSystemFrame();
|
||||||
|
@ -55,6 +82,19 @@ public:
|
||||||
*/
|
*/
|
||||||
double GetLastFrameTimeScale() const;
|
double GetLastFrameTimeScale() const;
|
||||||
|
|
||||||
|
void AddArticBaseTraffic(u32 bytes) {
|
||||||
|
artic_transmitted += bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportPerfArticEvent(PerfArticEventBits event, bool set) {
|
||||||
|
if (set) {
|
||||||
|
artic_events.Set(event, set);
|
||||||
|
prev_artic_event.Set(event, set);
|
||||||
|
} else {
|
||||||
|
artic_events.Set(event, set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mutable std::mutex object_mutex;
|
mutable std::mutex object_mutex;
|
||||||
|
|
||||||
|
@ -77,6 +117,12 @@ private:
|
||||||
u32 system_frames = 0;
|
u32 system_frames = 0;
|
||||||
/// Cumulative number of game frames (GSP frame submissions) since last reset
|
/// Cumulative number of game frames (GSP frame submissions) since last reset
|
||||||
u32 game_frames = 0;
|
u32 game_frames = 0;
|
||||||
|
/// Cumulative number of transmitted artic base traffic
|
||||||
|
std::atomic<u32> artic_transmitted = 0;
|
||||||
|
// System events that affect performance
|
||||||
|
PerfArticEvents artic_events;
|
||||||
|
|
||||||
|
PerfArticEvents prev_artic_event;
|
||||||
|
|
||||||
/// Point when the previous system frame ended
|
/// Point when the previous system frame ended
|
||||||
Clock::time_point previous_frame_end = reset_point;
|
Clock::time_point previous_frame_end = reset_point;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "common/zstd_compression.h"
|
#include "common/zstd_compression.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/loader/loader.h"
|
||||||
#include "core/movie.h"
|
#include "core/movie.h"
|
||||||
#include "core/savestate.h"
|
#include "core/savestate.h"
|
||||||
#include "core/savestate_data.h"
|
#include "core/savestate_data.h"
|
||||||
|
@ -122,6 +123,12 @@ std::vector<SaveStateInfo> ListSaveStates(u64 program_id, u64 movie_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::SaveState(u32 slot) const {
|
void System::SaveState(u32 slot) const {
|
||||||
|
if (app_loader) {
|
||||||
|
if (!app_loader->SupportsSaveStates()) {
|
||||||
|
throw std::runtime_error("The current app loader doesn't support save states");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::ostringstream sstream{std::ios_base::binary};
|
std::ostringstream sstream{std::ios_base::binary};
|
||||||
// Serialize
|
// Serialize
|
||||||
oarchive oa{sstream};
|
oarchive oa{sstream};
|
||||||
|
@ -164,6 +171,11 @@ void System::SaveState(u32 slot) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::LoadState(u32 slot) {
|
void System::LoadState(u32 slot) {
|
||||||
|
if (app_loader) {
|
||||||
|
if (!app_loader->SupportsSaveStates()) {
|
||||||
|
throw std::runtime_error("The current app loader doesn't support save states");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Network::GetRoomMember().lock()->IsConnected()) {
|
if (Network::GetRoomMember().lock()->IsConnected()) {
|
||||||
throw std::runtime_error("Unable to load while connected to multiplayer");
|
throw std::runtime_error("Unable to load while connected to multiplayer");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
add_library(network STATIC
|
add_library(network STATIC
|
||||||
announce_multiplayer_session.cpp
|
announce_multiplayer_session.cpp
|
||||||
announce_multiplayer_session.h
|
announce_multiplayer_session.h
|
||||||
|
artic_base/artic_base_client.cpp
|
||||||
|
artic_base/artic_base_client.h
|
||||||
|
artic_base/artic_base_common.h
|
||||||
network.cpp
|
network.cpp
|
||||||
network.h
|
network.h
|
||||||
network_settings.cpp
|
network_settings.cpp
|
||||||
|
@ -12,6 +15,8 @@ add_library(network STATIC
|
||||||
room.h
|
room.h
|
||||||
room_member.cpp
|
room_member.cpp
|
||||||
room_member.h
|
room_member.h
|
||||||
|
socket_manager.cpp
|
||||||
|
socket_manager.h
|
||||||
verify_user.cpp
|
verify_user.cpp
|
||||||
verify_user.h
|
verify_user.h
|
||||||
)
|
)
|
||||||
|
|
735
src/network/artic_base/artic_base_client.cpp
Normal file
735
src/network/artic_base/artic_base_client.cpp
Normal file
|
@ -0,0 +1,735 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "artic_base_client.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
#include "chrono"
|
||||||
|
#include "limits.h"
|
||||||
|
#include "memory"
|
||||||
|
#include "sstream"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#else
|
||||||
|
#include <cerrno>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define WSAEAGAIN WSAEWOULDBLOCK
|
||||||
|
#define WSAEMULTIHOP -1 // Invalid dummy value
|
||||||
|
#define ERRNO(x) WSA##x
|
||||||
|
#define GET_ERRNO WSAGetLastError()
|
||||||
|
#define poll(x, y, z) WSAPoll(x, y, z);
|
||||||
|
#define SHUT_RD SD_RECEIVE
|
||||||
|
#define SHUT_WR SD_SEND
|
||||||
|
#define SHUT_RDWR SD_BOTH
|
||||||
|
#else
|
||||||
|
#define ERRNO(x) x
|
||||||
|
#define GET_ERRNO errno
|
||||||
|
#define closesocket(x) close(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #define DISABLE_PING_TIMEOUT
|
||||||
|
|
||||||
|
namespace Network::ArticBase {
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
bool Client::Request::AddParameterS8(s8 parameter) {
|
||||||
|
if (parameters.size() >= max_param_count) {
|
||||||
|
LOG_ERROR(Network, "Too many parameters added to method: {}", method_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& param = parameters.emplace_back();
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_8;
|
||||||
|
std::memcpy(param.data, ¶meter, sizeof(s8));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Request::AddParameterS16(s16 parameter) {
|
||||||
|
if (parameters.size() >= max_param_count) {
|
||||||
|
LOG_ERROR(Network, "Too many parameters added to method: {}", method_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& param = parameters.emplace_back();
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_16;
|
||||||
|
std::memcpy(param.data, ¶meter, sizeof(s16));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Request::AddParameterS32(s32 parameter) {
|
||||||
|
if (parameters.size() >= max_param_count) {
|
||||||
|
LOG_ERROR(Network, "Too many parameters added to method: {}", method_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& param = parameters.emplace_back();
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_32;
|
||||||
|
std::memcpy(param.data, ¶meter, sizeof(s32));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Request::AddParameterS64(s64 parameter) {
|
||||||
|
if (parameters.size() >= max_param_count) {
|
||||||
|
LOG_ERROR(Network, "Too many parameters added to method: {}", method_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& param = parameters.emplace_back();
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_INTEGER_64;
|
||||||
|
std::memcpy(param.data, ¶meter, sizeof(s64));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Request::AddParameterBuffer(const void* buffer, size_t size) {
|
||||||
|
if (parameters.size() >= max_param_count) {
|
||||||
|
LOG_ERROR(Network, "Too many parameters added to method: {}", method_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto& param = parameters.emplace_back();
|
||||||
|
if (size <= sizeof(param.data)) {
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_SMALL_BUFFER;
|
||||||
|
std::memcpy(param.data, buffer, size);
|
||||||
|
param.parameterSize = static_cast<u16>(size);
|
||||||
|
} else {
|
||||||
|
param.type = ArticBaseCommon::RequestParameterType::IN_BIG_BUFFER;
|
||||||
|
param.bigBufferID = static_cast<u16>(pending_big_buffers.size());
|
||||||
|
s32 size_32 = static_cast<s32>(size);
|
||||||
|
std::memcpy(param.data, &size_32, sizeof(size_32));
|
||||||
|
pending_big_buffers.push_back(std::make_pair(buffer, size));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::Request::Request(u32 request_id, const std::string& method, size_t max_params) {
|
||||||
|
method_name = method;
|
||||||
|
max_param_count = max_params;
|
||||||
|
request_packet.requestID = request_id;
|
||||||
|
std::memcpy(request_packet.method.data(), method.data(),
|
||||||
|
std::min<size_t>(request_packet.method.size(), method.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::~Client() {
|
||||||
|
StopImpl(false);
|
||||||
|
|
||||||
|
for (auto it = handlers.begin(); it != handlers.end(); it++) {
|
||||||
|
Handler* h = *it;
|
||||||
|
h->thread->join();
|
||||||
|
delete h;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ping_thread.joinable()) {
|
||||||
|
ping_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketManager::DisableSockets();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Connect() {
|
||||||
|
if (connected)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
auto str_to_int = [](const std::string& str) -> int {
|
||||||
|
char* pEnd = NULL;
|
||||||
|
unsigned long ul = ::strtoul(str.c_str(), &pEnd, 10);
|
||||||
|
if (*pEnd)
|
||||||
|
return -1;
|
||||||
|
return static_cast<int>(ul);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct addrinfo hints, *addrinfo;
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
|
||||||
|
LOG_INFO(Network, "Starting Artic Base Client");
|
||||||
|
|
||||||
|
if (getaddrinfo(address.data(), NULL, &hints, &addrinfo) != 0) {
|
||||||
|
LOG_ERROR(Network, "Failed to get server address");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
main_socket = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (main_socket == -1) {
|
||||||
|
LOG_ERROR(Network, "Failed to create socket");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetNonBlock(main_socket, true)) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Cannot set non-blocking socket mode");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in servaddr = {0};
|
||||||
|
servaddr.sin_family = AF_INET;
|
||||||
|
servaddr.sin_addr.s_addr = ((struct sockaddr_in*)(addrinfo->ai_addr))->sin_addr.s_addr;
|
||||||
|
servaddr.sin_port = htons(port);
|
||||||
|
freeaddrinfo(addrinfo);
|
||||||
|
|
||||||
|
if (!ConnectWithTimeout(main_socket, &servaddr, sizeof(servaddr), 10)) {
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Failed to connect");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto version = SendSimpleRequest("VERSION");
|
||||||
|
if (version.has_value()) {
|
||||||
|
int version_value = str_to_int(*version);
|
||||||
|
if (version_value != SERVER_VERSION) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Incompatible server version: {}", version_value);
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't fetch server version.");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto max_work_size = SendSimpleRequest("MAXSIZE");
|
||||||
|
int max_work_size_value = -1;
|
||||||
|
if (max_work_size.has_value()) {
|
||||||
|
max_work_size_value = str_to_int(*max_work_size);
|
||||||
|
}
|
||||||
|
if (max_work_size_value < 0) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't fetch server work ram size");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
max_server_work_ram = max_work_size_value;
|
||||||
|
|
||||||
|
auto max_params = SendSimpleRequest("MAXPARAM");
|
||||||
|
int max_param_value = -1;
|
||||||
|
if (max_params.has_value()) {
|
||||||
|
max_param_value = str_to_int(*max_params);
|
||||||
|
}
|
||||||
|
if (max_param_value < 0) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't fetch server max params");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
max_parameter_count = max_param_value;
|
||||||
|
|
||||||
|
auto worker_ports = SendSimpleRequest("PORTS");
|
||||||
|
if (!worker_ports.has_value()) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't fetch server worker ports");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<u16> ports;
|
||||||
|
std::string str_port;
|
||||||
|
std::stringstream ss_port(worker_ports.value());
|
||||||
|
while (std::getline(ss_port, str_port, ',')) {
|
||||||
|
int port = str_to_int(str_port);
|
||||||
|
if (port < 0 || port > USHRT_MAX) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't parse server worker ports");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ports.push_back(static_cast<u16>(port));
|
||||||
|
}
|
||||||
|
if (ports.empty()) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't parse server worker ports");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 101; i++) {
|
||||||
|
auto ready_server = SendSimpleRequest("READY");
|
||||||
|
if (!ready_server.has_value() || i == 100) {
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
LOG_ERROR(Network, "Couldn't fetch server readiness");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (*ready_server == "1")
|
||||||
|
break;
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
ping_thread = std::thread(&Client::PingFunction, this);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
running_handlers = ports.size();
|
||||||
|
for (auto it = ports.begin(); it != ports.end(); it++) {
|
||||||
|
handlers.push_back(new Handler(*this, static_cast<u32>(servaddr.sin_addr.s_addr), *it, i));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
connected = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::StopImpl(bool from_error) {
|
||||||
|
bool expected = false;
|
||||||
|
if (!stopped.compare_exchange_strong(expected, true))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!from_error) {
|
||||||
|
SendSimpleRequest("STOP");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ping_thread.joinable()) {
|
||||||
|
std::scoped_lock l2(ping_cv_mutex);
|
||||||
|
ping_run = false;
|
||||||
|
ping_cv.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop handlers
|
||||||
|
for (auto it = handlers.begin(); it != handlers.end(); it++) {
|
||||||
|
Handler* handler = *it;
|
||||||
|
handler->should_run = false;
|
||||||
|
// Shouldn't matter if the socket is shut down twice
|
||||||
|
shutdown(handler->handler_socket, SHUT_RDWR);
|
||||||
|
closesocket(handler->handler_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close main socket
|
||||||
|
shutdown(main_socket, SHUT_RDWR);
|
||||||
|
closesocket(main_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<void*, size_t>> Client::Response::GetResponseBuffer(u32 buffer_id) const {
|
||||||
|
if (!resp_data_buffer)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
char* resp_data_buffer_end = resp_data_buffer + resp_data_size;
|
||||||
|
char* resp_data_buffer_start = resp_data_buffer;
|
||||||
|
while (resp_data_buffer_start + sizeof(ArticBaseCommon::Buffer) < resp_data_buffer_end) {
|
||||||
|
ArticBaseCommon::Buffer* curr_buffer =
|
||||||
|
reinterpret_cast<ArticBaseCommon::Buffer*>(resp_data_buffer_start);
|
||||||
|
resp_data_buffer_start += sizeof(ArticBaseCommon::Buffer);
|
||||||
|
if (curr_buffer->bufferID == buffer_id) {
|
||||||
|
if (curr_buffer->data + curr_buffer->bufferSize <= resp_data_buffer_end) {
|
||||||
|
return std::make_pair(curr_buffer->data, curr_buffer->bufferSize);
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp_data_buffer_start += curr_buffer->bufferSize;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Client::Response> Client::Send(Request& request) {
|
||||||
|
if (stopped)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
request.request_packet.parameterCount = static_cast<u32>(request.parameters.size());
|
||||||
|
PendingResponse resp(request);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock l(recv_map_mutex);
|
||||||
|
pending_responses[request.request_packet.requestID] = &resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto respPacket = SendRequestPacket(request.request_packet, false, request.parameters);
|
||||||
|
if (stopped || !respPacket.has_value()) {
|
||||||
|
std::scoped_lock l(recv_map_mutex);
|
||||||
|
pending_responses.erase(request.request_packet.requestID);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock cv_lk(resp.cv_mutex);
|
||||||
|
resp.cv.wait(cv_lk, [&resp]() { return resp.is_done; });
|
||||||
|
|
||||||
|
return std::optional<Client::Response>(std::move(resp.response));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::SignalCommunicationError() {
|
||||||
|
StopImpl(true);
|
||||||
|
LOG_CRITICAL(Network, "Communication error");
|
||||||
|
if (communication_error_callback)
|
||||||
|
communication_error_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::PingFunction() {
|
||||||
|
// Max silence time => 7 secs interval + 3 secs wait + 10 seconds timeout = 25 seconds
|
||||||
|
while (ping_run) {
|
||||||
|
std::chrono::time_point<std::chrono::steady_clock> last = last_sent_request;
|
||||||
|
if (std::chrono::steady_clock::now() - last > std::chrono::seconds(7)) {
|
||||||
|
#ifdef DISABLE_PING_TIMEOUT
|
||||||
|
client->last_sent_request = std::chrono::steady_clock::now();
|
||||||
|
#else
|
||||||
|
auto ping_reply = SendSimpleRequest("PING");
|
||||||
|
if (!ping_reply.has_value()) {
|
||||||
|
SignalCommunicationError();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
#endif // DISABLE_PING_TIMEOUT
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock lk(ping_cv_mutex);
|
||||||
|
ping_cv.wait_for(lk, std::chrono::seconds(3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::ConnectWithTimeout(SocketHolder sockFD, void* server_addr, size_t server_addr_len,
|
||||||
|
int timeout_seconds) {
|
||||||
|
|
||||||
|
int res = ::connect(sockFD, (struct sockaddr*)server_addr, static_cast<int>(server_addr_len));
|
||||||
|
if (res == -1 && ((GET_ERRNO == ERRNO(EINPROGRESS) || GET_ERRNO == ERRNO(EWOULDBLOCK)))) {
|
||||||
|
struct timeval tv;
|
||||||
|
fd_set fdset;
|
||||||
|
FD_ZERO(&fdset);
|
||||||
|
FD_SET(sockFD, &fdset);
|
||||||
|
|
||||||
|
tv.tv_sec = timeout_seconds;
|
||||||
|
tv.tv_usec = 0;
|
||||||
|
int select_res = ::select(static_cast<int>(sockFD + 1), NULL, &fdset, NULL, &tv);
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (select_res == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool select_good = false;
|
||||||
|
if (select_res == 1) {
|
||||||
|
int so_error;
|
||||||
|
socklen_t len = sizeof so_error;
|
||||||
|
|
||||||
|
getsockopt(sockFD, SOL_SOCKET, SO_ERROR, &so_error, &len);
|
||||||
|
|
||||||
|
if (so_error == 0) {
|
||||||
|
select_good = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!select_good) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
} else if (res == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::SetNonBlock(SocketHolder sockFD, bool nonBlocking) {
|
||||||
|
bool blocking = !nonBlocking;
|
||||||
|
#ifdef _WIN32
|
||||||
|
unsigned long nonblocking = (blocking) ? 0 : 1;
|
||||||
|
int ret = ioctlsocket(sockFD, FIONBIO, &nonblocking);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int flags = ::fcntl(sockFD, F_GETFL, 0);
|
||||||
|
if (flags == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags &= ~O_NONBLOCK;
|
||||||
|
if (!blocking) { // O_NONBLOCK
|
||||||
|
flags |= O_NONBLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int ret = ::fcntl(sockFD, F_SETFL, flags);
|
||||||
|
if (ret == -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Read(SocketHolder sockFD, void* buffer, size_t size,
|
||||||
|
const std::chrono::nanoseconds& timeout) {
|
||||||
|
size_t read_bytes = 0;
|
||||||
|
auto before = std::chrono::steady_clock::now();
|
||||||
|
while (read_bytes != size) {
|
||||||
|
int new_read =
|
||||||
|
::recv(sockFD, (char*)((uintptr_t)buffer + read_bytes), (int)(size - read_bytes), 0);
|
||||||
|
if (new_read < 0) {
|
||||||
|
if (GET_ERRNO == ERRNO(EWOULDBLOCK) &&
|
||||||
|
(timeout == std::chrono::nanoseconds(0) ||
|
||||||
|
std::chrono::steady_clock::now() - before < timeout)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
read_bytes = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (report_traffic_callback && new_read) {
|
||||||
|
report_traffic_callback(new_read);
|
||||||
|
}
|
||||||
|
read_bytes += new_read;
|
||||||
|
}
|
||||||
|
return read_bytes == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::Write(SocketHolder sockFD, const void* buffer, size_t size,
|
||||||
|
const std::chrono::nanoseconds& timeout) {
|
||||||
|
size_t write_bytes = 0;
|
||||||
|
auto before = std::chrono::steady_clock::now();
|
||||||
|
while (write_bytes != size) {
|
||||||
|
int new_written = ::send(sockFD, (const char*)((uintptr_t)buffer + write_bytes),
|
||||||
|
(int)(size - write_bytes), 0);
|
||||||
|
if (new_written < 0) {
|
||||||
|
if (GET_ERRNO == ERRNO(EWOULDBLOCK) &&
|
||||||
|
(timeout == std::chrono::nanoseconds(0) ||
|
||||||
|
std::chrono::steady_clock::now() - before < timeout)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
write_bytes = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (report_traffic_callback && new_written) {
|
||||||
|
report_traffic_callback(new_written);
|
||||||
|
}
|
||||||
|
write_bytes += new_written;
|
||||||
|
}
|
||||||
|
return write_bytes == size;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ArticBaseCommon::DataPacket> Client::SendRequestPacket(
|
||||||
|
const ArticBaseCommon::RequestPacket& req, bool expect_response,
|
||||||
|
const std::vector<ArticBaseCommon::RequestParameter>& params,
|
||||||
|
const std::chrono::nanoseconds& read_timeout) {
|
||||||
|
std::scoped_lock<std::mutex> l(send_mutex);
|
||||||
|
|
||||||
|
if (main_socket == -1) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Write(main_socket, &req, sizeof(req))) {
|
||||||
|
LOG_WARNING(Network, "Failed to write to socket");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params.empty()) {
|
||||||
|
if (!Write(main_socket, params.data(),
|
||||||
|
params.size() * sizeof(ArticBaseCommon::RequestParameter))) {
|
||||||
|
LOG_WARNING(Network, "Failed to write to socket");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ArticBaseCommon::DataPacket resp;
|
||||||
|
if (expect_response) {
|
||||||
|
if (!Read(main_socket, &resp, sizeof(resp), read_timeout)) {
|
||||||
|
LOG_WARNING(Network, "Failed to read from socket");
|
||||||
|
SignalCommunicationError();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resp.requestID != req.requestID) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_sent_request = std::chrono::steady_clock::now();
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> Client::SendSimpleRequest(const std::string& method) {
|
||||||
|
ArticBaseCommon::RequestPacket req{};
|
||||||
|
req.requestID = GetNextRequestID();
|
||||||
|
const std::string final_method = "$" + method;
|
||||||
|
if (final_method.size() > sizeof(req.method)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::memcpy(req.method.data(), final_method.data(), final_method.size());
|
||||||
|
auto resp = SendRequestPacket(req, true, {}, std::chrono::seconds(10));
|
||||||
|
if (!resp.has_value() || resp->requestID != req.requestID) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
char respBody[sizeof(ArticBaseCommon::DataPacket::dataRaw) + 1] = {0};
|
||||||
|
std::memcpy(respBody, resp->dataRaw, sizeof(ArticBaseCommon::DataPacket::dataRaw));
|
||||||
|
return respBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::Handler::Handler(Client& _client, u32 _addr, u16 _port, int _id)
|
||||||
|
: id(_id), client(_client), addr(_addr), port(_port) {
|
||||||
|
thread = new std::thread(
|
||||||
|
[](Handler* handler) {
|
||||||
|
handler->RunLoop();
|
||||||
|
handler->should_run = false;
|
||||||
|
if (--handler->client.running_handlers == 0) {
|
||||||
|
handler->client.OnAllHandlersFinished();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::Handler::RunLoop() {
|
||||||
|
handler_socket = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (handler_socket == -1) {
|
||||||
|
LOG_ERROR(Network, "Failed to create socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SetNonBlock(handler_socket, true)) {
|
||||||
|
closesocket(handler_socket);
|
||||||
|
client.SignalCommunicationError();
|
||||||
|
LOG_ERROR(Network, "Cannot set non-blocking socket mode");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in servaddr = {0};
|
||||||
|
servaddr.sin_family = AF_INET;
|
||||||
|
servaddr.sin_addr.s_addr = static_cast<decltype(servaddr.sin_addr.s_addr)>(addr);
|
||||||
|
servaddr.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (!ConnectWithTimeout(handler_socket, &servaddr, sizeof(servaddr), 10)) {
|
||||||
|
closesocket(handler_socket);
|
||||||
|
LOG_ERROR(Network, "Failed to connect");
|
||||||
|
client.SignalCommunicationError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto signal_error = [&] {
|
||||||
|
if (should_run) {
|
||||||
|
client.SignalCommunicationError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ArticBaseCommon::DataPacket dataPacket;
|
||||||
|
u32 retry_count = 0;
|
||||||
|
while (should_run) {
|
||||||
|
if (!client.Read(handler_socket, &dataPacket, sizeof(dataPacket))) {
|
||||||
|
if (should_run) {
|
||||||
|
LOG_WARNING(Network, "Failed to read from socket");
|
||||||
|
std::this_thread::sleep_for(100ms);
|
||||||
|
if (++retry_count == 300) {
|
||||||
|
signal_error();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
retry_count = 0;
|
||||||
|
|
||||||
|
PendingResponse* pending_response;
|
||||||
|
{
|
||||||
|
std::scoped_lock l(client.recv_map_mutex);
|
||||||
|
auto it = client.pending_responses.find(dataPacket.requestID);
|
||||||
|
if (it == client.pending_responses.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pending_response = it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dataPacket.resp.articResult) {
|
||||||
|
case ArticBaseCommon::ResponseMethod::ArticResult::SUCCESS: {
|
||||||
|
pending_response->response.articResult = dataPacket.resp.articResult;
|
||||||
|
pending_response->response.methodResult = dataPacket.resp.methodResult;
|
||||||
|
if (dataPacket.resp.bufferSize) {
|
||||||
|
pending_response->response.resp_data_buffer =
|
||||||
|
reinterpret_cast<char*>(operator new(dataPacket.resp.bufferSize));
|
||||||
|
ASSERT_MSG(pending_response->response.resp_data_buffer != nullptr,
|
||||||
|
"ArticBase Handler: Cannot allocate buffer");
|
||||||
|
pending_response->response.resp_data_size =
|
||||||
|
static_cast<size_t>(dataPacket.resp.bufferSize);
|
||||||
|
if (!client.Read(handler_socket, pending_response->response.resp_data_buffer,
|
||||||
|
dataPacket.resp.bufferSize)) {
|
||||||
|
signal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case ArticBaseCommon::ResponseMethod::ArticResult::METHOD_NOT_FOUND: {
|
||||||
|
LOG_ERROR(Network, "Method {} not found by server",
|
||||||
|
pending_response->request.method_name);
|
||||||
|
pending_response->response.articResult = dataPacket.resp.articResult;
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case ArticBaseCommon::ResponseMethod::ArticResult::PROVIDE_INPUT: {
|
||||||
|
size_t bufferID = static_cast<size_t>(dataPacket.resp.provideInputBufferID);
|
||||||
|
if (bufferID >= pending_response->request.pending_big_buffers.size() ||
|
||||||
|
pending_response->request.pending_big_buffers[bufferID].second !=
|
||||||
|
static_cast<size_t>(dataPacket.resp.bufferSize)) {
|
||||||
|
LOG_ERROR(Network, "Method {} incorrect big buffer state {}",
|
||||||
|
pending_response->request.method_name, bufferID);
|
||||||
|
dataPacket.resp.articResult =
|
||||||
|
ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR;
|
||||||
|
if (client.Write(handler_socket, &dataPacket, sizeof(dataPacket))) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
signal_error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto& buffer = pending_response->request.pending_big_buffers[bufferID];
|
||||||
|
if (client.Write(handler_socket, &dataPacket, sizeof(dataPacket))) {
|
||||||
|
if (client.Write(handler_socket, buffer.first, buffer.second)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
signal_error();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signal_error();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR:
|
||||||
|
default: {
|
||||||
|
LOG_ERROR(Network, "Method {} error {}", pending_response->request.method_name,
|
||||||
|
dataPacket.resp.methodResult);
|
||||||
|
pending_response->response.articResult = dataPacket.resp.articResult;
|
||||||
|
pending_response->response.methodState =
|
||||||
|
static_cast<ArticBaseCommon::MethodState>(dataPacket.resp.methodResult);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock l(client.recv_map_mutex);
|
||||||
|
client.pending_responses.erase(dataPacket.requestID);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::scoped_lock<std::mutex> lk(pending_response->cv_mutex);
|
||||||
|
pending_response->is_done = true;
|
||||||
|
pending_response->cv.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
should_run = false;
|
||||||
|
shutdown(handler_socket, SHUT_RDWR);
|
||||||
|
closesocket(handler_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::OnAllHandlersFinished() {
|
||||||
|
// If no handlers are running, signal all pending requests so that
|
||||||
|
// they don't become stuck.
|
||||||
|
std::scoped_lock l(recv_map_mutex);
|
||||||
|
for (auto& [id, response] : pending_responses) {
|
||||||
|
std::scoped_lock l2(response->cv_mutex);
|
||||||
|
response->is_done = true;
|
||||||
|
response->cv.notify_one();
|
||||||
|
}
|
||||||
|
pending_responses.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Network::ArticBase
|
288
src/network/artic_base/artic_base_client.h
Normal file
288
src/network/artic_base/artic_base_client.h
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "condition_variable"
|
||||||
|
#include "cstring"
|
||||||
|
#include "functional"
|
||||||
|
#include "map"
|
||||||
|
#include "memory"
|
||||||
|
#include "mutex"
|
||||||
|
#include "optional"
|
||||||
|
#include "string"
|
||||||
|
#include "thread"
|
||||||
|
#include "utility"
|
||||||
|
|
||||||
|
#include "artic_base_common.h"
|
||||||
|
#include "network/socket_manager.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
using SocketHolder = unsigned long long;
|
||||||
|
#else
|
||||||
|
using SocketHolder = int;
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
namespace Network::ArticBase {
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
class Request {
|
||||||
|
public:
|
||||||
|
bool AddParameterS8(s8 parameter);
|
||||||
|
bool AddParameterU8(u8 parameter) {
|
||||||
|
return AddParameterS8(static_cast<s8>(parameter));
|
||||||
|
}
|
||||||
|
bool AddParameterS16(s16 parameter);
|
||||||
|
bool AddParameterU16(u16 parameter) {
|
||||||
|
return AddParameterS16(static_cast<s16>(parameter));
|
||||||
|
}
|
||||||
|
bool AddParameterS32(s32 parameter);
|
||||||
|
bool AddParameterU32(u32 parameter) {
|
||||||
|
return AddParameterS32(static_cast<s32>(parameter));
|
||||||
|
}
|
||||||
|
bool AddParameterS64(s64 parameter);
|
||||||
|
bool AddParameterU64(u64 parameter) {
|
||||||
|
return AddParameterS64(static_cast<s64>(parameter));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Buffer pointer must remain alive until the response is received
|
||||||
|
bool AddParameterBuffer(const void* buffer, size_t bufferSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Client;
|
||||||
|
Request(u32 request_id, const std::string& method, size_t max_params);
|
||||||
|
|
||||||
|
ArticBaseCommon::RequestPacket request_packet{};
|
||||||
|
std::vector<ArticBaseCommon::RequestParameter> parameters;
|
||||||
|
std::string method_name;
|
||||||
|
size_t max_param_count;
|
||||||
|
std::vector<std::pair<const void*, size_t>> pending_big_buffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
Client(const std::string& _address, u16 _port) : address(_address), port(_port) {
|
||||||
|
SocketManager::EnableSockets();
|
||||||
|
}
|
||||||
|
~Client();
|
||||||
|
|
||||||
|
bool Connect();
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
|
size_t GetServerRequestMaxSize() {
|
||||||
|
return max_server_work_ram;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request NewRequest(const std::string& method) {
|
||||||
|
return Request(GetNextRequestID(), method, max_parameter_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Stop() {
|
||||||
|
StopImpl(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCommunicationErrorCallback(const std::function<void()>& callback) {
|
||||||
|
communication_error_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetArticReportTrafficCallback(const std::function<void(s32)>& callback) {
|
||||||
|
report_traffic_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReportArticEvent(u64 event) {
|
||||||
|
if (report_artic_event_callback) {
|
||||||
|
report_artic_event_callback(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void SetReportArticEventCallback(const std::function<void(u64)>& callback) {
|
||||||
|
report_artic_event_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const int SERVER_VERSION = 0;
|
||||||
|
|
||||||
|
std::string address;
|
||||||
|
u16 port;
|
||||||
|
|
||||||
|
SocketHolder main_socket = -1;
|
||||||
|
std::atomic<u32> currRequestID;
|
||||||
|
u32 GetNextRequestID() {
|
||||||
|
return currRequestID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalCommunicationError();
|
||||||
|
std::function<void()> communication_error_callback;
|
||||||
|
|
||||||
|
std::function<void(u64)> report_artic_event_callback;
|
||||||
|
|
||||||
|
size_t max_server_work_ram = 0;
|
||||||
|
size_t max_parameter_count = 0;
|
||||||
|
std::mutex send_mutex;
|
||||||
|
|
||||||
|
std::atomic<bool> stopped = false;
|
||||||
|
|
||||||
|
std::atomic<std::chrono::time_point<std::chrono::steady_clock>> last_sent_request;
|
||||||
|
std::thread ping_thread;
|
||||||
|
std::condition_variable ping_cv;
|
||||||
|
std::mutex ping_cv_mutex;
|
||||||
|
bool ping_run = true;
|
||||||
|
|
||||||
|
void StopImpl(bool from_error);
|
||||||
|
|
||||||
|
void PingFunction();
|
||||||
|
|
||||||
|
static bool ConnectWithTimeout(SocketHolder sockFD, void* server_addr, size_t server_addr_len,
|
||||||
|
int timeout_seconds);
|
||||||
|
static bool SetNonBlock(SocketHolder sockFD, bool blocking);
|
||||||
|
bool Read(SocketHolder sockFD, void* buffer, size_t size,
|
||||||
|
const std::chrono::nanoseconds& timeout = std::chrono::nanoseconds(0));
|
||||||
|
bool Write(SocketHolder sockFD, const void* buffer, size_t size,
|
||||||
|
const std::chrono::nanoseconds& timeout = std::chrono::nanoseconds(0));
|
||||||
|
std::function<void(u32)> report_traffic_callback;
|
||||||
|
|
||||||
|
std::optional<ArticBaseCommon::DataPacket> SendRequestPacket(
|
||||||
|
const ArticBaseCommon::RequestPacket& req, bool expect_response,
|
||||||
|
const std::vector<ArticBaseCommon::RequestParameter>& params,
|
||||||
|
const std::chrono::nanoseconds& read_timeout = std::chrono::nanoseconds(0));
|
||||||
|
std::optional<std::string> SendSimpleRequest(const std::string& method);
|
||||||
|
|
||||||
|
class Handler {
|
||||||
|
public:
|
||||||
|
Handler(Client& _client, u32 _addr, u16 _port, int _id);
|
||||||
|
~Handler() {
|
||||||
|
delete thread;
|
||||||
|
}
|
||||||
|
void RunLoop();
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
bool should_run = true;
|
||||||
|
SocketHolder handler_socket = -1;
|
||||||
|
std::thread* thread = nullptr;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Client& client;
|
||||||
|
u32 addr;
|
||||||
|
u16 port;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PendingResponse;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Response {
|
||||||
|
public:
|
||||||
|
Response() {}
|
||||||
|
Response(Response& other)
|
||||||
|
: articResult(other.articResult), methodResult(other.methodResult),
|
||||||
|
resp_data_size(other.resp_data_size) {
|
||||||
|
if (resp_data_size) {
|
||||||
|
resp_data_buffer = reinterpret_cast<char*>(operator new(resp_data_size));
|
||||||
|
std::memcpy(resp_data_buffer, other.resp_data_buffer, resp_data_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Response(Response&& other) noexcept
|
||||||
|
: articResult(other.articResult), methodResult(other.methodResult),
|
||||||
|
resp_data_buffer(std::exchange(other.resp_data_buffer, nullptr)),
|
||||||
|
resp_data_size(other.resp_data_size) {}
|
||||||
|
|
||||||
|
Response& operator=(Response& other) {
|
||||||
|
articResult = other.articResult;
|
||||||
|
methodResult = other.methodResult;
|
||||||
|
resp_data_size = other.resp_data_size;
|
||||||
|
if (resp_data_size) {
|
||||||
|
resp_data_buffer = reinterpret_cast<char*>(operator new(resp_data_size));
|
||||||
|
std::memcpy(resp_data_buffer, other.resp_data_buffer, resp_data_size);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response& operator=(Response&& other) noexcept {
|
||||||
|
articResult = other.articResult;
|
||||||
|
methodResult = other.methodResult;
|
||||||
|
resp_data_size = other.resp_data_size;
|
||||||
|
resp_data_buffer = std::exchange(other.resp_data_buffer, nullptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Response() {
|
||||||
|
if (resp_data_buffer) {
|
||||||
|
operator delete(resp_data_buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Succeeded() const {
|
||||||
|
return articResult == ArticBaseCommon::ResponseMethod::ArticResult::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetMethodResult() const {
|
||||||
|
return methodResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<void*, size_t>> GetResponseBuffer(u32 buffer_id) const;
|
||||||
|
|
||||||
|
std::optional<s32> GetResponseS32(u32 buffer_id) const {
|
||||||
|
auto buf = GetResponseBuffer(buffer_id);
|
||||||
|
if (!buf.has_value() || buf->second != sizeof(s32)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<s32*>(buf->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<s64> GetResponseS64(u32 buffer_id) const {
|
||||||
|
auto buf = GetResponseBuffer(buffer_id);
|
||||||
|
if (!buf.has_value() || buf->second != sizeof(s64)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<s64*>(buf->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<u64> GetResponseU64(u32 buffer_id) const {
|
||||||
|
auto buf = GetResponseBuffer(buffer_id);
|
||||||
|
if (!buf.has_value() || buf->second != sizeof(u64)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<u64*>(buf->first);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Client;
|
||||||
|
friend class Client::Handler;
|
||||||
|
friend class PendingResponse;
|
||||||
|
|
||||||
|
// Start in error state in case the request is not fullfilled properly.
|
||||||
|
ArticBaseCommon::ResponseMethod::ArticResult articResult =
|
||||||
|
ArticBaseCommon::ResponseMethod::ArticResult::METHOD_ERROR;
|
||||||
|
union {
|
||||||
|
ArticBaseCommon::MethodState methodState =
|
||||||
|
ArticBaseCommon::MethodState::INTERNAL_METHOD_ERROR;
|
||||||
|
int methodResult;
|
||||||
|
};
|
||||||
|
char* resp_data_buffer{};
|
||||||
|
size_t resp_data_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Response> Send(Request& request);
|
||||||
|
|
||||||
|
private:
|
||||||
|
class PendingResponse {
|
||||||
|
public:
|
||||||
|
bool is_done = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Client;
|
||||||
|
friend class Client::Handler;
|
||||||
|
PendingResponse(const Request& req) : request(req) {}
|
||||||
|
std::condition_variable cv;
|
||||||
|
std::mutex cv_mutex;
|
||||||
|
|
||||||
|
const Request& request;
|
||||||
|
|
||||||
|
Response response{};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::mutex recv_map_mutex;
|
||||||
|
std::map<u32, PendingResponse*> pending_responses;
|
||||||
|
|
||||||
|
std::vector<Handler*> handlers;
|
||||||
|
std::atomic<size_t> running_handlers;
|
||||||
|
void OnAllHandlersFinished();
|
||||||
|
};
|
||||||
|
} // namespace Network::ArticBase
|
92
src/network/artic_base/artic_base_common.h
Normal file
92
src/network/artic_base/artic_base_common.h
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <array>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Network {
|
||||||
|
namespace ArticBaseCommon {
|
||||||
|
enum class MethodState : int {
|
||||||
|
PARSING_INPUT = 0,
|
||||||
|
PARAMETER_TYPE_MISMATCH = 1,
|
||||||
|
PARAMETER_COUNT_MISMATCH = 2,
|
||||||
|
BIG_BUFFER_READ_FAIL = 3,
|
||||||
|
BIG_BUFFER_WRITE_FAIL = 4,
|
||||||
|
OUT_OF_MEMORY = 5,
|
||||||
|
|
||||||
|
GENERATING_OUTPUT = 6,
|
||||||
|
UNEXPECTED_PARSING_INPUT = 7,
|
||||||
|
OUT_OF_MEMORY_OUTPUT = 8,
|
||||||
|
|
||||||
|
INTERNAL_METHOD_ERROR = 9,
|
||||||
|
FINISHED = 10,
|
||||||
|
};
|
||||||
|
enum class RequestParameterType : u16 {
|
||||||
|
IN_INTEGER_8 = 0,
|
||||||
|
IN_INTEGER_16 = 1,
|
||||||
|
IN_INTEGER_32 = 2,
|
||||||
|
IN_INTEGER_64 = 3,
|
||||||
|
IN_SMALL_BUFFER = 4,
|
||||||
|
IN_BIG_BUFFER = 5,
|
||||||
|
};
|
||||||
|
struct RequestParameter {
|
||||||
|
RequestParameterType type{};
|
||||||
|
union {
|
||||||
|
u16 parameterSize{};
|
||||||
|
u16 bigBufferID;
|
||||||
|
};
|
||||||
|
|
||||||
|
char data[0x1C]{};
|
||||||
|
};
|
||||||
|
struct RequestPacket {
|
||||||
|
u32 requestID{};
|
||||||
|
std::array<char, 0x20> method{};
|
||||||
|
u32 parameterCount{};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(RequestPacket) == 0x28);
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 4200)
|
||||||
|
#endif
|
||||||
|
struct Buffer {
|
||||||
|
u32 bufferID;
|
||||||
|
u32 bufferSize;
|
||||||
|
|
||||||
|
char data[];
|
||||||
|
};
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct ResponseMethod {
|
||||||
|
enum class ArticResult : u32 {
|
||||||
|
SUCCESS = 0,
|
||||||
|
METHOD_NOT_FOUND = 1,
|
||||||
|
METHOD_ERROR = 2,
|
||||||
|
PROVIDE_INPUT = 3,
|
||||||
|
};
|
||||||
|
ArticResult articResult{};
|
||||||
|
union {
|
||||||
|
int methodResult{};
|
||||||
|
int provideInputBufferID;
|
||||||
|
};
|
||||||
|
int bufferSize{};
|
||||||
|
u8 padding[0x10]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DataPacket {
|
||||||
|
DataPacket() {}
|
||||||
|
u32 requestID{};
|
||||||
|
|
||||||
|
union {
|
||||||
|
char dataRaw[0x1C]{};
|
||||||
|
ResponseMethod resp;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(DataPacket) == 0x20);
|
||||||
|
}; // namespace ArticBaseCommon
|
||||||
|
} // namespace Network
|
31
src/network/socket_manager.cpp
Normal file
31
src/network/socket_manager.cpp
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "socket_manager.h"
|
||||||
|
|
||||||
|
namespace Network {
|
||||||
|
std::atomic<u32> SocketManager::count = 0;
|
||||||
|
|
||||||
|
void SocketManager::EnableSockets() {
|
||||||
|
if (count++ == 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSADATA data;
|
||||||
|
WSAStartup(MAKEWORD(2, 2), &data);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketManager::DisableSockets() {
|
||||||
|
if (--count == 0) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
WSACleanup();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace Network
|
19
src/network/socket_manager.h
Normal file
19
src/network/socket_manager.h
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2024 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "atomic"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Network {
|
||||||
|
class SocketManager {
|
||||||
|
public:
|
||||||
|
static void EnableSockets();
|
||||||
|
static void DisableSockets();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SocketManager();
|
||||||
|
static std::atomic<u32> count;
|
||||||
|
};
|
||||||
|
} // namespace Network
|
Loading…
Reference in a new issue