Add Artic Base support (#105)

* Add Artic Base support

* Add Android support
This commit is contained in:
PabloMK7 2024-05-12 20:17:06 +02:00 committed by GitHub
parent 572d3ab71c
commit 24c6ec5e6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 5592 additions and 516 deletions

View file

@ -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
} }

View file

@ -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,

View file

@ -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(

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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{});
} }

View file

@ -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();

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -14,6 +14,7 @@
//---------------------------------------------------------------------------// //---------------------------------------------------------------------------//
#pragma once #pragma once
#include <algorithm>
#include <array> #include <array>
#include <list> #include <list>
#include <tuple> #include <tuple>

View file

@ -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)

View file

@ -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) {

View file

@ -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;

View 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

View 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)

View file

@ -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;
} }

View file

@ -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;

View file

@ -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)

View file

@ -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;
} }

View file

@ -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,

View file

@ -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;
}; };

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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;

View file

@ -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;
} }

View file

@ -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:

View file

@ -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;

View file

@ -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:

View file

@ -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;
} }

View file

@ -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:

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View 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

View 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)

View file

@ -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>

View file

@ -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();
} }

View file

@ -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;
} }

View file

@ -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

View 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;

View file

@ -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 {}

View file

@ -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

View file

@ -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)

View file

@ -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()) {

View file

@ -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:

View 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

View 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)

View 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

View 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)

View file

@ -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();

View file

@ -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.

View file

@ -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 {}

View file

@ -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:

View file

@ -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;

View file

@ -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);

View file

@ -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();
} }
} }

View file

@ -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();

View file

@ -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();
} }

View file

@ -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;
}; };

View file

@ -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

View file

@ -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;
}; };

View file

@ -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;

View file

@ -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;

View file

@ -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();
} }
} }

View file

@ -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
View 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
View 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

View file

@ -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);

View file

@ -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;

View 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;
} }

View file

@ -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;

View file

@ -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");
} }

View file

@ -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
) )

View 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, &parameter, 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, &parameter, 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, &parameter, 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, &parameter, 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

View 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

View 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

View 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

View 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