Compare commits

..

14 Commits

Author SHA1 Message Date
1837979d3d Android 193 2024-01-15 02:01:54 +00:00
62b4a3292a Merge yuzu-emu#12677 2024-01-15 02:01:54 +00:00
7207321680 Merge yuzu-emu#12665 2024-01-15 02:01:54 +00:00
d12d409d34 Merge yuzu-emu#12659 2024-01-15 02:01:54 +00:00
a66f40b29e Merge yuzu-emu#12652 2024-01-15 02:01:54 +00:00
4e50342f2b Merge yuzu-emu#12612 2024-01-15 02:01:53 +00:00
9c89ccd761 Merge yuzu-emu#12611 2024-01-15 02:01:53 +00:00
a393ec3a71 Merge yuzu-emu#12610 2024-01-15 02:01:53 +00:00
47e19d5003 Merge yuzu-emu#12579 2024-01-15 02:01:53 +00:00
817c7c445d Merge pull request #12667 from t895/version-info
android: Show version name instead of build hash in about fragment
2024-01-13 20:23:12 -05:00
da714a362b Merge pull request #12666 from t895/ktlint-yuzu-verify
android: Move ktlintCheck to yuzu-verify
2024-01-13 20:23:02 -05:00
7b3941e5d4 android: Show version name instead of git hash in the about fragment 2024-01-13 18:12:19 -05:00
15d8a40529 android: Clean up git commands in build.gradle 2024-01-13 18:06:33 -05:00
cdeaca73c4 android: Move ktlintCheck to yuzu-verify 2024-01-13 17:41:01 -05:00
16 changed files with 219 additions and 150 deletions

View File

@ -32,3 +32,6 @@ if [ ! -z "$DIFF" ]; then
echo "$DIFF" echo "$DIFF"
exit 1 exit 1
fi fi
cd src/android
./gradlew ktlintCheck

View File

@ -13,13 +13,15 @@ jobs:
format: format:
name: 'verify format' name: 'verify format'
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: yuzuemu/build-environments:linux-clang-format
options: -u 1001
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: false submodules: false
- name: set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: 'Verify Formatting' - name: 'Verify Formatting'
run: bash -ex ./.ci/scripts/format/script.sh run: bash -ex ./.ci/scripts/format/script.sh
build: build:

View File

@ -6,6 +6,8 @@
| [12612](https://github.com/yuzu-emu/yuzu-android//pull/12612) | [`76880b84f`](https://github.com/yuzu-emu/yuzu-android//pull/12612/files) | fsp-srv: use program registry for SetCurrentProcess | [liamwhite](https://github.com/liamwhite/) | Yes | | [12612](https://github.com/yuzu-emu/yuzu-android//pull/12612) | [`76880b84f`](https://github.com/yuzu-emu/yuzu-android//pull/12612/files) | fsp-srv: use program registry for SetCurrentProcess | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12652](https://github.com/yuzu-emu/yuzu-android//pull/12652) | [`2a0d707ce`](https://github.com/yuzu-emu/yuzu-android//pull/12652/files) | shader_recompiler: emulate 8-bit and 16-bit storage writes with cas loop | [liamwhite](https://github.com/liamwhite/) | Yes | | [12652](https://github.com/yuzu-emu/yuzu-android//pull/12652) | [`2a0d707ce`](https://github.com/yuzu-emu/yuzu-android//pull/12652/files) | shader_recompiler: emulate 8-bit and 16-bit storage writes with cas loop | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12659](https://github.com/yuzu-emu/yuzu-android//pull/12659) | [`d94097478`](https://github.com/yuzu-emu/yuzu-android//pull/12659/files) | audio: fetch process object from handle table | [liamwhite](https://github.com/liamwhite/) | Yes | | [12659](https://github.com/yuzu-emu/yuzu-android//pull/12659) | [`d94097478`](https://github.com/yuzu-emu/yuzu-android//pull/12659/files) | audio: fetch process object from handle table | [liamwhite](https://github.com/liamwhite/) | Yes |
| [12665](https://github.com/yuzu-emu/yuzu-android//pull/12665) | [`bee22540a`](https://github.com/yuzu-emu/yuzu-android//pull/12665/files) | service: acc: Only save profiles when profiles have changed | [german77](https://github.com/german77/) | Yes |
| [12677](https://github.com/yuzu-emu/yuzu-android//pull/12677) | [`d4acdac16`](https://github.com/yuzu-emu/yuzu-android//pull/12677/files) | core: Support multiple modules per patcher | [GPUCode](https://github.com/GPUCode/) | Yes |
End of merge log. You can find the original README.md below the break. End of merge log. You can find the original README.md below the break.

View File

@ -188,8 +188,15 @@ tasks.create<Delete>("ktlintReset") {
delete(File(buildDir.path + File.separator + "intermediates/ktLint")) delete(File(buildDir.path + File.separator + "intermediates/ktLint"))
} }
val showFormatHelp = {
logger.lifecycle(
"If this check fails, please try running \"gradlew ktlintFormat\" for automatic " +
"codestyle fixes"
)
}
tasks.getByPath("ktlintKotlinScriptCheck").doFirst { showFormatHelp.invoke() }
tasks.getByPath("ktlintMainSourceSetCheck").doFirst { showFormatHelp.invoke() }
tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset") tasks.getByPath("loadKtlintReporters").dependsOn("ktlintReset")
tasks.getByPath("preBuild").dependsOn("ktlintCheck")
ktlint { ktlint {
version.set("0.47.1") version.set("0.47.1")
@ -228,71 +235,33 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
} }
fun getGitVersion(): String { fun runGitCommand(command: List<String>): String {
var versionName = "0.0" return try {
ProcessBuilder(command)
try {
versionName = ProcessBuilder("git", "describe", "--always", "--long")
.directory(project.rootDir) .directory(project.rootDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE) .redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE) .redirectError(ProcessBuilder.Redirect.PIPE)
.start().inputStream.bufferedReader().use { it.readText() } .start().inputStream.bufferedReader().use { it.readText() }
.trim() .trim()
} catch (e: Exception) {
logger.error("Cannot find git")
""
}
}
fun getGitVersion(): String {
val versionName = if (System.getenv("GITHUB_ACTIONS") != null) {
val gitTag = System.getenv("GIT_TAG_NAME") ?: ""
gitTag
} else {
runGitCommand(listOf("git", "describe", "--always", "--long"))
.replace(Regex("(-0)?-[^-]+$"), "") .replace(Regex("(-0)?-[^-]+$"), "")
} catch (e: Exception) {
logger.error("Cannot find git, defaulting to dummy version number")
} }
return versionName.ifEmpty { "0.0" }
if (System.getenv("GITHUB_ACTIONS") != null) {
val gitTag = System.getenv("GIT_TAG_NAME")
versionName = gitTag ?: versionName
}
return versionName
} }
fun getGitHash(): String { fun getGitHash(): String =
try { runGitCommand(listOf("git", "rev-parse", "--short", "HEAD")).ifEmpty { "dummy-hash" }
val processBuilder = ProcessBuilder("git", "rev-parse", "--short", "HEAD")
processBuilder.directory(project.rootDir)
val process = processBuilder.start()
val inputStream = process.inputStream
val errorStream = process.errorStream
process.waitFor()
return if (process.exitValue() == 0) { fun getBranch(): String =
inputStream.bufferedReader() runGitCommand(listOf("git", "rev-parse", "--abbrev-ref", "HEAD")).ifEmpty { "dummy-hash" }
.use { it.readText().trim() } // return the value of gitHash
} else {
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
logger.error("Error running git command: $errorMessage")
"dummy-hash" // return a dummy hash value in case of an error
}
} catch (e: Exception) {
logger.error("$e: Cannot find git, defaulting to dummy build hash")
return "dummy-hash" // return a dummy hash value in case of an error
}
}
fun getBranch(): String {
try {
val processBuilder = ProcessBuilder("git", "rev-parse", "--abbrev-ref", "HEAD")
processBuilder.directory(project.rootDir)
val process = processBuilder.start()
val inputStream = process.inputStream
val errorStream = process.errorStream
process.waitFor()
return if (process.exitValue() == 0) {
inputStream.bufferedReader()
.use { it.readText().trim() } // return the value of gitHash
} else {
val errorMessage = errorStream.bufferedReader().use { it.readText().trim() }
logger.error("Error running git command: $errorMessage")
"dummy-hash" // return a dummy hash value in case of an error
}
} catch (e: Exception) {
logger.error("$e: Cannot find git, defaulting to dummy build hash")
return "dummy-hash" // return a dummy hash value in case of an error
}
}

View File

@ -76,8 +76,8 @@ class AboutFragment : Fragment() {
binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment) binding.root.findNavController().navigate(R.id.action_aboutFragment_to_licensesFragment)
} }
binding.textBuildHash.text = BuildConfig.GIT_HASH binding.textVersionName.text = BuildConfig.VERSION_NAME
binding.buttonBuildHash.setOnClickListener { binding.textVersionName.setOnClickListener {
val clipBoard = val clipBoard =
requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH) val clip = ClipData.newPlainText(getString(R.string.build), BuildConfig.GIT_HASH)

View File

@ -147,7 +147,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_build_hash" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground" android:background="?attr/selectableItemBackground"
@ -164,7 +164,7 @@
android:textAlignment="viewStart" /> android:textAlignment="viewStart" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_build_hash" android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -148,7 +148,7 @@
android:layout_marginHorizontal="20dp" /> android:layout_marginHorizontal="20dp" />
<LinearLayout <LinearLayout
android:id="@+id/button_build_hash" android:id="@+id/button_version_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingVertical="16dp" android:paddingVertical="16dp"
@ -165,7 +165,7 @@
android:text="@string/build" /> android:text="@string/build" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_build_hash" android:id="@+id/text_version_name"
style="@style/TextAppearance.Material3.BodyMedium" style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -22,14 +22,10 @@ using NativeExecutionParameters = Kernel::KThread::NativeExecutionParameters;
constexpr size_t MaxRelativeBranch = 128_MiB; constexpr size_t MaxRelativeBranch = 128_MiB;
constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32); constexpr u32 ModuleCodeIndex = 0x24 / sizeof(u32);
Patcher::Patcher() : c(m_patch_instructions) {} Patcher::Patcher() : c(m_patch_instructions) {
// The first word of the patch section is always a branch to the first instruction of the
Patcher::~Patcher() = default; // module.
c.dw(0);
void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// Branch to the first instruction of the module.
this->BranchToModule(0);
// Write save context helper function. // Write save context helper function.
c.l(m_save_context); c.l(m_save_context);
@ -38,6 +34,25 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
// Write load context helper function. // Write load context helper function.
c.l(m_load_context); c.l(m_load_context);
WriteLoadContext(); WriteLoadContext();
}
Patcher::~Patcher() = default;
bool Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code) {
// If we have patched modules but cannot reach the new module, then it needs its own patcher.
const size_t image_size = program_image.size();
if (total_program_size + image_size > MaxRelativeBranch && total_program_size > 0) {
return false;
}
// Add a new module patch to our list
modules.emplace_back();
curr_patch = &modules.back();
// The first word of the patch section is always a branch to the first instruction of the
// module.
curr_patch->m_branch_to_module_relocations.push_back({0, 0});
// Retrieve text segment data. // Retrieve text segment data.
const auto text = std::span{program_image}.subspan(code.offset, code.size); const auto text = std::span{program_image}.subspan(code.offset, code.size);
@ -94,16 +109,17 @@ void Patcher::PatchText(const Kernel::PhysicalMemory& program_image,
} }
if (auto exclusive = Exclusive{inst}; exclusive.Verify()) { if (auto exclusive = Exclusive{inst}; exclusive.Verify()) {
m_exclusives.push_back(i); curr_patch->m_exclusives.push_back(i);
} }
} }
// Determine patching mode for the final relocation step // Determine patching mode for the final relocation step
const size_t image_size = program_image.size(); total_program_size += image_size;
this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData; this->mode = image_size > MaxRelativeBranch ? PatchMode::PreText : PatchMode::PostData;
return true;
} }
void Patcher::RelocateAndCopy(Common::ProcessAddress load_base, bool Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
const Kernel::CodeSet::Segment& code, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, Kernel::PhysicalMemory& program_image,
EntryTrampolines* out_trampolines) { EntryTrampolines* out_trampolines) {
@ -120,7 +136,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) { if (mode == PatchMode::PreText) {
rc.B(rel.patch_offset - patch_size - rel.module_offset); rc.B(rel.patch_offset - patch_size - rel.module_offset);
} else { } else {
rc.B(image_size - rel.module_offset + rel.patch_offset); rc.B(total_program_size - rel.module_offset + rel.patch_offset);
} }
}; };
@ -129,7 +145,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) { if (mode == PatchMode::PreText) {
rc.B(patch_size - rel.patch_offset + rel.module_offset); rc.B(patch_size - rel.patch_offset + rel.module_offset);
} else { } else {
rc.B(rel.module_offset - image_size - rel.patch_offset); rc.B(rel.module_offset - total_program_size - rel.patch_offset);
} }
}; };
@ -137,7 +153,7 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
if (mode == PatchMode::PreText) { if (mode == PatchMode::PreText) {
return GetInteger(load_base) + patch_offset; return GetInteger(load_base) + patch_offset;
} else { } else {
return GetInteger(load_base) + image_size + patch_offset; return GetInteger(load_base) + total_program_size + patch_offset;
} }
}; };
@ -150,39 +166,50 @@ void Patcher::RelocateAndCopy(Common::ProcessAddress load_base,
}; };
// We are now ready to relocate! // We are now ready to relocate!
for (const Relocation& rel : m_branch_to_patch_relocations) { auto& patch = modules[m_relocate_module_index++];
for (const Relocation& rel : patch.m_branch_to_patch_relocations) {
ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel); ApplyBranchToPatchRelocation(text_words.data() + rel.module_offset / sizeof(u32), rel);
} }
for (const Relocation& rel : m_branch_to_module_relocations) { for (const Relocation& rel : patch.m_branch_to_module_relocations) {
ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32), ApplyBranchToModuleRelocation(m_patch_instructions.data() + rel.patch_offset / sizeof(u32),
rel); rel);
} }
// Rewrite PC constants and record post trampolines // Rewrite PC constants and record post trampolines
for (const Relocation& rel : m_write_module_pc_relocations) { for (const Relocation& rel : patch.m_write_module_pc_relocations) {
oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)}; oaknut::CodeGenerator rc{m_patch_instructions.data() + rel.patch_offset / sizeof(u32)};
rc.dx(RebasePc(rel.module_offset)); rc.dx(RebasePc(rel.module_offset));
} }
for (const Trampoline& rel : m_trampolines) { for (const Trampoline& rel : patch.m_trampolines) {
out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)}); out_trampolines->insert({RebasePc(rel.module_offset), RebasePatch(rel.patch_offset)});
} }
// Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not. // Cortex-A57 seems to treat all exclusives as ordered, but newer processors do not.
// Convert to ordered to preserve this assumption. // Convert to ordered to preserve this assumption.
for (const ModuleTextAddress i : m_exclusives) { for (const ModuleTextAddress i : patch.m_exclusives) {
auto exclusive = Exclusive{text_words[i]}; auto exclusive = Exclusive{text_words[i]};
text_words[i] = exclusive.AsOrdered(); text_words[i] = exclusive.AsOrdered();
} }
// Copy to program image // Remove the patched module size from the total. This is done so total_program_size
if (this->mode == PatchMode::PreText) { // always represents the distance from the currently patched module to the patch section.
std::memcpy(program_image.data(), m_patch_instructions.data(), total_program_size -= image_size;
m_patch_instructions.size() * sizeof(u32));
} else { // Only copy to the program image of the last module
program_image.resize(image_size + patch_size); if (m_relocate_module_index == modules.size()) {
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(), if (this->mode == PatchMode::PreText) {
m_patch_instructions.size() * sizeof(u32)); ASSERT(image_size == total_program_size);
std::memcpy(program_image.data(), m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
} else {
program_image.resize(image_size + patch_size);
std::memcpy(program_image.data() + image_size, m_patch_instructions.data(),
m_patch_instructions.size() * sizeof(u32));
}
return true;
} }
return false;
} }
size_t Patcher::GetSectionSize() const noexcept { size_t Patcher::GetSectionSize() const noexcept {
@ -322,7 +349,7 @@ void Patcher::WriteSvcTrampoline(ModuleDestLabel module_dest, u32 svc_id) {
// Write the post-SVC trampoline address, which will jump back to the guest after restoring its // Write the post-SVC trampoline address, which will jump back to the guest after restoring its
// state. // state.
m_trampolines.push_back({c.offset(), module_dest}); curr_patch->m_trampolines.push_back({c.offset(), module_dest});
// Host called this location. Save the return address so we can // Host called this location. Save the return address so we can
// unwind the stack properly when jumping back. // unwind the stack properly when jumping back.

View File

@ -31,9 +31,9 @@ public:
explicit Patcher(); explicit Patcher();
~Patcher(); ~Patcher();
void PatchText(const Kernel::PhysicalMemory& program_image, bool PatchText(const Kernel::PhysicalMemory& program_image,
const Kernel::CodeSet::Segment& code); const Kernel::CodeSet::Segment& code);
void RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code, bool RelocateAndCopy(Common::ProcessAddress load_base, const Kernel::CodeSet::Segment& code,
Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines); Kernel::PhysicalMemory& program_image, EntryTrampolines* out_trampolines);
size_t GetSectionSize() const noexcept; size_t GetSectionSize() const noexcept;
@ -61,16 +61,16 @@ private:
private: private:
void BranchToPatch(uintptr_t module_dest) { void BranchToPatch(uintptr_t module_dest) {
m_branch_to_patch_relocations.push_back({c.offset(), module_dest}); curr_patch->m_branch_to_patch_relocations.push_back({c.offset(), module_dest});
} }
void BranchToModule(uintptr_t module_dest) { void BranchToModule(uintptr_t module_dest) {
m_branch_to_module_relocations.push_back({c.offset(), module_dest}); curr_patch->m_branch_to_module_relocations.push_back({c.offset(), module_dest});
c.dw(0); c.dw(0);
} }
void WriteModulePc(uintptr_t module_dest) { void WriteModulePc(uintptr_t module_dest) {
m_write_module_pc_relocations.push_back({c.offset(), module_dest}); curr_patch->m_write_module_pc_relocations.push_back({c.offset(), module_dest});
c.dx(0); c.dx(0);
} }
@ -84,15 +84,22 @@ private:
uintptr_t module_offset; ///< Offset in bytes from the start of the text section. uintptr_t module_offset; ///< Offset in bytes from the start of the text section.
}; };
struct ModulePatch {
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<ModuleTextAddress> m_exclusives{};
};
oaknut::VectorCodeGenerator c; oaknut::VectorCodeGenerator c;
std::vector<Trampoline> m_trampolines;
std::vector<Relocation> m_branch_to_patch_relocations{};
std::vector<Relocation> m_branch_to_module_relocations{};
std::vector<Relocation> m_write_module_pc_relocations{};
std::vector<ModuleTextAddress> m_exclusives{};
oaknut::Label m_save_context{}; oaknut::Label m_save_context{};
oaknut::Label m_load_context{}; oaknut::Label m_load_context{};
PatchMode mode{PatchMode::None}; PatchMode mode{PatchMode::None};
size_t total_program_size{};
size_t m_relocate_module_index{};
std::vector<ModulePatch> modules;
ModulePatch* curr_patch;
}; };
} // namespace Core::NCE } // namespace Core::NCE

View File

@ -1239,10 +1239,10 @@ void KProcess::LoadModule(CodeSet code_set, KProcessAddress base_addr) {
ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite); ReprotectSegment(code_set.DataSegment(), Svc::MemoryPermission::ReadWrite);
#ifdef HAS_NCE #ifdef HAS_NCE
if (this->IsApplication() && Settings::IsNceEnabled()) { const auto& patch = code_set.PatchSegment();
if (this->IsApplication() && Settings::IsNceEnabled() && patch.size != 0) {
auto& buffer = m_kernel.System().DeviceMemory().buffer; auto& buffer = m_kernel.System().DeviceMemory().buffer;
const auto& code = code_set.CodeSegment(); const auto& code = code_set.CodeSegment();
const auto& patch = code_set.PatchSegment();
buffer.Protect(GetInteger(base_addr + code.addr), code.size, buffer.Protect(GetInteger(base_addr + code.addr), code.size,
Common::MemoryPermission::Read | Common::MemoryPermission::Execute); Common::MemoryPermission::Read | Common::MemoryPermission::Execute);
buffer.Protect(GetInteger(base_addr + patch.addr), patch.size, buffer.Protect(GetInteger(base_addr + patch.addr), patch.size,

View File

@ -61,9 +61,7 @@ ProfileManager::ProfileManager() {
OpenUser(*GetUser(current)); OpenUser(*GetUser(current));
} }
ProfileManager::~ProfileManager() { ProfileManager::~ProfileManager() = default;
WriteUserSaveFile();
}
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles /// internal management of the users profiles
@ -113,6 +111,8 @@ Result ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& username)
return ERROR_USER_ALREADY_EXISTS; return ERROR_USER_ALREADY_EXISTS;
} }
is_save_needed = true;
return AddUser({ return AddUser({
.user_uuid = uuid, .user_uuid = uuid,
.username = username, .username = username,
@ -326,6 +326,9 @@ bool ProfileManager::RemoveUser(UUID uuid) {
profiles[*index] = ProfileInfo{}; profiles[*index] = ProfileInfo{};
std::stable_partition(profiles.begin(), profiles.end(), std::stable_partition(profiles.begin(), profiles.end(),
[](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); }); [](const ProfileInfo& profile) { return profile.user_uuid.IsValid(); });
is_save_needed = true;
return true; return true;
} }
@ -340,6 +343,8 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
profile.username = profile_new.username; profile.username = profile_new.username;
profile.creation_time = profile_new.timestamp; profile.creation_time = profile_new.timestamp;
is_save_needed = true;
return true; return true;
} }
@ -348,6 +353,7 @@ bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase&
const auto index = GetUserIndex(uuid); const auto index = GetUserIndex(uuid);
if (index.has_value() && SetProfileBase(uuid, profile_new)) { if (index.has_value() && SetProfileBase(uuid, profile_new)) {
profiles[*index].data = data_new; profiles[*index].data = data_new;
is_save_needed = true;
return true; return true;
} }
@ -391,6 +397,10 @@ void ProfileManager::ParseUserSaveFile() {
} }
void ProfileManager::WriteUserSaveFile() { void ProfileManager::WriteUserSaveFile() {
if (!is_save_needed) {
return;
}
ProfileDataRaw raw{}; ProfileDataRaw raw{};
for (std::size_t i = 0; i < MAX_USERS; ++i) { for (std::size_t i = 0; i < MAX_USERS; ++i) {
@ -423,7 +433,10 @@ void ProfileManager::WriteUserSaveFile() {
if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) { if (!save.IsOpen() || !save.SetSize(sizeof(ProfileDataRaw)) || !save.WriteObject(raw)) {
LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
"made in current session will be saved."); "made in current session will be saved.");
return;
} }
is_save_needed = false;
} }
}; // namespace Service::Account }; // namespace Service::Account

View File

@ -103,6 +103,7 @@ private:
std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
bool RemoveProfileAtIndex(std::size_t index); bool RemoveProfileAtIndex(std::size_t index);
bool is_save_needed{};
std::array<ProfileInfo, MAX_USERS> profiles{}; std::array<ProfileInfo, MAX_USERS> profiles{};
std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{}; std::array<ProfileInfo, MAX_USERS> stored_opened_profiles{};
std::size_t user_count{}; std::size_t user_count{};

View File

@ -19,8 +19,54 @@
#include "core/arm/nce/patcher.h" #include "core/arm/nce/patcher.h"
#endif #endif
#ifndef HAS_NCE
namespace Core::NCE {
class Patcher {};
} // namespace Core::NCE
#endif
namespace Loader { namespace Loader {
struct PatchCollection {
explicit PatchCollection(bool is_application_) : is_application{is_application_} {
module_patcher_indices.fill(-1);
patchers.emplace_back();
}
std::vector<Core::NCE::Patcher>* GetPatchers() {
if (is_application && Settings::IsNceEnabled()) {
return &patchers;
}
return nullptr;
}
size_t GetTotalPatchSize() const {
size_t total_size{};
#ifdef HAS_NCE
for (auto& patcher : patchers) {
total_size += patcher.GetSectionSize();
}
#endif
return total_size;
}
void SaveIndex(size_t module) {
module_patcher_indices[module] = static_cast<s32>(patchers.size() - 1);
}
s32 GetIndex(size_t module) const {
return module_patcher_indices[module];
}
s32 GetLastIndex() const {
return static_cast<s32>(patchers.size()) - 1;
}
bool is_application;
std::vector<Core::NCE::Patcher> patchers;
std::array<s32, 13> module_patcher_indices{};
};
AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
bool override_update_) bool override_update_)
: AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) {
@ -142,18 +188,7 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
std::size_t code_size{}; std::size_t code_size{};
// Define an nce patch context for each potential module. // Define an nce patch context for each potential module.
#ifdef HAS_NCE PatchCollection patch_ctx{is_application};
std::array<Core::NCE::Patcher, 13> module_patchers;
#endif
const auto GetPatcher = [&](size_t i) -> Core::NCE::Patcher* {
#ifdef HAS_NCE
if (is_application && Settings::IsNceEnabled()) {
return &module_patchers[i];
}
#endif
return nullptr;
};
// Use the NSO module loader to figure out the code layout // Use the NSO module loader to figure out the code layout
for (size_t i = 0; i < static_modules.size(); i++) { for (size_t i = 0; i < static_modules.size(); i++) {
@ -164,13 +199,14 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
} }
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
AppLoader_NSO::LoadModule(process, system, *module_file, code_size, process, system, *module_file, code_size, should_pass_arguments, false, {},
should_pass_arguments, false, {}, GetPatcher(i)); patch_ctx.GetPatchers(), patch_ctx.GetLastIndex());
if (!tentative_next_load_addr) { if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}}; return {ResultStatus::ErrorLoadingNSO, {}};
} }
patch_ctx.SaveIndex(i);
code_size = *tentative_next_load_addr; code_size = *tentative_next_load_addr;
} }
@ -184,6 +220,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
return 0; return 0;
}(); }();
// Add patch size to the total module size
code_size += patch_ctx.GetTotalPatchSize();
// Setup the process code layout // Setup the process code layout
if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) { if (process.LoadFromMetadata(metadata, code_size, fastmem_base, is_hbl).IsError()) {
return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; return {ResultStatus::ErrorUnableToParseKernelMetadata, {}};
@ -204,9 +243,9 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect
const VAddr load_addr{next_load_addr}; const VAddr load_addr{next_load_addr};
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0; const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr = const auto tentative_next_load_addr = AppLoader_NSO::LoadModule(
AppLoader_NSO::LoadModule(process, system, *module_file, load_addr, process, system, *module_file, load_addr, should_pass_arguments, true, pm,
should_pass_arguments, true, pm, GetPatcher(i)); patch_ctx.GetPatchers(), patch_ctx.GetIndex(i));
if (!tentative_next_load_addr) { if (!tentative_next_load_addr) {
return {ResultStatus::ErrorLoadingNSO, {}}; return {ResultStatus::ErrorLoadingNSO, {}};
} }

View File

@ -77,7 +77,8 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
const FileSys::VfsFile& nso_file, VAddr load_base, const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process, bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm, std::optional<FileSys::PatchManager> pm,
Core::NCE::Patcher* patch) { std::vector<Core::NCE::Patcher>* patches,
s32 patch_index) {
if (nso_file.GetSize() < sizeof(NSOHeader)) { if (nso_file.GetSize() < sizeof(NSOHeader)) {
return std::nullopt; return std::nullopt;
} }
@ -94,8 +95,11 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
// Allocate some space at the beginning if we are patching in PreText mode. // Allocate some space at the beginning if we are patching in PreText mode.
const size_t module_start = [&]() -> size_t { const size_t module_start = [&]() -> size_t {
#ifdef HAS_NCE #ifdef HAS_NCE
if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::PreText) { if (patches && load_into_process) {
return patch->GetSectionSize(); auto* patch = &patches->operator[](patch_index);
if (patch->GetPatchMode() == Core::NCE::PatchMode::PreText) {
return patch->GetSectionSize();
}
} }
#endif #endif
return 0; return 0;
@ -160,27 +164,24 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core::
#ifdef HAS_NCE #ifdef HAS_NCE
// If we are computing the process code layout and using nce backend, patch. // If we are computing the process code layout and using nce backend, patch.
const auto& code = codeset.CodeSegment(); const auto& code = codeset.CodeSegment();
if (patch && patch->GetPatchMode() == Core::NCE::PatchMode::None) { auto* patch = patches ? &patches->operator[](patch_index) : nullptr;
if (patch && !load_into_process) {
// Patch SVCs and MRS calls in the guest code // Patch SVCs and MRS calls in the guest code
patch->PatchText(program_image, code); while (!patch->PatchText(program_image, code)) {
patch = &patches->emplace_back();
// Add patch section size to the module size. }
image_size += static_cast<u32>(patch->GetSectionSize());
} else if (patch) { } else if (patch) {
// Relocate code patch and copy to the program_image. // Relocate code patch and copy to the program_image.
patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers()); if (patch->RelocateAndCopy(load_base, code, program_image, &process.GetPostHandlers())) {
// Update patch section.
// Update patch section. auto& patch_segment = codeset.PatchSegment();
auto& patch_segment = codeset.PatchSegment(); patch_segment.addr =
patch_segment.addr = patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size;
patch->GetPatchMode() == Core::NCE::PatchMode::PreText ? 0 : image_size; patch_segment.size = static_cast<u32>(patch->GetSectionSize());
patch_segment.size = static_cast<u32>(patch->GetSectionSize());
// Add patch section size to the module size. In PreText mode image_size
// already contains the patch segment as part of module_start.
if (patch->GetPatchMode() == Core::NCE::PatchMode::PostData) {
image_size += patch_segment.size;
} }
// Refresh image_size to take account the patch section if it was added by RelocateAndCopy
image_size = static_cast<u32>(program_image.size());
} }
#endif #endif

View File

@ -93,7 +93,8 @@ public:
const FileSys::VfsFile& nso_file, VAddr load_base, const FileSys::VfsFile& nso_file, VAddr load_base,
bool should_pass_arguments, bool load_into_process, bool should_pass_arguments, bool load_into_process,
std::optional<FileSys::PatchManager> pm = {}, std::optional<FileSys::PatchManager> pm = {},
Core::NCE::Patcher* patch = nullptr); std::vector<Core::NCE::Patcher>* patches = nullptr,
s32 patch_index = -1);
LoadResult Load(Kernel::KProcess& process, Core::System& system) override; LoadResult Load(Kernel::KProcess& process, Core::System& system) override;

View File

@ -205,6 +205,7 @@ void ConfigureProfileManager::AddUser() {
const auto uuid = Common::UUID::MakeRandom(); const auto uuid = Common::UUID::MakeRandom();
profile_manager.CreateNewUser(uuid, username.toStdString()); profile_manager.CreateNewUser(uuid, username.toStdString());
profile_manager.WriteUserSaveFile();
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)}); item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
} }
@ -228,6 +229,7 @@ void ConfigureProfileManager::RenameUser() {
std::copy(username_std.begin(), username_std.end(), profile.username.begin()); std::copy(username_std.begin(), username_std.end(), profile.username.begin());
profile_manager.SetProfileBase(*uuid, profile); profile_manager.SetProfileBase(*uuid, profile);
profile_manager.WriteUserSaveFile();
item_model->setItem( item_model->setItem(
user, 0, user, 0,
@ -256,6 +258,8 @@ void ConfigureProfileManager::DeleteUser(const Common::UUID& uuid) {
return; return;
} }
profile_manager.WriteUserSaveFile();
item_model->removeRows(tree_view->currentIndex().row(), 1); item_model->removeRows(tree_view->currentIndex().row(), 1);
tree_view->clearSelection(); tree_view->clearSelection();