diff --git a/app/src/main/cpp/loader_jni.cpp b/app/src/main/cpp/loader_jni.cpp index 5ea1a598..4b8c97a3 100644 --- a/app/src/main/cpp/loader_jni.cpp +++ b/app/src/main/cpp/loader_jni.cpp @@ -51,6 +51,7 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn jclass clazz{env->GetObjectClass(thiz)}; jfieldID applicationNameField{env->GetFieldID(clazz, "applicationName", "Ljava/lang/String;")}; + jfieldID applicationTitleIdField{env->GetFieldID(clazz, "applicationTitleId", "Ljava/lang/String;")}; jfieldID applicationAuthorField{env->GetFieldID(clazz, "applicationAuthor", "Ljava/lang/String;")}; jfieldID rawIconField{env->GetFieldID(clazz, "rawIcon", "[B")}; jfieldID applicationVersionField{env->GetFieldID(clazz, "applicationVersion", "Ljava/lang/String;")}; @@ -62,6 +63,7 @@ extern "C" JNIEXPORT jint JNICALL Java_emu_skyline_loader_RomFile_populate(JNIEn env->SetObjectField(thiz, applicationNameField, env->NewStringUTF(loader->nacp->GetApplicationName(language).c_str())); env->SetObjectField(thiz, applicationVersionField, env->NewStringUTF(loader->nacp->GetApplicationVersion().c_str())); + env->SetObjectField(thiz, applicationTitleIdField, env->NewStringUTF(loader->nacp->GetSaveDataOwnerId().c_str())); env->SetObjectField(thiz, applicationAuthorField, env->NewStringUTF(loader->nacp->GetApplicationPublisher(language).c_str())); auto icon{loader->GetIcon(language)}; diff --git a/app/src/main/cpp/skyline/os.cpp b/app/src/main/cpp/skyline/os.cpp index f366dcf3..7ecde478 100644 --- a/app/src/main/cpp/skyline/os.cpp +++ b/app/src/main/cpp/skyline/os.cpp @@ -61,7 +61,7 @@ namespace skyline::kernel { name = nacp->GetApplicationName(nacp->GetFirstSupportedTitleLanguage()); if (publisher.empty()) publisher = nacp->GetApplicationPublisher(nacp->GetFirstSupportedTitleLanguage()); - Logger::InfoNoPrefix(R"(Starting "{}" v{} by "{}")", name, nacp->GetApplicationVersion(), publisher); + Logger::InfoNoPrefix(R"(Starting "{}" ({}) v{} by "{}")", name, nacp->GetSaveDataOwnerId(), nacp->GetApplicationVersion(), publisher); } process->InitializeHeapTls(); diff --git a/app/src/main/cpp/skyline/vfs/nacp.cpp b/app/src/main/cpp/skyline/vfs/nacp.cpp index 97f797a1..222e52da 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.cpp +++ b/app/src/main/cpp/skyline/vfs/nacp.cpp @@ -34,6 +34,11 @@ namespace skyline::vfs { return std::string(applicationPublisher.as_string(true)); } + std::string NACP::GetSaveDataOwnerId() { + auto applicationTitleId{nacpContents.saveDataOwnerId}; + return fmt::format("{:016X}", applicationTitleId); + } + std::string NACP::GetApplicationPublisher(language::ApplicationLanguage language) { auto applicationPublisher{span(nacpContents.titleEntries.at(static_cast(language)).applicationPublisher)}; return std::string(applicationPublisher.as_string(true)); diff --git a/app/src/main/cpp/skyline/vfs/nacp.h b/app/src/main/cpp/skyline/vfs/nacp.h index 7688342e..11d230c4 100644 --- a/app/src/main/cpp/skyline/vfs/nacp.h +++ b/app/src/main/cpp/skyline/vfs/nacp.h @@ -49,6 +49,8 @@ namespace skyline::vfs { std::string GetApplicationVersion(); + std::string GetSaveDataOwnerId(); + std::string GetApplicationPublisher(language::ApplicationLanguage language); }; } diff --git a/app/src/main/java/emu/skyline/AppDialog.kt b/app/src/main/java/emu/skyline/AppDialog.kt index ff043554..f5be9556 100644 --- a/app/src/main/java/emu/skyline/AppDialog.kt +++ b/app/src/main/java/emu/skyline/AppDialog.kt @@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toBitmap import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.snackbar.Snackbar import emu.skyline.data.AppItem import emu.skyline.databinding.AppDialogBinding import emu.skyline.loader.LoaderResult @@ -66,6 +67,7 @@ class AppDialog : BottomSheetDialogFragment() { binding.gameIcon.setImageBitmap(item.icon ?: missingIcon) binding.gameTitle.text = item.title binding.gameVersion.text = item.version ?: item.loaderResultString(requireContext()) + binding.gameTitleId.text = item.titleId binding.gameAuthor.text = item.author binding.gamePlay.isEnabled = item.loaderResult == LoaderResult.Success @@ -91,6 +93,13 @@ class AppDialog : BottomSheetDialogFragment() { shortcutManager.requestPinShortcut(info.build(), null) } + binding.gameTitleId.setOnLongClickListener { + val clipboard = requireActivity().getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager + clipboard.setPrimaryClip(android.content.ClipData.newPlainText("Title ID", item.titleId)) + Snackbar.make(binding.root, getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT).show() + true + } + dialog?.setOnKeyListener { _, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BUTTON_B && event.action == KeyEvent.ACTION_UP) { dialog?.onBackPressed() diff --git a/app/src/main/java/emu/skyline/data/DataItem.kt b/app/src/main/java/emu/skyline/data/DataItem.kt index 685d275f..fdc4e6de 100644 --- a/app/src/main/java/emu/skyline/data/DataItem.kt +++ b/app/src/main/java/emu/skyline/data/DataItem.kt @@ -29,6 +29,11 @@ data class AppItem(private val meta : AppEntry) : DataItem() { */ val title get() = meta.name + /** + * The title ID of the application + */ + val titleId get() = meta.titleId + /** * The application version */ diff --git a/app/src/main/java/emu/skyline/loader/RomFile.kt b/app/src/main/java/emu/skyline/loader/RomFile.kt index 0de2469a..52e26b24 100644 --- a/app/src/main/java/emu/skyline/loader/RomFile.kt +++ b/app/src/main/java/emu/skyline/loader/RomFile.kt @@ -66,6 +66,7 @@ enum class LoaderResult(val value : Int) { data class AppEntry( var name : String, var version : String?, + var titleId : String?, var author : String?, var icon : Bitmap?, var format : RomFormat, @@ -76,7 +77,7 @@ data class AppEntry( val nameIndex : Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() cursor.getString(nameIndex) - }!!.dropLast(format.name.length + 1), null, null, null, format, uri, loaderResult) + }!!.dropLast(format.name.length + 1), null, null, null, null, format, uri, loaderResult) private fun writeObject(output : ObjectOutputStream) { output.writeUTF(name) @@ -85,6 +86,9 @@ data class AppEntry( output.writeBoolean(version != null) if (version != null) output.writeUTF(version) + output.writeBoolean(titleId != null) + if (titleId != null) + output.writeUTF(titleId) output.writeBoolean(author != null) if (author != null) output.writeUTF(author) @@ -105,6 +109,8 @@ data class AppEntry( uri = Uri.parse(input.readUTF()) if (input.readBoolean()) version = input.readUTF() + if (input.readBoolean()) + titleId = input.readUTF() if (input.readBoolean()) author = input.readUTF() loaderResult = LoaderResult.get(input.readInt()) @@ -129,6 +135,11 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL */ private var applicationName : String? = null + /** + * @note This field is filled in by native code + */ + private var applicationTitleId : String? = null + /** * @note This field is filled in by native code */ @@ -158,9 +169,11 @@ internal class RomFile(context : Context, format : RomFormat, uri : Uri, systemL appEntry = applicationName?.let { name -> applicationVersion?.let { version -> - applicationAuthor?.let { author -> - rawIcon?.let { icon -> - AppEntry(name, version, author, BitmapFactory.decodeByteArray(icon, 0, icon.size), format, uri, result) + applicationTitleId?.let { titleId -> + applicationAuthor?.let { author -> + rawIcon?.let { icon -> + AppEntry(name, version, titleId, author, BitmapFactory.decodeByteArray(icon, 0, icon.size), format, uri, result) + } } } } diff --git a/app/src/main/res/layout/app_dialog.xml b/app/src/main/res/layout/app_dialog.xml index 37f1a4ec..07777b91 100644 --- a/app/src/main/res/layout/app_dialog.xml +++ b/app/src/main/res/layout/app_dialog.xml @@ -54,7 +54,7 @@ tools:text="The Legend of Zelda: Breath of the Wild" /> + + Search An error has occurred + Copied to clipboard Settings Share Logs