mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-05 18:23:39 -04:00
core: Add ability to insert cartridges
This commit is contained in:
parent
15e542778e
commit
624be242c4
40 changed files with 445 additions and 82 deletions
1
dist/license.md
vendored
1
dist/license.md
vendored
|
|
@ -16,6 +16,7 @@ qt_themes/default/icons/48x48/no_avatar.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
|
||||||
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/default/icons/48x48/star.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
qt_themes/default/icons/128x128/cartridge.png | CC0 1.0 | Designed by PabloMK7
|
||||||
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/connected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/connected_notification.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
qt_themes/qdarkstyle/icons/16x16/disconnected.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
|
|
||||||
BIN
dist/qt_themes/default/icons/128x128/cartridge.png
vendored
Normal file
BIN
dist/qt_themes/default/icons/128x128/cartridge.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
5
dist/qt_themes/default/icons/index.theme
vendored
5
dist/qt_themes/default/icons/index.theme
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
[Icon Theme]
|
[Icon Theme]
|
||||||
Name=default
|
Name=default
|
||||||
Comment=default theme
|
Comment=default theme
|
||||||
Directories=16x16,48x48,256x256
|
Directories=16x16,48x48,128x128,256x256
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
@ -9,5 +9,8 @@ Size=16
|
||||||
[48x48]
|
[48x48]
|
||||||
Size=48
|
Size=48
|
||||||
|
|
||||||
|
[128x128]
|
||||||
|
Size=128
|
||||||
|
|
||||||
[256x256]
|
[256x256]
|
||||||
Size=256
|
Size=256
|
||||||
BIN
dist/qt_themes/default/icons_light/128x128/cartridge.png
vendored
Normal file
BIN
dist/qt_themes/default/icons_light/128x128/cartridge.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
|
|
@ -1,7 +1,7 @@
|
||||||
[Icon Theme]
|
[Icon Theme]
|
||||||
Name=default
|
Name=default
|
||||||
Comment=default theme
|
Comment=default theme
|
||||||
Directories=16x16,48x48,256x256
|
Directories=16x16,48x48,128x128,256x256
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
@ -9,5 +9,8 @@ Size=16
|
||||||
[48x48]
|
[48x48]
|
||||||
Size=48
|
Size=48
|
||||||
|
|
||||||
|
[128x128]
|
||||||
|
Size=128
|
||||||
|
|
||||||
[256x256]
|
[256x256]
|
||||||
Size=256
|
Size=256
|
||||||
2
dist/qt_themes/default/theme_default.qrc
vendored
2
dist/qt_themes/default/theme_default.qrc
vendored
|
|
@ -13,6 +13,7 @@
|
||||||
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
<file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file>
|
||||||
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
<file alias="48x48/plus.png">icons/48x48/plus.png</file>
|
||||||
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
<file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
|
||||||
|
<file alias="128x128/cartridge.png">icons/128x128/cartridge.png</file>
|
||||||
<file alias="256x256/azahar.png">icons/256x256/azahar.png</file>
|
<file alias="256x256/azahar.png">icons/256x256/azahar.png</file>
|
||||||
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
<file alias="48x48/star.png">icons/48x48/star.png</file>
|
||||||
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
<file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
|
||||||
|
|
@ -31,6 +32,7 @@
|
||||||
<file alias="48x48/no_avatar.png">icons_light/48x48/no_avatar.png</file>
|
<file alias="48x48/no_avatar.png">icons_light/48x48/no_avatar.png</file>
|
||||||
<file alias="48x48/plus.png">icons_light/48x48/plus.png</file>
|
<file alias="48x48/plus.png">icons_light/48x48/plus.png</file>
|
||||||
<file alias="48x48/sd_card.png">icons_light/48x48/sd_card.png</file>
|
<file alias="48x48/sd_card.png">icons_light/48x48/sd_card.png</file>
|
||||||
|
<file alias="128x128/cartridge.png">icons_light/128x128/cartridge.png</file>
|
||||||
<file alias="256x256/azahar.png">icons_light/256x256/azahar.png</file>
|
<file alias="256x256/azahar.png">icons_light/256x256/azahar.png</file>
|
||||||
<file alias="48x48/star.png">icons_light/48x48/star.png</file>
|
<file alias="48x48/star.png">icons_light/48x48/star.png</file>
|
||||||
<file alias="256x256/plus_folder.png">icons_light/256x256/plus_folder.png</file>
|
<file alias="256x256/plus_folder.png">icons_light/256x256/plus_folder.png</file>
|
||||||
|
|
|
||||||
|
|
@ -357,3 +357,4 @@ plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 fro
|
||||||
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
|
||||||
sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
sd_card.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
star.png | CC BY-ND 3.0 | https://icons8.com
|
star.png | CC BY-ND 3.0 | https://icons8.com
|
||||||
|
cartridge.png | CC0 1.0 | Designed by PabloMK7
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,12 @@ object NativeLibrary {
|
||||||
external fun createLogFile()
|
external fun createLogFile()
|
||||||
external fun logUserDirectory(directory: String)
|
external fun logUserDirectory(directory: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the inserted cartridge that will appear
|
||||||
|
* in the home menu. Empty string to clear.
|
||||||
|
*/
|
||||||
|
external fun setInsertedCartridge(path: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Begins emulation.
|
* Begins emulation.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
|
@ -69,6 +70,9 @@ class GameAdapter(
|
||||||
private var imagePath: String? = null
|
private var imagePath: String? = null
|
||||||
private var dialogShortcutBinding: DialogShortcutBinding? = null
|
private var dialogShortcutBinding: DialogShortcutBinding? = null
|
||||||
|
|
||||||
|
private val preferences: SharedPreferences
|
||||||
|
get() = PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
|
||||||
|
|
||||||
fun handleShortcutImageResult(uri: Uri?) {
|
fun handleShortcutImageResult(uri: Uri?) {
|
||||||
val path = uri?.toString()
|
val path = uri?.toString()
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
|
|
@ -196,6 +200,11 @@ class GameAdapter(
|
||||||
binding.textGameTitle.text = game.title
|
binding.textGameTitle.text = game.title
|
||||||
binding.textCompany.text = game.company
|
binding.textCompany.text = game.company
|
||||||
binding.textGameRegion.text = game.regions
|
binding.textGameRegion.text = game.regions
|
||||||
|
binding.imageCartridge.visibility = if (preferences.getString("insertedCartridge", "") != game.path) {
|
||||||
|
View.GONE
|
||||||
|
} else {
|
||||||
|
View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
val backgroundColorId =
|
val backgroundColorId =
|
||||||
if (
|
if (
|
||||||
|
|
@ -345,12 +354,29 @@ class GameAdapter(
|
||||||
val bottomSheetDialog = BottomSheetDialog(context)
|
val bottomSheetDialog = BottomSheetDialog(context)
|
||||||
bottomSheetDialog.setContentView(bottomSheetView)
|
bottomSheetDialog.setContentView(bottomSheetView)
|
||||||
|
|
||||||
|
val insertable = game.isInsertable
|
||||||
|
val inserted = insertable && (preferences.getString("insertedCartridge", "") == game.path)
|
||||||
|
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
|
bottomSheetView.findViewById<TextView>(R.id.about_game_title).text = game.title
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
|
bottomSheetView.findViewById<TextView>(R.id.about_game_company).text = game.company
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
|
bottomSheetView.findViewById<TextView>(R.id.about_game_region).text = game.regions
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
bottomSheetView.findViewById<TextView>(R.id.about_game_id).text = context.getString(R.string.game_context_id) + " " + String.format("%016X", game.titleId)
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
|
bottomSheetView.findViewById<TextView>(R.id.about_game_filename).text = context.getString(R.string.game_context_file) + " " + game.filename
|
||||||
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
|
bottomSheetView.findViewById<TextView>(R.id.about_game_filetype).text = context.getString(R.string.game_context_type) + " " + game.fileType
|
||||||
|
|
||||||
|
val insertButton = bottomSheetView.findViewById<MaterialButton>(R.id.insert_cartridge_button)
|
||||||
|
insertButton.text = if (inserted) { context.getString(R.string.game_context_eject) } else { context.getString(R.string.game_context_insert) }
|
||||||
|
insertButton.visibility = if (insertable) View.VISIBLE else View.GONE
|
||||||
|
insertButton.setOnClickListener {
|
||||||
|
if (inserted) {
|
||||||
|
preferences.edit().putString("insertedCartridge", "").apply()
|
||||||
|
} else {
|
||||||
|
preferences.edit().putString("insertedCartridge", game.path).apply()
|
||||||
|
}
|
||||||
|
bottomSheetDialog.dismiss()
|
||||||
|
notifyItemRangeChanged(0, currentList.size)
|
||||||
|
}
|
||||||
|
|
||||||
GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon))
|
GameIconUtils.loadGameIcon(activity, game, bottomSheetView.findViewById(R.id.game_icon))
|
||||||
|
|
||||||
bottomSheetView.findViewById<MaterialButton>(R.id.about_game_play).setOnClickListener {
|
bottomSheetView.findViewById<MaterialButton>(R.id.about_game_play).setOnClickListener {
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val insertedCartridge = preferences.getString("insertedCartridge", "")
|
||||||
|
NativeLibrary.setInsertedCartridge(insertedCartridge ?: "")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
game = args.game ?: intentGame!!
|
game = args.game ?: intentGame!!
|
||||||
} catch (e: NullPointerException) {
|
} catch (e: NullPointerException) {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ class Game(
|
||||||
val isInstalled: Boolean = false,
|
val isInstalled: Boolean = false,
|
||||||
val isSystemTitle: Boolean = false,
|
val isSystemTitle: Boolean = false,
|
||||||
val isVisibleSystemTitle: Boolean = false,
|
val isVisibleSystemTitle: Boolean = false,
|
||||||
|
val isInsertable: Boolean = false,
|
||||||
val icon: IntArray? = null,
|
val icon: IntArray? = null,
|
||||||
val fileType: String = "",
|
val fileType: String = "",
|
||||||
val isCompressed: Boolean = false,
|
val isCompressed: Boolean = false,
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,8 @@ class GameInfo(path: String) {
|
||||||
|
|
||||||
external fun getFileType(): String
|
external fun getFileType(): String
|
||||||
|
|
||||||
|
external fun getIsInsertable(): Boolean
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private external fun initialize(path: String): Long
|
private external fun initialize(path: String): Long
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ object GameHelper {
|
||||||
isInstalled,
|
isInstalled,
|
||||||
gameInfo?.isSystemTitle() ?: false,
|
gameInfo?.isSystemTitle() ?: false,
|
||||||
gameInfo?.getIsVisibleSystemTitle() ?: false,
|
gameInfo?.getIsVisibleSystemTitle() ?: false,
|
||||||
|
gameInfo?.getIsInsertable() ?: false,
|
||||||
gameInfo?.getIcon(),
|
gameInfo?.getIcon(),
|
||||||
gameInfo?.getFileType() ?: "",
|
gameInfo?.getFileType() ?: "",
|
||||||
gameInfo?.getFileType()?.contains("(Z)") ?: false,
|
gameInfo?.getFileType()?.contains("(Z)") ?: false,
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ struct GameInfoData {
|
||||||
bool loaded = false;
|
bool loaded = false;
|
||||||
bool is_encrypted = false;
|
bool is_encrypted = false;
|
||||||
std::string file_type = "";
|
std::string file_type = "";
|
||||||
|
bool is_insertable = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
GameInfoData* GetNewGameInfoData(const std::string& path) {
|
GameInfoData* GetNewGameInfoData(const std::string& path) {
|
||||||
|
|
@ -89,6 +90,7 @@ GameInfoData* GetNewGameInfoData(const std::string& path) {
|
||||||
gid->is_encrypted = is_encrypted;
|
gid->is_encrypted = is_encrypted;
|
||||||
gid->title_id = program_id;
|
gid->title_id = program_id;
|
||||||
gid->file_type = Loader::GetFileTypeString(loader->GetFileType(), loader->IsFileCompressed());
|
gid->file_type = Loader::GetFileTypeString(loader->GetFileType(), loader->IsFileCompressed());
|
||||||
|
gid->is_insertable = loader->GetFileType() == Loader::FileType::CCI;
|
||||||
|
|
||||||
return gid;
|
return gid;
|
||||||
}
|
}
|
||||||
|
|
@ -230,4 +232,7 @@ jstring Java_org_citra_citra_1emu_model_GameInfo_getFileType(JNIEnv* env, jobjec
|
||||||
|
|
||||||
return ToJString(env, file_type);
|
return ToJString(env, file_type);
|
||||||
}
|
}
|
||||||
|
jboolean Java_org_citra_citra_1emu_model_GameInfo_getIsInsertable(JNIEnv* env, jobject obj) {
|
||||||
|
return GetPointer(env, obj)->is_insertable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ std::mutex paused_mutex;
|
||||||
std::mutex running_mutex;
|
std::mutex running_mutex;
|
||||||
std::condition_variable running_cv;
|
std::condition_variable running_cv;
|
||||||
|
|
||||||
|
std::string inserted_cartridge;
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
||||||
|
|
@ -148,7 +150,10 @@ static void TryShutdown() {
|
||||||
secondary_window->DoneCurrent();
|
secondary_window->DoneCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::System::GetInstance().Shutdown();
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
|
system.Shutdown();
|
||||||
|
system.EjectCartridge();
|
||||||
|
|
||||||
window.reset();
|
window.reset();
|
||||||
if (secondary_window) {
|
if (secondary_window) {
|
||||||
|
|
@ -179,6 +184,10 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
||||||
|
|
||||||
Core::System& system{Core::System::GetInstance()};
|
Core::System& system{Core::System::GetInstance()};
|
||||||
|
|
||||||
|
if (!inserted_cartridge.empty()) {
|
||||||
|
system.InsertCartridge(inserted_cartridge);
|
||||||
|
}
|
||||||
|
|
||||||
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
const auto graphics_api = Settings::values.graphics_api.GetValue();
|
||||||
EGLContext* shared_context;
|
EGLContext* shared_context;
|
||||||
switch (graphics_api) {
|
switch (graphics_api) {
|
||||||
|
|
@ -1090,4 +1099,9 @@ jlong Java_org_citra_citra_1emu_NativeLibrary_playTimeManagerGetCurrentTitleId(J
|
||||||
return ptm_current_title_id;
|
return ptm_current_title_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Java_org_citra_citra_1emu_NativeLibrary_setInsertedCartridge(JNIEnv* env, jobject obj,
|
||||||
|
jstring path) {
|
||||||
|
inserted_cartridge = GetJString(env, path);
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
|
||||||
BIN
src/android/app/src/main/res/drawable/cartridge.png
Normal file
BIN
src/android/app/src/main/res/drawable/cartridge.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
|
|
@ -28,6 +28,16 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/image_cartridge"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:src="@drawable/cartridge"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="1.0" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,13 @@
|
||||||
android:contentDescription="@string/cheats"
|
android:contentDescription="@string/cheats"
|
||||||
android:text="@string/cheats" />
|
android:text="@string/cheats" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/insert_cartridge_button"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
||||||
|
|
@ -598,6 +598,8 @@
|
||||||
<string name="game_context_id">ID:</string>
|
<string name="game_context_id">ID:</string>
|
||||||
<string name="game_context_file">File:</string>
|
<string name="game_context_file">File:</string>
|
||||||
<string name="game_context_type">Type:</string>
|
<string name="game_context_type">Type:</string>
|
||||||
|
<string name="game_context_insert">Insert Cartridge</string>
|
||||||
|
<string name="game_context_eject">Eject Cartridge</string>
|
||||||
|
|
||||||
<!-- Performance Overlay settings -->
|
<!-- Performance Overlay settings -->
|
||||||
<string name="performance_overlay_show">Show Performance Overlay</string>
|
<string name="performance_overlay_show">Show Performance Overlay</string>
|
||||||
|
|
|
||||||
|
|
@ -1243,6 +1243,10 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
|
|
||||||
const auto scope = render_window->Acquire();
|
const auto scope = render_window->Acquire();
|
||||||
|
|
||||||
|
if (!UISettings::values.inserted_cartridge.GetValue().empty()) {
|
||||||
|
system.InsertCartridge(UISettings::values.inserted_cartridge.GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
const Core::System::ResultStatus result{
|
const Core::System::ResultStatus result{
|
||||||
system.Load(*render_window, filename.toStdString(), secondary_window)};
|
system.Load(*render_window, filename.toStdString(), secondary_window)};
|
||||||
|
|
||||||
|
|
@ -1532,6 +1536,8 @@ void GMainWindow::ShutdownGame() {
|
||||||
emu_thread->wait();
|
emu_thread->wait();
|
||||||
emu_thread = nullptr;
|
emu_thread = nullptr;
|
||||||
|
|
||||||
|
system.EjectCartridge();
|
||||||
|
|
||||||
OnCloseMovie();
|
OnCloseMovie();
|
||||||
|
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
|
|
@ -3813,6 +3819,9 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
|
||||||
ShutdownGame();
|
ShutdownGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save settings in case they were changed from outside the configuration menu.
|
||||||
|
config->Save();
|
||||||
|
|
||||||
render_window->close();
|
render_window->close();
|
||||||
secondary_window->close();
|
secondary_window->close();
|
||||||
multiplayer_state->Close();
|
multiplayer_state->Close();
|
||||||
|
|
|
||||||
|
|
@ -670,6 +670,11 @@ void QtConfig::ReadPathValues() {
|
||||||
ReadSetting(QStringLiteral("last_artic_base_addr"), QString{}).toString();
|
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();
|
||||||
|
|
||||||
|
ReadBasicSetting(UISettings::values.inserted_cartridge);
|
||||||
|
if (!FileUtil::Exists(UISettings::values.inserted_cartridge.GetValue())) {
|
||||||
|
UISettings::values.inserted_cartridge.SetValue("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
@ -1204,6 +1209,7 @@ void QtConfig::SavePathValues() {
|
||||||
UISettings::values.last_artic_base_addr, QString{});
|
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{});
|
||||||
|
WriteBasicSetting(UISettings::values.inserted_cartridge);
|
||||||
}
|
}
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,10 @@
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QModelIndex>
|
#include <QModelIndex>
|
||||||
|
#include <QPainter>
|
||||||
#include <QStandardItem>
|
#include <QStandardItem>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
@ -307,6 +309,42 @@ void GameList::OnFilterCloseClicked() {
|
||||||
main_window->filterBarSetChecked(false);
|
main_window->filterBarSetChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CartridgeIconDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
|
|
||||||
|
void paint(QPainter* painter, const QStyleOptionViewItem& option,
|
||||||
|
const QModelIndex& index) const override {
|
||||||
|
QStyleOptionViewItem opt(option);
|
||||||
|
initStyleOption(&opt, index);
|
||||||
|
|
||||||
|
QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
|
||||||
|
|
||||||
|
// Draw the default item (background, text, selection, etc.)
|
||||||
|
style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, opt.widget);
|
||||||
|
|
||||||
|
// Draw cartridge inserted icon
|
||||||
|
quint32 can_insert = index.data(GameListItemPath::CanInsertRole).value<quint32>();
|
||||||
|
QString game_path = index.data(GameListItemPath::FullPathRole).value<QString>();
|
||||||
|
|
||||||
|
bool is_inserted = can_insert && UISettings::values.inserted_cartridge.GetValue() ==
|
||||||
|
game_path.toStdString();
|
||||||
|
|
||||||
|
if (is_inserted) {
|
||||||
|
QPixmap pixmap = QIcon::fromTheme(QStringLiteral("cartridge")).pixmap(24);
|
||||||
|
|
||||||
|
const int margin = 12;
|
||||||
|
QSize pmSize = pixmap.size() / pixmap.devicePixelRatio();
|
||||||
|
|
||||||
|
QRect pmRect(opt.rect.right() - pmSize.width() - margin,
|
||||||
|
opt.rect.center().y() - pmSize.height() / 2, pmSize.width(),
|
||||||
|
pmSize.height());
|
||||||
|
|
||||||
|
painter->drawPixmap(pmRect, pixmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* parent)
|
GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* parent)
|
||||||
: QWidget{parent}, play_time_manager{play_time_manager_} {
|
: QWidget{parent}, play_time_manager{play_time_manager_} {
|
||||||
watcher = new QFileSystemWatcher(this);
|
watcher = new QFileSystemWatcher(this);
|
||||||
|
|
@ -329,6 +367,7 @@ GameList::GameList(PlayTime::PlayTimeManager& play_time_manager_, GMainWindow* p
|
||||||
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
|
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
|
||||||
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
|
tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
|
||||||
|
tree_view->setItemDelegateForColumn(0, new CartridgeIconDelegate(tree_view));
|
||||||
tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
UpdateColumnVisibility();
|
UpdateColumnVisibility();
|
||||||
|
|
@ -534,7 +573,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
||||||
selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
|
selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
|
||||||
selected.data(GameListItemPath::ExtdataIdRole).toULongLong(),
|
selected.data(GameListItemPath::ExtdataIdRole).toULongLong(),
|
||||||
static_cast<Service::FS::MediaType>(
|
static_cast<Service::FS::MediaType>(
|
||||||
selected.data(GameListItemPath::MediaTypeRole).toUInt()));
|
selected.data(GameListItemPath::MediaTypeRole).toUInt()),
|
||||||
|
selected.data(GameListItemPath::CanInsertRole).toUInt() != 0);
|
||||||
break;
|
break;
|
||||||
case GameListItemType::CustomDir:
|
case GameListItemType::CustomDir:
|
||||||
AddPermDirPopup(context_menu, selected);
|
AddPermDirPopup(context_menu, selected);
|
||||||
|
|
@ -604,8 +644,16 @@ void ForEachOpenGLCacheFile(u64 program_id, auto func) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name,
|
void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QString& name,
|
||||||
u64 program_id, u64 extdata_id, Service::FS::MediaType media_type) {
|
u64 program_id, u64 extdata_id, Service::FS::MediaType media_type,
|
||||||
|
bool can_insert) {
|
||||||
QAction* favorite = context_menu.addAction(tr("Favorite"));
|
QAction* favorite = context_menu.addAction(tr("Favorite"));
|
||||||
|
bool is_inserted =
|
||||||
|
can_insert && UISettings::values.inserted_cartridge.GetValue() == path.toStdString();
|
||||||
|
QAction* cartridge_insert = nullptr;
|
||||||
|
if (can_insert) {
|
||||||
|
cartridge_insert =
|
||||||
|
context_menu.addAction(is_inserted ? tr("Eject Cartridge") : tr("Insert Cartridge"));
|
||||||
|
}
|
||||||
context_menu.addSeparator();
|
context_menu.addSeparator();
|
||||||
QMenu* open_menu = context_menu.addMenu(tr("Open"));
|
QMenu* open_menu = context_menu.addMenu(tr("Open"));
|
||||||
QAction* open_application_location = open_menu->addAction(tr("Application Location"));
|
QAction* open_application_location = open_menu->addAction(tr("Application Location"));
|
||||||
|
|
@ -719,6 +767,16 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
|
||||||
connect(open_extdata_location, &QAction::triggered, this, [this, extdata_id] {
|
connect(open_extdata_location, &QAction::triggered, this, [this, extdata_id] {
|
||||||
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
|
emit OpenFolderRequested(extdata_id, GameListOpenTarget::EXT_DATA);
|
||||||
});
|
});
|
||||||
|
if (cartridge_insert) {
|
||||||
|
connect(cartridge_insert, &QAction::triggered, this, [this, path, is_inserted] {
|
||||||
|
if (is_inserted) {
|
||||||
|
UISettings::values.inserted_cartridge.SetValue("");
|
||||||
|
} else {
|
||||||
|
UISettings::values.inserted_cartridge.SetValue(path.toStdString());
|
||||||
|
}
|
||||||
|
tree_view->viewport()->update();
|
||||||
|
});
|
||||||
|
}
|
||||||
connect(open_application_location, &QAction::triggered, this, [this, program_id] {
|
connect(open_application_location, &QAction::triggered, this, [this, program_id] {
|
||||||
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
|
emit OpenFolderRequested(program_id, GameListOpenTarget::APPLICATION);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ private:
|
||||||
void PopupContextMenu(const QPoint& menu_location);
|
void PopupContextMenu(const QPoint& menu_location);
|
||||||
void PopupHeaderContextMenu(const QPoint& menu_location);
|
void PopupHeaderContextMenu(const QPoint& menu_location);
|
||||||
void AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, u64 program_id,
|
void AddGamePopup(QMenu& context_menu, const QString& path, const QString& name, u64 program_id,
|
||||||
u64 extdata_id, Service::FS::MediaType media_type);
|
u64 extdata_id, Service::FS::MediaType media_type, bool can_insert);
|
||||||
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
|
void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
|
||||||
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
|
void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
|
||||||
void AddFavoritesPopup(QMenu& context_menu);
|
void AddFavoritesPopup(QMenu& context_menu);
|
||||||
|
|
|
||||||
|
|
@ -159,15 +159,18 @@ public:
|
||||||
static constexpr int ExtdataIdRole = SortRole + 4;
|
static constexpr int ExtdataIdRole = SortRole + 4;
|
||||||
static constexpr int LongTitleRole = SortRole + 5;
|
static constexpr int LongTitleRole = SortRole + 5;
|
||||||
static constexpr int MediaTypeRole = SortRole + 6;
|
static constexpr int MediaTypeRole = SortRole + 6;
|
||||||
|
static constexpr int CanInsertRole = SortRole + 7;
|
||||||
|
|
||||||
GameListItemPath() = default;
|
GameListItemPath() = default;
|
||||||
GameListItemPath(const QString& game_path, std::span<const u8> smdh_data, u64 program_id,
|
GameListItemPath(const QString& game_path, std::span<const u8> smdh_data, u64 program_id,
|
||||||
u64 extdata_id, Service::FS::MediaType media_type, bool is_encrypted) {
|
u64 extdata_id, Service::FS::MediaType media_type, bool is_encrypted,
|
||||||
|
bool can_insert) {
|
||||||
setData(type(), TypeRole);
|
setData(type(), TypeRole);
|
||||||
setData(game_path, FullPathRole);
|
setData(game_path, FullPathRole);
|
||||||
setData(qulonglong(program_id), ProgramIdRole);
|
setData(qulonglong(program_id), ProgramIdRole);
|
||||||
setData(qulonglong(extdata_id), ExtdataIdRole);
|
setData(qulonglong(extdata_id), ExtdataIdRole);
|
||||||
setData(quint32(media_type), MediaTypeRole);
|
setData(quint32(media_type), MediaTypeRole);
|
||||||
|
setData(quint32(can_insert), CanInsertRole);
|
||||||
|
|
||||||
if (UISettings::values.game_list_icon_size.GetValue() ==
|
if (UISettings::values.game_list_icon_size.GetValue() ==
|
||||||
UISettings::GameListIconSize::NoIcon) {
|
UISettings::GameListIconSize::NoIcon) {
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
|
||||||
{
|
{
|
||||||
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id,
|
new GameListItemPath(QString::fromStdString(physical_name), smdh, program_id,
|
||||||
extdata_id, media_type,
|
extdata_id, media_type,
|
||||||
res == Loader::ResultStatus::ErrorEncrypted),
|
res == Loader::ResultStatus::ErrorEncrypted,
|
||||||
|
loader->GetFileType() == Loader::FileType::CCI),
|
||||||
new GameListItemCompat(compatibility),
|
new GameListItemCompat(compatibility),
|
||||||
new GameListItemRegion(smdh),
|
new GameListItemRegion(smdh),
|
||||||
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(
|
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,8 @@ struct Values {
|
||||||
Settings::Setting<bool> hide_mouse{false, "hideInactiveMouse"};
|
Settings::Setting<bool> hide_mouse{false, "hideInactiveMouse"};
|
||||||
Settings::Setting<bool> check_for_update_on_start{true, "check_for_update_on_start"};
|
Settings::Setting<bool> check_for_update_on_start{true, "check_for_update_on_start"};
|
||||||
|
|
||||||
|
Settings::Setting<std::string> inserted_cartridge{"", "inserted_cartridge"};
|
||||||
|
|
||||||
// Discord RPC
|
// Discord RPC
|
||||||
Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
|
Settings::Setting<bool> enable_discord_presence{true, "enable_discord_presence"};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/dumping/backend.h"
|
#include "core/dumping/backend.h"
|
||||||
|
#include "core/file_sys/ncch_container.h"
|
||||||
#include "core/frontend/image_interface.h"
|
#include "core/frontend/image_interface.h"
|
||||||
#include "core/gdbstub/gdbstub.h"
|
#include "core/gdbstub/gdbstub.h"
|
||||||
#include "core/global.h"
|
#include "core/global.h"
|
||||||
|
|
@ -812,6 +813,18 @@ void System::RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader)
|
||||||
early_app_loader = std::move(loader);
|
early_app_loader = std::move(loader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void System::InsertCartridge(const std::string& path) {
|
||||||
|
FileSys::NCCHContainer cartridge_container(path);
|
||||||
|
if (cartridge_container.LoadHeader() == Loader::ResultStatus::Success &&
|
||||||
|
cartridge_container.IsNCSD()) {
|
||||||
|
inserted_cartridge = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::EjectCartridge() {
|
||||||
|
inserted_cartridge.clear();
|
||||||
|
}
|
||||||
|
|
||||||
bool System::IsInitialSetup() {
|
bool System::IsInitialSetup() {
|
||||||
return app_loader && app_loader->DoingInitialSetup();
|
return app_loader && app_loader->DoingInitialSetup();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,14 @@ public:
|
||||||
|
|
||||||
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
|
void RegisterAppLoaderEarly(std::unique_ptr<Loader::AppLoader>& loader);
|
||||||
|
|
||||||
|
void InsertCartridge(const std::string& path);
|
||||||
|
|
||||||
|
void EjectCartridge();
|
||||||
|
|
||||||
|
const std::string& GetCartridge() const {
|
||||||
|
return inserted_cartridge;
|
||||||
|
}
|
||||||
|
|
||||||
bool IsInitialSetup();
|
bool IsInitialSetup();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -399,6 +407,9 @@ private:
|
||||||
// Temporary app loader passed from frontend
|
// Temporary app loader passed from frontend
|
||||||
std::unique_ptr<Loader::AppLoader> early_app_loader;
|
std::unique_ptr<Loader::AppLoader> early_app_loader;
|
||||||
|
|
||||||
|
/// Path for current inserted cartridge
|
||||||
|
std::string inserted_cartridge;
|
||||||
|
|
||||||
/// 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;
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,22 @@ ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path,
|
||||||
std::memcpy(&openfile_path, binary.data(), sizeof(NCCHFilePath));
|
std::memcpy(&openfile_path, binary.data(), sizeof(NCCHFilePath));
|
||||||
|
|
||||||
std::string file_path;
|
std::string file_path;
|
||||||
|
|
||||||
|
if (media_type == Service::FS::MediaType::GameCard) {
|
||||||
|
const auto& cartridge = Core::System::GetInstance().GetCartridge();
|
||||||
|
if (cartridge.empty()) {
|
||||||
|
return ResultNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 card_program_id;
|
||||||
|
auto cartridge_loader = Loader::GetLoader(cartridge);
|
||||||
|
FileSys::NCCHContainer cartridge_ncch(cartridge);
|
||||||
|
if (cartridge_ncch.ReadProgramId(card_program_id) != Loader::ResultStatus::Success ||
|
||||||
|
card_program_id != title_id) {
|
||||||
|
return ResultNotFound;
|
||||||
|
}
|
||||||
|
file_path = cartridge;
|
||||||
|
} else {
|
||||||
if (Settings::values.is_new_3ds) {
|
if (Settings::values.is_new_3ds) {
|
||||||
// Try the New 3DS specific variant first.
|
// Try the New 3DS specific variant first.
|
||||||
file_path = Service::AM::GetTitleContentPath(media_type, title_id | 0x20000000,
|
file_path = Service::AM::GetTitleContentPath(media_type, title_id | 0x20000000,
|
||||||
|
|
@ -97,6 +113,7 @@ ResultVal<std::unique_ptr<FileBackend>> NCCHArchive::OpenFile(const Path& path,
|
||||||
file_path =
|
file_path =
|
||||||
Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index);
|
Service::AM::GetTitleContentPath(media_type, title_id, openfile_path.content_index);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index);
|
auto ncch_container = NCCHContainer(file_path, 0, openfile_path.content_index);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1285,7 +1285,7 @@ std::string GetTitleContentPath(Service::FS::MediaType media_type, u64 tid, std:
|
||||||
auto fs_user =
|
auto fs_user =
|
||||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
||||||
"fs:USER");
|
"fs:USER");
|
||||||
return fs_user->GetCurrentGamecardPath();
|
return fs_user->GetRegisteredGamecardPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string content_path = GetTitlePath(media_type, tid) + "content/";
|
std::string content_path = GetTitlePath(media_type, tid) + "content/";
|
||||||
|
|
@ -1330,7 +1330,7 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid) {
|
||||||
auto fs_user =
|
auto fs_user =
|
||||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
||||||
"fs:USER");
|
"fs:USER");
|
||||||
return fs_user->GetCurrentGamecardPath();
|
return fs_user->GetRegisteredGamecardPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -1351,7 +1351,7 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
|
||||||
auto fs_user =
|
auto fs_user =
|
||||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
||||||
"fs:USER");
|
"fs:USER");
|
||||||
return fs_user->GetCurrentGamecardPath();
|
return fs_user->GetRegisteredGamecardPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -1414,6 +1414,17 @@ void Module::ScanForTitlesImpl(Service::FS::MediaType media_type) {
|
||||||
|
|
||||||
LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast<int>(media_type));
|
LOG_DEBUG(Service_AM, "Starting title scan for media_type={}", static_cast<int>(media_type));
|
||||||
|
|
||||||
|
if (media_type == FS::MediaType::GameCard) {
|
||||||
|
const auto& cartridge = system.GetCartridge();
|
||||||
|
if (!cartridge.empty()) {
|
||||||
|
u64 program_id = 0;
|
||||||
|
FileSys::NCCHContainer cartridge_ncch(cartridge);
|
||||||
|
Loader::ResultStatus res = cartridge_ncch.ReadProgramId(program_id);
|
||||||
|
if (res == Loader::ResultStatus::Success) {
|
||||||
|
am_title_list[static_cast<u32>(media_type)].push_back(program_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
std::string title_path = GetMediaTitlePath(media_type);
|
std::string title_path = GetMediaTitlePath(media_type);
|
||||||
|
|
||||||
FileUtil::FSTEntry entries;
|
FileUtil::FSTEntry entries;
|
||||||
|
|
@ -1447,6 +1458,8 @@ void Module::ScanForTitlesImpl(Service::FS::MediaType media_type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOG_DEBUG(Service_AM, "Finished title scan for media_type={}", static_cast<int>(media_type));
|
LOG_DEBUG(Service_AM, "Finished title scan for media_type={}", static_cast<int>(media_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1455,6 +1468,7 @@ void Module::ScanForAllTitles() {
|
||||||
ScanForTicketsImpl();
|
ScanForTicketsImpl();
|
||||||
ScanForTitlesImpl(Service::FS::MediaType::NAND);
|
ScanForTitlesImpl(Service::FS::MediaType::NAND);
|
||||||
ScanForTitlesImpl(Service::FS::MediaType::SDMC);
|
ScanForTitlesImpl(Service::FS::MediaType::SDMC);
|
||||||
|
ScanForTitlesImpl(Service::FS::MediaType::GameCard);
|
||||||
} else {
|
} else {
|
||||||
scan_all_future = std::async([this]() {
|
scan_all_future = std::async([this]() {
|
||||||
std::scoped_lock lock(am_lists_mutex);
|
std::scoped_lock lock(am_lists_mutex);
|
||||||
|
|
@ -1465,6 +1479,9 @@ void Module::ScanForAllTitles() {
|
||||||
if (!stop_scan_flag) {
|
if (!stop_scan_flag) {
|
||||||
ScanForTitlesImpl(Service::FS::MediaType::SDMC);
|
ScanForTitlesImpl(Service::FS::MediaType::SDMC);
|
||||||
}
|
}
|
||||||
|
if (!stop_scan_flag) {
|
||||||
|
ScanForTitlesImpl(Service::FS::MediaType::GameCard);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1987,10 +2004,54 @@ void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::MediaType media_type,
|
Result GetTitleInfoFromList(Core::System& system, std::span<const u64> title_id_list,
|
||||||
|
Service::FS::MediaType media_type,
|
||||||
std::vector<TitleInfo>& title_info_out) {
|
std::vector<TitleInfo>& title_info_out) {
|
||||||
title_info_out.reserve(title_id_list.size());
|
title_info_out.reserve(title_id_list.size());
|
||||||
for (u32 i = 0; i < title_id_list.size(); i++) {
|
for (u32 i = 0; i < title_id_list.size(); i++) {
|
||||||
|
if (media_type == Service::FS::MediaType::GameCard) {
|
||||||
|
auto& cartridge = system.GetCartridge();
|
||||||
|
if (cartridge.empty()) {
|
||||||
|
LOG_DEBUG(Service_AM, "cartridge not inserted");
|
||||||
|
return Result(ErrorDescription::NotFound, ErrorModule::AM,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSys::NCCHContainer ncch_container(cartridge);
|
||||||
|
if (ncch_container.Load() != Loader::ResultStatus::Success ||
|
||||||
|
!ncch_container.IsNCSD()) {
|
||||||
|
LOG_ERROR(Service_AM, "failed to load cartridge card");
|
||||||
|
return Result(ErrorDescription::NotFound, ErrorModule::AM,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is what Process9 does for getting the information, from disassembly.
|
||||||
|
// It is still unclear what do those values mean, like the title info type.
|
||||||
|
if (ncch_container.exheader_header.arm11_system_local_caps.program_id !=
|
||||||
|
title_id_list[i]) {
|
||||||
|
LOG_DEBUG(Service_AM,
|
||||||
|
"cartridge has different title ID than requested title_id={:016X} != "
|
||||||
|
"cartridge_title_id={:016X}",
|
||||||
|
title_id_list[i],
|
||||||
|
ncch_container.exheader_header.arm11_system_local_caps.program_id);
|
||||||
|
return Result(ErrorDescription::NotFound, ErrorModule::AM,
|
||||||
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleInfo title_info = {};
|
||||||
|
title_info.tid = title_id_list[i];
|
||||||
|
title_info.version =
|
||||||
|
(*reinterpret_cast<u16_le*>(
|
||||||
|
&ncch_container.exheader_header.codeset_info.flags.remaster_version)
|
||||||
|
<< 10) &
|
||||||
|
0xFC00;
|
||||||
|
title_info.size = 0;
|
||||||
|
title_info.type = 0x40;
|
||||||
|
|
||||||
|
LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
|
||||||
|
title_info.version);
|
||||||
|
title_info_out.push_back(title_info);
|
||||||
|
} else {
|
||||||
std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]);
|
std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]);
|
||||||
|
|
||||||
TitleInfo title_info = {};
|
TitleInfo title_info = {};
|
||||||
|
|
@ -2005,13 +2066,14 @@ Result GetTitleInfoFromList(std::span<const u64> title_id_list, Service::FS::Med
|
||||||
title_info.type = tmd.GetTitleType();
|
title_info.type = tmd.GetTitleType();
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]);
|
LOG_DEBUG(Service_AM, "not found title_id={:016X}", title_id_list[i]);
|
||||||
return Result(ErrorDescription::NotFound, ErrorModule::AM, ErrorSummary::InvalidState,
|
return Result(ErrorDescription::NotFound, ErrorModule::AM,
|
||||||
ErrorLevel::Permanent);
|
ErrorSummary::InvalidState, ErrorLevel::Permanent);
|
||||||
}
|
}
|
||||||
LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
|
LOG_DEBUG(Service_AM, "found title_id={:016X} version={:04X}", title_id_list[i],
|
||||||
title_info.version);
|
title_info.version);
|
||||||
title_info_out.push_back(title_info);
|
title_info_out.push_back(title_info);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ResultSuccess;
|
return ResultSuccess;
|
||||||
}
|
}
|
||||||
|
|
@ -2142,7 +2204,7 @@ void Module::Interface::GetProgramInfosImpl(Kernel::HLERequestContext& ctx, bool
|
||||||
}
|
}
|
||||||
|
|
||||||
if (async_data->res.IsSuccess()) {
|
if (async_data->res.IsSuccess()) {
|
||||||
async_data->res = GetTitleInfoFromList(async_data->title_id_list,
|
async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list,
|
||||||
async_data->media_type, async_data->out);
|
async_data->media_type, async_data->out);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -2356,7 +2418,7 @@ void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (async_data->res.IsSuccess()) {
|
if (async_data->res.IsSuccess()) {
|
||||||
async_data->res = GetTitleInfoFromList(async_data->title_id_list,
|
async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list,
|
||||||
async_data->media_type, async_data->out);
|
async_data->media_type, async_data->out);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -2503,7 +2565,7 @@ void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (async_data->res.IsSuccess()) {
|
if (async_data->res.IsSuccess()) {
|
||||||
async_data->res = GetTitleInfoFromList(async_data->title_id_list,
|
async_data->res = GetTitleInfoFromList(am->system, async_data->title_id_list,
|
||||||
async_data->media_type, async_data->out);
|
async_data->media_type, async_data->out);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,10 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap
|
||||||
// Note: In the real console the title id of a given applet slot is set by the APT module when
|
// Note: In the real console the title id of a given applet slot is set by the APT module when
|
||||||
// calling StartApplication.
|
// calling StartApplication.
|
||||||
slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id;
|
slot_data->title_id = system.Kernel().GetCurrentProcess()->codeset->program_id;
|
||||||
|
if (app_id == AppletId::Application) {
|
||||||
|
slot_data->media_type = next_app_mediatype;
|
||||||
|
next_app_mediatype = static_cast<FS::MediaType>(UINT32_MAX);
|
||||||
|
}
|
||||||
slot_data->attributes.raw = attributes.raw;
|
slot_data->attributes.raw = attributes.raw;
|
||||||
|
|
||||||
// Applications need to receive a Wakeup signal to actually start up, this signal is usually
|
// Applications need to receive a Wakeup signal to actually start up, this signal is usually
|
||||||
|
|
@ -1207,7 +1211,14 @@ ResultVal<AppletManager::AppletInfo> AppletManager::GetAppletInfo(AppletId app_i
|
||||||
ErrorLevel::Status);
|
ErrorLevel::Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto media_type = Service::AM::GetTitleMediaType(slot_data->title_id);
|
FS::MediaType media_type;
|
||||||
|
if (slot_data->media_type != static_cast<FS::MediaType>(UINT32_MAX)) {
|
||||||
|
media_type = slot_data->media_type;
|
||||||
|
} else {
|
||||||
|
// Applet was not started from StartApplication, so we need to guess.
|
||||||
|
media_type = Service::AM::GetTitleMediaType(slot_data->title_id);
|
||||||
|
}
|
||||||
|
|
||||||
return AppletInfo{
|
return AppletInfo{
|
||||||
.title_id = slot_data->title_id,
|
.title_id = slot_data->title_id,
|
||||||
.media_type = media_type,
|
.media_type = media_type,
|
||||||
|
|
@ -1234,7 +1245,13 @@ ResultVal<Service::FS::MediaType> AppletManager::Unknown54(u32 in_param) {
|
||||||
in_param >= 0x40 ? Service::FS::MediaType::GameCard : Service::FS::MediaType::SDMC;
|
in_param >= 0x40 ? Service::FS::MediaType::GameCard : Service::FS::MediaType::SDMC;
|
||||||
auto check_update = in_param == 0x01 || in_param == 0x42;
|
auto check_update = in_param == 0x01 || in_param == 0x42;
|
||||||
|
|
||||||
auto app_media_type = Service::AM::GetTitleMediaType(slot_data->title_id);
|
FS::MediaType app_media_type;
|
||||||
|
if (slot_data->media_type != static_cast<FS::MediaType>(UINT32_MAX)) {
|
||||||
|
app_media_type = slot_data->media_type;
|
||||||
|
} else {
|
||||||
|
// Applet was not started from StartApplication, so we need to guess.
|
||||||
|
app_media_type = Service::AM::GetTitleMediaType(slot_data->title_id);
|
||||||
|
}
|
||||||
auto app_update_media_type =
|
auto app_update_media_type =
|
||||||
Service::AM::GetTitleMediaType(Service::AM::GetTitleUpdateId(slot_data->title_id));
|
Service::AM::GetTitleMediaType(Service::AM::GetTitleUpdateId(slot_data->title_id));
|
||||||
if (app_media_type == check_target || (check_update && app_update_media_type == check_target)) {
|
if (app_media_type == check_target || (check_update && app_update_media_type == check_target)) {
|
||||||
|
|
@ -1283,8 +1300,16 @@ Result AppletManager::PrepareToDoApplicationJump(u64 title_id, FS::MediaType med
|
||||||
// Save the title data to send it to the Home Menu when DoApplicationJump is called.
|
// Save the title data to send it to the Home Menu when DoApplicationJump is called.
|
||||||
auto application_slot_data = GetAppletSlot(AppletSlot::Application);
|
auto application_slot_data = GetAppletSlot(AppletSlot::Application);
|
||||||
app_jump_parameters.current_title_id = application_slot_data->title_id;
|
app_jump_parameters.current_title_id = application_slot_data->title_id;
|
||||||
app_jump_parameters.current_media_type =
|
|
||||||
Service::AM::GetTitleMediaType(application_slot_data->title_id);
|
FS::MediaType curr_media_type;
|
||||||
|
if (application_slot_data->media_type != static_cast<FS::MediaType>(UINT32_MAX)) {
|
||||||
|
curr_media_type = application_slot_data->media_type;
|
||||||
|
} else {
|
||||||
|
// Applet was not started from StartApplication, so we need to guess.
|
||||||
|
curr_media_type = Service::AM::GetTitleMediaType(application_slot_data->title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
app_jump_parameters.current_media_type = curr_media_type;
|
||||||
if (flags == ApplicationJumpFlags::UseCurrentParameters) {
|
if (flags == ApplicationJumpFlags::UseCurrentParameters) {
|
||||||
app_jump_parameters.next_title_id = app_jump_parameters.current_title_id;
|
app_jump_parameters.next_title_id = app_jump_parameters.current_title_id;
|
||||||
app_jump_parameters.next_media_type = app_jump_parameters.current_media_type;
|
app_jump_parameters.next_media_type = app_jump_parameters.current_media_type;
|
||||||
|
|
@ -1369,7 +1394,15 @@ Result AppletManager::PrepareToStartApplication(u64 title_id, FS::MediaType medi
|
||||||
|
|
||||||
title_id = ConvertTitleID(system, title_id);
|
title_id = ConvertTitleID(system, title_id);
|
||||||
|
|
||||||
std::string path = AM::GetTitleContentPath(media_type, title_id);
|
std::string path;
|
||||||
|
if (media_type == FS::MediaType::GameCard) {
|
||||||
|
path = system.GetCartridge();
|
||||||
|
} else {
|
||||||
|
path = AM::GetTitleContentPath(media_type, title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
next_app_mediatype = media_type;
|
||||||
|
|
||||||
auto loader = Loader::GetLoader(path);
|
auto loader = Loader::GetLoader(path);
|
||||||
|
|
||||||
if (!loader) {
|
if (!loader) {
|
||||||
|
|
|
||||||
|
|
@ -456,6 +456,7 @@ private:
|
||||||
AppletId applet_id;
|
AppletId applet_id;
|
||||||
AppletSlot slot;
|
AppletSlot slot;
|
||||||
u64 title_id;
|
u64 title_id;
|
||||||
|
FS::MediaType media_type;
|
||||||
bool registered;
|
bool registered;
|
||||||
bool loaded;
|
bool loaded;
|
||||||
AppletAttributes attributes;
|
AppletAttributes attributes;
|
||||||
|
|
@ -476,6 +477,7 @@ private:
|
||||||
ar & applet_id;
|
ar & applet_id;
|
||||||
ar & slot;
|
ar & slot;
|
||||||
ar & title_id;
|
ar & title_id;
|
||||||
|
ar & media_type;
|
||||||
ar & registered;
|
ar & registered;
|
||||||
ar & loaded;
|
ar & loaded;
|
||||||
ar & attributes.raw;
|
ar & attributes.raw;
|
||||||
|
|
@ -514,6 +516,8 @@ private:
|
||||||
bool last_home_button_state = false;
|
bool last_home_button_state = false;
|
||||||
bool last_power_button_state = false;
|
bool last_power_button_state = false;
|
||||||
|
|
||||||
|
FS::MediaType next_app_mediatype = static_cast<FS::MediaType>(UINT32_MAX);
|
||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
|
||||||
AppletSlotData* GetAppletSlot(AppletSlot slot) {
|
AppletSlotData* GetAppletSlot(AppletSlot slot) {
|
||||||
|
|
@ -569,6 +573,7 @@ private:
|
||||||
ar & capture_info;
|
ar & capture_info;
|
||||||
ar & applet_slots;
|
ar & applet_slots;
|
||||||
ar & library_applet_closing_command;
|
ar & library_applet_closing_command;
|
||||||
|
ar & next_app_mediatype;
|
||||||
|
|
||||||
if (Archive::is_loading::value) {
|
if (Archive::is_loading::value) {
|
||||||
LoadInputDevices();
|
LoadInputDevices();
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,20 @@ void Module::NSInterface::SetWirelessRebootInfo(Kernel::HLERequestContext& ctx)
|
||||||
LOG_WARNING(Service_APT, "called size={}", size);
|
LOG_WARNING(Service_APT, "called size={}", size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Module::NSInterface::CardUpdateInitialize(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
rp.Pop<u32>(); // Shared mem size
|
||||||
|
rp.Pop<u32>(); // Always 0
|
||||||
|
rp.Pop<u32>(); // Shared mem handle
|
||||||
|
|
||||||
|
LOG_WARNING(Service_APT, "(stubbed) called");
|
||||||
|
const Result update_not_needed(11, ErrorModule::CUP, ErrorSummary::NothingHappened,
|
||||||
|
ErrorLevel::Status);
|
||||||
|
|
||||||
|
auto rb = rp.MakeBuilder(1, 0);
|
||||||
|
rb.Push(update_not_needed);
|
||||||
|
}
|
||||||
|
|
||||||
void Module::NSInterface::ShutdownAsync(Kernel::HLERequestContext& ctx) {
|
void Module::NSInterface::ShutdownAsync(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,8 @@ public:
|
||||||
*/
|
*/
|
||||||
void SetWirelessRebootInfo(Kernel::HLERequestContext& ctx);
|
void SetWirelessRebootInfo(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void CardUpdateInitialize(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NS::ShutdownAsync service function.
|
* NS::ShutdownAsync service function.
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,21 @@ namespace Service::NS {
|
||||||
|
|
||||||
std::shared_ptr<Kernel::Process> LaunchTitle(Core::System& system, FS::MediaType media_type,
|
std::shared_ptr<Kernel::Process> LaunchTitle(Core::System& system, FS::MediaType media_type,
|
||||||
u64 title_id) {
|
u64 title_id) {
|
||||||
std::string path = AM::GetTitleContentPath(media_type, title_id);
|
std::string path;
|
||||||
auto loader = Loader::GetLoader(path);
|
|
||||||
|
|
||||||
if (!loader) {
|
if (media_type == FS::MediaType::GameCard) {
|
||||||
LOG_WARNING(Service_NS, "Could not find .app for title 0x{:016x}", title_id);
|
path = system.GetCartridge();
|
||||||
|
} else {
|
||||||
|
path = AM::GetTitleContentPath(media_type, title_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto loader = Loader::GetLoader(path);
|
||||||
|
u64 program_id;
|
||||||
|
|
||||||
|
if (!loader || loader->ReadProgramId(program_id) != Loader::ResultStatus::Success ||
|
||||||
|
program_id != title_id) {
|
||||||
|
LOG_WARNING(Service_NS, "Could not load title=0x{:016x} media_type={}", title_id,
|
||||||
|
static_cast<int>(media_type));
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,7 +70,13 @@ std::shared_ptr<Kernel::Process> LaunchTitle(Core::System& system, FS::MediaType
|
||||||
|
|
||||||
void RebootToTitle(Core::System& system, FS::MediaType media_type, u64 title_id,
|
void RebootToTitle(Core::System& system, FS::MediaType media_type, u64 title_id,
|
||||||
std::optional<Kernel::MemoryMode> mem_mode) {
|
std::optional<Kernel::MemoryMode> mem_mode) {
|
||||||
auto new_path = AM::GetTitleContentPath(media_type, title_id);
|
std::string new_path;
|
||||||
|
if (media_type == FS::MediaType::GameCard) {
|
||||||
|
new_path = system.GetCartridge();
|
||||||
|
} else {
|
||||||
|
new_path = AM::GetTitleContentPath(media_type, title_id);
|
||||||
|
}
|
||||||
|
|
||||||
if (new_path.empty() || !FileUtil::Exists(new_path)) {
|
if (new_path.empty() || !FileUtil::Exists(new_path)) {
|
||||||
// TODO: This can happen if the requested title is not installed. Need a way to find
|
// TODO: This can happen if the requested title is not installed. Need a way to find
|
||||||
// non-installed titles in the game list.
|
// non-installed titles in the game list.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2015 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ NS_S::NS_S(std::shared_ptr<Service::APT::Module> apt)
|
||||||
{0x0004, nullptr, "TerminateProcess"},
|
{0x0004, nullptr, "TerminateProcess"},
|
||||||
{0x0005, nullptr, "LaunchApplicationFIRM"},
|
{0x0005, nullptr, "LaunchApplicationFIRM"},
|
||||||
{0x0006, &NS_S::SetWirelessRebootInfo, "SetWirelessRebootInfo"},
|
{0x0006, &NS_S::SetWirelessRebootInfo, "SetWirelessRebootInfo"},
|
||||||
{0x0007, nullptr, "CardUpdateInitialize"},
|
{0x0007, &NS_S::CardUpdateInitialize, "CardUpdateInitialize"},
|
||||||
{0x0008, nullptr, "CardUpdateShutdown"},
|
{0x0008, nullptr, "CardUpdateShutdown"},
|
||||||
{0x000D, nullptr, "SetTWLBannerHMAC"},
|
{0x000D, nullptr, "SetTWLBannerHMAC"},
|
||||||
{0x000E, &NS_S::ShutdownAsync, "ShutdownAsync"},
|
{0x000E, &NS_S::ShutdownAsync, "ShutdownAsync"},
|
||||||
|
|
|
||||||
|
|
@ -913,6 +913,14 @@ void FS_USER::GetFreeBytes(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FS_USER::GetCardType(Kernel::HLERequestContext& ctx) {
|
||||||
|
IPC::RequestParser rp(ctx);
|
||||||
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
|
rb.Push(ResultSuccess);
|
||||||
|
rb.Push(0); // CTR Card
|
||||||
|
LOG_DEBUG(Service_FS, "(STUBBED) called");
|
||||||
|
}
|
||||||
|
|
||||||
void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
|
void FS_USER::GetSdmcArchiveResource(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
|
|
||||||
|
|
@ -999,8 +1007,8 @@ void FS_USER::CardSlotIsInserted(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.Push(false);
|
rb.Push(!system.GetCartridge().empty());
|
||||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
LOG_DEBUG(Service_FS, "called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::DeleteSystemSaveData(Kernel::HLERequestContext& ctx) {
|
void FS_USER::DeleteSystemSaveData(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
@ -1681,14 +1689,20 @@ void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) {
|
void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) {
|
||||||
const MediaType media_type = GetMediaTypeFromPath(filepath);
|
MediaType media_type;
|
||||||
|
if (filepath == system.GetCartridge()) {
|
||||||
|
media_type = MediaType::GameCard;
|
||||||
|
} else {
|
||||||
|
media_type = GetMediaTypeFromPath(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
|
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
|
||||||
if (media_type == MediaType::GameCard) {
|
if (media_type == MediaType::GameCard) {
|
||||||
current_gamecard_path = filepath;
|
current_gamecard_path = filepath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FS_USER::GetCurrentGamecardPath() const {
|
std::string FS_USER::GetRegisteredGamecardPath() const {
|
||||||
return current_gamecard_path;
|
return current_gamecard_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1779,7 +1793,7 @@ FS_USER::FS_USER(Core::System& system)
|
||||||
{0x0810, &FS_USER::CreateLegacySystemSaveData, "CreateLegacySystemSaveData"},
|
{0x0810, &FS_USER::CreateLegacySystemSaveData, "CreateLegacySystemSaveData"},
|
||||||
{0x0811, nullptr, "DeleteSystemSaveData"},
|
{0x0811, nullptr, "DeleteSystemSaveData"},
|
||||||
{0x0812, &FS_USER::GetFreeBytes, "GetFreeBytes"},
|
{0x0812, &FS_USER::GetFreeBytes, "GetFreeBytes"},
|
||||||
{0x0813, nullptr, "GetCardType"},
|
{0x0813, &FS_USER::GetCardType, "GetCardType"},
|
||||||
{0x0814, &FS_USER::GetSdmcArchiveResource, "GetSdmcArchiveResource"},
|
{0x0814, &FS_USER::GetSdmcArchiveResource, "GetSdmcArchiveResource"},
|
||||||
{0x0815, &FS_USER::GetNandArchiveResource, "GetNandArchiveResource"},
|
{0x0815, &FS_USER::GetNandArchiveResource, "GetNandArchiveResource"},
|
||||||
{0x0816, nullptr, "GetSdmcFatfsError"},
|
{0x0816, nullptr, "GetSdmcFatfsError"},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2014 Citra Emulator Project
|
// Copyright Citra Emulator Project / Azahar Emulator Project
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
|
@ -54,7 +54,7 @@ public:
|
||||||
// loader and pm, which we HLEed, we can just directly use it here
|
// loader and pm, which we HLEed, we can just directly use it here
|
||||||
void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath);
|
void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath);
|
||||||
|
|
||||||
std::string GetCurrentGamecardPath() const;
|
std::string GetRegisteredGamecardPath() const;
|
||||||
|
|
||||||
struct ProductInfo {
|
struct ProductInfo {
|
||||||
std::array<u8, 0x10> product_code;
|
std::array<u8, 0x10> product_code;
|
||||||
|
|
@ -361,6 +361,8 @@ private:
|
||||||
*/
|
*/
|
||||||
void GetFreeBytes(Kernel::HLERequestContext& ctx);
|
void GetFreeBytes(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
void GetCardType(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FS_User::GetSdmcArchiveResource service function.
|
* FS_User::GetSdmcArchiveResource service function.
|
||||||
* Inputs:
|
* Inputs:
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,10 @@ public:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual std::string GetFilePath() {
|
||||||
|
return file ? file->Filename() : "";
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
std::unique_ptr<FileUtil::IOFile> file;
|
std::unique_ptr<FileUtil::IOFile> file;
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ public:
|
||||||
|
|
||||||
bool IsFileCompressed() override;
|
bool IsFileCompressed() override;
|
||||||
|
|
||||||
|
std::string GetFilePath() override {
|
||||||
|
return filepath;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Loads .code section into memory for booting
|
* Loads .code section into memory for booting
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue