Compare commits
14 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12f86f89fc | ||
|
|
a2f23746c2 | ||
|
|
215b13f2a2 | ||
|
|
35ed9425d7 | ||
|
|
74cc8721c7 | ||
|
|
8ef1db78b0 | ||
|
|
18c8f10ff2 | ||
|
|
96d881f087 | ||
|
|
0e950baf41 | ||
|
|
8113f55f4b | ||
|
|
ddbefc71cb | ||
|
|
85143e8376 | ||
|
|
504abbd6e0 | ||
|
|
5eb5c96750 |
1
dist/languages/.tx/config
vendored
1
dist/languages/.tx/config
vendored
@@ -11,3 +11,4 @@ type = QT
|
||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = ja_JP:ja, ko_KR:ko, pt_BR:pt-rBR, pt_PT:pt-rPT, ru_RU:ru, vi_VN:vi, zh_CN:zh-rCN, zh_TW:zh-rTW
|
||||
|
||||
@@ -186,68 +186,68 @@ static_assert(std::is_trivially_destructible_v<PhysicalAddress>);
|
||||
static_assert(std::is_trivially_destructible_v<VirtualAddress>);
|
||||
static_assert(std::is_trivially_destructible_v<ProcessAddress>);
|
||||
|
||||
static_assert(Null<uint64_t> == 0);
|
||||
static_assert(Null<uint64_t> == 0U);
|
||||
static_assert(Null<PhysicalAddress> == Null<uint64_t>);
|
||||
static_assert(Null<VirtualAddress> == Null<uint64_t>);
|
||||
static_assert(Null<ProcessAddress> == Null<uint64_t>);
|
||||
|
||||
// Constructor/assignment validations.
|
||||
static_assert([] {
|
||||
const PhysicalAddress a(5);
|
||||
const PhysicalAddress a(5U);
|
||||
PhysicalAddress b(a);
|
||||
return b;
|
||||
}() == PhysicalAddress(5));
|
||||
}() == PhysicalAddress(5U));
|
||||
static_assert([] {
|
||||
const PhysicalAddress a(5);
|
||||
PhysicalAddress b(10);
|
||||
const PhysicalAddress a(5U);
|
||||
PhysicalAddress b(10U);
|
||||
b = a;
|
||||
return b;
|
||||
}() == PhysicalAddress(5));
|
||||
}() == PhysicalAddress(5U));
|
||||
|
||||
// Arithmetic validations.
|
||||
static_assert(PhysicalAddress(10) + 5 == PhysicalAddress(15));
|
||||
static_assert(PhysicalAddress(10) - 5 == PhysicalAddress(5));
|
||||
static_assert(PhysicalAddress(10U) + 5U == PhysicalAddress(15U));
|
||||
static_assert(PhysicalAddress(10U) - 5U == PhysicalAddress(5U));
|
||||
static_assert([] {
|
||||
PhysicalAddress v(10);
|
||||
v += 5;
|
||||
PhysicalAddress v(10U);
|
||||
v += 5U;
|
||||
return v;
|
||||
}() == PhysicalAddress(15));
|
||||
}() == PhysicalAddress(15U));
|
||||
static_assert([] {
|
||||
PhysicalAddress v(10);
|
||||
v -= 5;
|
||||
PhysicalAddress v(10U);
|
||||
v -= 5U;
|
||||
return v;
|
||||
}() == PhysicalAddress(5));
|
||||
static_assert(PhysicalAddress(10)++ == PhysicalAddress(10));
|
||||
static_assert(++PhysicalAddress(10) == PhysicalAddress(11));
|
||||
static_assert(PhysicalAddress(10)-- == PhysicalAddress(10));
|
||||
static_assert(--PhysicalAddress(10) == PhysicalAddress(9));
|
||||
}() == PhysicalAddress(5U));
|
||||
static_assert(PhysicalAddress(10U)++ == PhysicalAddress(10U));
|
||||
static_assert(++PhysicalAddress(10U) == PhysicalAddress(11U));
|
||||
static_assert(PhysicalAddress(10U)-- == PhysicalAddress(10U));
|
||||
static_assert(--PhysicalAddress(10U) == PhysicalAddress(9U));
|
||||
|
||||
// Logical validations.
|
||||
static_assert((PhysicalAddress(0b11111111) >> 1) == 0b01111111);
|
||||
static_assert((PhysicalAddress(0b10101010) >> 1) == 0b01010101);
|
||||
static_assert((PhysicalAddress(0b11111111) << 1) == 0b111111110);
|
||||
static_assert((PhysicalAddress(0b01010101) << 1) == 0b10101010);
|
||||
static_assert((PhysicalAddress(0b11111111) & 0b01010101) == 0b01010101);
|
||||
static_assert((PhysicalAddress(0b11111111) & 0b10101010) == 0b10101010);
|
||||
static_assert((PhysicalAddress(0b01010101) & 0b10101010) == 0b00000000);
|
||||
static_assert((PhysicalAddress(0b00000000) | 0b01010101) == 0b01010101);
|
||||
static_assert((PhysicalAddress(0b11111111) | 0b01010101) == 0b11111111);
|
||||
static_assert((PhysicalAddress(0b10101010) | 0b01010101) == 0b11111111);
|
||||
static_assert((PhysicalAddress(0b11111111U) >> 1) == 0b01111111U);
|
||||
static_assert((PhysicalAddress(0b10101010U) >> 1) == 0b01010101U);
|
||||
static_assert((PhysicalAddress(0b11111111U) << 1) == 0b111111110U);
|
||||
static_assert((PhysicalAddress(0b01010101U) << 1) == 0b10101010U);
|
||||
static_assert((PhysicalAddress(0b11111111U) & 0b01010101U) == 0b01010101U);
|
||||
static_assert((PhysicalAddress(0b11111111U) & 0b10101010U) == 0b10101010U);
|
||||
static_assert((PhysicalAddress(0b01010101U) & 0b10101010U) == 0b00000000U);
|
||||
static_assert((PhysicalAddress(0b00000000U) | 0b01010101U) == 0b01010101U);
|
||||
static_assert((PhysicalAddress(0b11111111U) | 0b01010101U) == 0b11111111U);
|
||||
static_assert((PhysicalAddress(0b10101010U) | 0b01010101U) == 0b11111111U);
|
||||
|
||||
// Comparisons.
|
||||
static_assert(PhysicalAddress(0) == PhysicalAddress(0));
|
||||
static_assert(PhysicalAddress(0) != PhysicalAddress(1));
|
||||
static_assert(PhysicalAddress(0) < PhysicalAddress(1));
|
||||
static_assert(PhysicalAddress(0) <= PhysicalAddress(1));
|
||||
static_assert(PhysicalAddress(1) > PhysicalAddress(0));
|
||||
static_assert(PhysicalAddress(1) >= PhysicalAddress(0));
|
||||
static_assert(PhysicalAddress(0U) == PhysicalAddress(0U));
|
||||
static_assert(PhysicalAddress(0U) != PhysicalAddress(1U));
|
||||
static_assert(PhysicalAddress(0U) < PhysicalAddress(1U));
|
||||
static_assert(PhysicalAddress(0U) <= PhysicalAddress(1U));
|
||||
static_assert(PhysicalAddress(1U) > PhysicalAddress(0U));
|
||||
static_assert(PhysicalAddress(1U) >= PhysicalAddress(0U));
|
||||
|
||||
static_assert(!(PhysicalAddress(0) == PhysicalAddress(1)));
|
||||
static_assert(!(PhysicalAddress(0) != PhysicalAddress(0)));
|
||||
static_assert(!(PhysicalAddress(1) < PhysicalAddress(0)));
|
||||
static_assert(!(PhysicalAddress(1) <= PhysicalAddress(0)));
|
||||
static_assert(!(PhysicalAddress(0) > PhysicalAddress(1)));
|
||||
static_assert(!(PhysicalAddress(0) >= PhysicalAddress(1)));
|
||||
static_assert(!(PhysicalAddress(0U) == PhysicalAddress(1U)));
|
||||
static_assert(!(PhysicalAddress(0U) != PhysicalAddress(0U)));
|
||||
static_assert(!(PhysicalAddress(1U) < PhysicalAddress(0U)));
|
||||
static_assert(!(PhysicalAddress(1U) <= PhysicalAddress(0U)));
|
||||
static_assert(!(PhysicalAddress(0U) > PhysicalAddress(1U)));
|
||||
static_assert(!(PhysicalAddress(0U) >= PhysicalAddress(1U)));
|
||||
|
||||
} // namespace Common
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ std::string GDBStubA32::RegRead(const Kernel::KThread* thread, size_t id) const
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
return ValueToHex(context.pstate);
|
||||
} else if (id >= D0_REGISTER && id < Q0_REGISTER) {
|
||||
return ValueToHex(fprs[id - D0_REGISTER][0]);
|
||||
return ValueToHex(fprs[(id - D0_REGISTER) / 2][(id - D0_REGISTER) % 2]);
|
||||
} else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
|
||||
return ValueToHex(fprs[id - Q0_REGISTER]);
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
@@ -406,7 +406,7 @@ void GDBStubA32::RegWrite(Kernel::KThread* thread, size_t id, std::string_view v
|
||||
} else if (id == CPSR_REGISTER) {
|
||||
context.pstate = HexToValue<u32>(value);
|
||||
} else if (id >= D0_REGISTER && id < Q0_REGISTER) {
|
||||
fprs[id - D0_REGISTER] = {HexToValue<u64>(value), 0};
|
||||
fprs[(id - D0_REGISTER) / 2][(id - D0_REGISTER) % 2] = HexToValue<u64>(value);
|
||||
} else if (id >= Q0_REGISTER && id < FPSCR_REGISTER) {
|
||||
fprs[id - Q0_REGISTER] = HexToValue<u128>(value);
|
||||
} else if (id == FPSCR_REGISTER) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||
#include "core/hle/service/nvnflinger/buffer_item.h"
|
||||
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
|
||||
#include "core/hle/service/nvnflinger/buffer_queue_producer.h"
|
||||
#include "core/hle/service/nvnflinger/hardware_composer.h"
|
||||
#include "core/hle/service/nvnflinger/hwc_layer.h"
|
||||
#include "core/hle/service/nvnflinger/ui/graphic_buffer.h"
|
||||
@@ -46,31 +45,9 @@ HardwareComposer::HardwareComposer() = default;
|
||||
HardwareComposer::~HardwareComposer() = default;
|
||||
|
||||
u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
|
||||
Nvidia::Devices::nvdisp_disp0& nvdisp, u32 frame_advance) {
|
||||
Nvidia::Devices::nvdisp_disp0& nvdisp) {
|
||||
boost::container::small_vector<HwcLayer, 2> composition_stack;
|
||||
|
||||
m_frame_number += frame_advance;
|
||||
|
||||
// Release any necessary framebuffers.
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
// Not yet ready to release this framebuffer.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* layer = display.FindLayer(layer_id); layer != nullptr) {
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->GetConsumer().ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Set default speed limit to 100%.
|
||||
*out_speed_scale = 1.0f;
|
||||
|
||||
@@ -142,7 +119,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, VI::Display& display,
|
||||
MicroProfileFlip();
|
||||
|
||||
// Advance by at least one frame.
|
||||
return swap_interval.value_or(1);
|
||||
const u32 frame_advance = swap_interval.value_or(1);
|
||||
m_frame_number += frame_advance;
|
||||
|
||||
// Release any necessary framebuffers.
|
||||
for (auto& [layer_id, framebuffer] : m_framebuffers) {
|
||||
if (framebuffer.release_frame_number > m_frame_number) {
|
||||
// Not yet ready to release this framebuffer.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!framebuffer.is_acquired) {
|
||||
// Already released.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* layer = display.FindLayer(layer_id); layer != nullptr) {
|
||||
// TODO: support release fence
|
||||
// This is needed to prevent screen tearing
|
||||
layer->GetConsumer().ReleaseBuffer(framebuffer.item, android::Fence::NoFence());
|
||||
framebuffer.is_acquired = false;
|
||||
}
|
||||
}
|
||||
|
||||
return frame_advance;
|
||||
}
|
||||
|
||||
void HardwareComposer::RemoveLayerLocked(VI::Display& display, LayerId layer_id) {
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
~HardwareComposer();
|
||||
|
||||
u32 ComposeLocked(f32* out_speed_scale, VI::Display& display,
|
||||
Nvidia::Devices::nvdisp_disp0& nvdisp, u32 frame_advance);
|
||||
Nvidia::Devices::nvdisp_disp0& nvdisp);
|
||||
void RemoveLayerLocked(VI::Display& display, LayerId layer_id);
|
||||
|
||||
private:
|
||||
|
||||
@@ -291,8 +291,7 @@ void Nvnflinger::Compose() {
|
||||
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
|
||||
ASSERT(nvdisp);
|
||||
|
||||
swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp,
|
||||
swap_interval);
|
||||
swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_process_page_table.h"
|
||||
#include "core/hle/service/hid/hid_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/memory.h"
|
||||
@@ -46,12 +47,23 @@ StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMeta
|
||||
|
||||
StandardVmCallbacks::~StandardVmCallbacks() = default;
|
||||
|
||||
void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
|
||||
system.ApplicationMemory().ReadBlock(SanitizeAddress(address), data, size);
|
||||
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
|
||||
// Return zero on invalid address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
std::memset(data, 0, size);
|
||||
return;
|
||||
}
|
||||
|
||||
system.ApplicationMemory().ReadBlock(address, data, size);
|
||||
}
|
||||
|
||||
void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
|
||||
system.ApplicationMemory().WriteBlock(SanitizeAddress(address), data, size);
|
||||
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
|
||||
// Skip invalid memory write address
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
system.ApplicationMemory().WriteBlock(address, data, size);
|
||||
}
|
||||
|
||||
u64 StandardVmCallbacks::HidKeysDown() {
|
||||
@@ -81,21 +93,25 @@ void StandardVmCallbacks::CommandLog(std::string_view data) {
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
|
||||
}
|
||||
|
||||
VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
|
||||
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
||||
if ((in < metadata.main_nso_extents.base ||
|
||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
||||
(in < metadata.heap_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
|
||||
LOG_ERROR(CheatEngine,
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
||||
(in < metadata.alias_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.alias_extents.size) &&
|
||||
(in < metadata.aslr_extents.base ||
|
||||
in >= metadata.heap_extents.base + metadata.aslr_extents.size)) {
|
||||
LOG_DEBUG(CheatEngine,
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this "
|
||||
"persists, "
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if "
|
||||
"the game has not properly set up yet.",
|
||||
in);
|
||||
return 0; ///< Invalid addresses will hard crash
|
||||
return false; ///< Invalid addresses will hard crash
|
||||
}
|
||||
|
||||
return in;
|
||||
return true;
|
||||
}
|
||||
|
||||
CheatParser::~CheatParser() = default;
|
||||
@@ -211,16 +227,14 @@ void CheatEngine::Initialize() {
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()),
|
||||
.size = page_table.GetHeapRegionSize(),
|
||||
};
|
||||
|
||||
metadata.address_space_extents = {
|
||||
.base = GetInteger(page_table.GetAddressSpaceStart()),
|
||||
.size = page_table.GetAddressSpaceSize(),
|
||||
};
|
||||
|
||||
metadata.alias_extents = {
|
||||
metadata.aslr_extents = {
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
||||
.size = page_table.GetAliasCodeRegionSize(),
|
||||
};
|
||||
metadata.alias_extents = {
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()),
|
||||
.size = page_table.GetAliasRegionSize(),
|
||||
};
|
||||
|
||||
is_pending_reload.exchange(true);
|
||||
}
|
||||
|
||||
@@ -27,17 +27,17 @@ public:
|
||||
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_);
|
||||
~StandardVmCallbacks() override;
|
||||
|
||||
void MemoryRead(VAddr address, void* data, u64 size) override;
|
||||
void MemoryWrite(VAddr address, const void* data, u64 size) override;
|
||||
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override;
|
||||
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override;
|
||||
u64 HidKeysDown() override;
|
||||
void DebugLog(u8 id, u64 value) override;
|
||||
void CommandLog(std::string_view data) override;
|
||||
|
||||
private:
|
||||
VAddr SanitizeAddress(VAddr address) const;
|
||||
bool IsAddressInRange(VAddr address) const;
|
||||
|
||||
const CheatProcessMetadata& metadata;
|
||||
System& system;
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
|
||||
|
||||
@@ -18,7 +18,7 @@ struct CheatProcessMetadata {
|
||||
MemoryRegionExtents main_nso_extents{};
|
||||
MemoryRegionExtents heap_extents{};
|
||||
MemoryRegionExtents alias_extents{};
|
||||
MemoryRegionExtents address_space_extents{};
|
||||
MemoryRegionExtents aslr_extents{};
|
||||
std::array<u8, 0x20> main_nso_build_id{};
|
||||
};
|
||||
|
||||
|
||||
@@ -322,8 +322,9 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
|
||||
} break;
|
||||
case CheatVmOpcodeType::EndConditionalBlock: {
|
||||
// 20000000
|
||||
// There's actually nothing left to process here!
|
||||
opcode.opcode = EndConditionalOpcode{};
|
||||
opcode.opcode = EndConditionalOpcode{
|
||||
.is_else = ((first_dword >> 24) & 0xf) == 1,
|
||||
};
|
||||
} break;
|
||||
case CheatVmOpcodeType::ControlLoop: {
|
||||
// 300R0000 VVVVVVVV
|
||||
@@ -555,6 +556,18 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
|
||||
.idx = first_dword & 0xF,
|
||||
};
|
||||
} break;
|
||||
case CheatVmOpcodeType::PauseProcess: {
|
||||
/* FF0????? */
|
||||
/* FF0 = opcode 0xFF0 */
|
||||
/* Pauses the current process. */
|
||||
opcode.opcode = PauseProcessOpcode{};
|
||||
} break;
|
||||
case CheatVmOpcodeType::ResumeProcess: {
|
||||
/* FF0????? */
|
||||
/* FF0 = opcode 0xFF0 */
|
||||
/* Pauses the current process. */
|
||||
opcode.opcode = ResumeProcessOpcode{};
|
||||
} break;
|
||||
case CheatVmOpcodeType::DebugLog: {
|
||||
// FFFTIX##
|
||||
// FFFTI0Ma aaaaaaaa
|
||||
@@ -621,7 +634,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
void DmntCheatVm::SkipConditionalBlock() {
|
||||
void DmntCheatVm::SkipConditionalBlock(bool is_if) {
|
||||
if (condition_depth > 0) {
|
||||
// We want to continue until we're out of the current block.
|
||||
const std::size_t desired_depth = condition_depth - 1;
|
||||
@@ -637,8 +650,12 @@ void DmntCheatVm::SkipConditionalBlock() {
|
||||
// We also support nesting of conditional blocks, and Gateway does not.
|
||||
if (skip_opcode.begin_conditional_block) {
|
||||
condition_depth++;
|
||||
} else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) {
|
||||
condition_depth--;
|
||||
} else if (auto end_cond = std::get_if<EndConditionalOpcode>(&skip_opcode.opcode)) {
|
||||
if (!end_cond->is_else) {
|
||||
condition_depth--;
|
||||
} else if (is_if && condition_depth - 1 == desired_depth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -675,6 +692,10 @@ u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
|
||||
return metadata.main_nso_extents.base + rel_address;
|
||||
case MemoryAccessType::Heap:
|
||||
return metadata.heap_extents.base + rel_address;
|
||||
case MemoryAccessType::Alias:
|
||||
return metadata.alias_extents.base + rel_address;
|
||||
case MemoryAccessType::Aslr:
|
||||
return metadata.aslr_extents.base + rel_address;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,7 +703,6 @@ void DmntCheatVm::ResetState() {
|
||||
registers.fill(0);
|
||||
saved_values.fill(0);
|
||||
loop_tops.fill(0);
|
||||
static_registers.fill(0);
|
||||
instruction_ptr = 0;
|
||||
condition_depth = 0;
|
||||
decode_success = true;
|
||||
@@ -753,7 +773,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width);
|
||||
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, store_static->bit_width);
|
||||
break;
|
||||
}
|
||||
} else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
|
||||
@@ -766,7 +786,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width);
|
||||
callbacks->MemoryReadUnsafe(src_address, &src_value, begin_cond->bit_width);
|
||||
break;
|
||||
}
|
||||
// Check against condition.
|
||||
@@ -794,13 +814,18 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
}
|
||||
// Skip conditional block if condition not met.
|
||||
if (!cond_met) {
|
||||
SkipConditionalBlock();
|
||||
SkipConditionalBlock(true);
|
||||
}
|
||||
} else if (std::holds_alternative<EndConditionalOpcode>(cur_opcode.opcode)) {
|
||||
// Decrement the condition depth.
|
||||
// We will assume, graciously, that mismatched conditional block ends are a nop.
|
||||
if (condition_depth > 0) {
|
||||
condition_depth--;
|
||||
} else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
|
||||
if (end_cond->is_else) {
|
||||
/* Skip to the end of the conditional block. */
|
||||
this->SkipConditionalBlock(false);
|
||||
} else {
|
||||
/* Decrement the condition depth. */
|
||||
/* We will assume, graciously, that mismatched conditional block ends are a nop. */
|
||||
if (condition_depth > 0) {
|
||||
condition_depth--;
|
||||
}
|
||||
}
|
||||
} else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) {
|
||||
if (ctrl_loop->start_loop) {
|
||||
@@ -832,8 +857,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryRead(src_address, ®isters[ldr_memory->reg_index],
|
||||
ldr_memory->bit_width);
|
||||
callbacks->MemoryReadUnsafe(src_address, ®isters[ldr_memory->reg_index],
|
||||
ldr_memory->bit_width);
|
||||
break;
|
||||
}
|
||||
} else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
|
||||
@@ -849,7 +874,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width);
|
||||
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_static->bit_width);
|
||||
break;
|
||||
}
|
||||
// Increment register if relevant.
|
||||
@@ -908,7 +933,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
// Check for keypress.
|
||||
if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) {
|
||||
// Keys not pressed. Skip conditional block.
|
||||
SkipConditionalBlock();
|
||||
SkipConditionalBlock(true);
|
||||
}
|
||||
} else if (auto perform_math_reg =
|
||||
std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
|
||||
@@ -1007,7 +1032,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width);
|
||||
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_register->bit_width);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1086,7 +1111,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width);
|
||||
callbacks->MemoryReadUnsafe(cond_address, &cond_value,
|
||||
begin_reg_cond->bit_width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1116,7 +1142,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
|
||||
// Skip conditional block if condition not met.
|
||||
if (!cond_met) {
|
||||
SkipConditionalBlock();
|
||||
SkipConditionalBlock(true);
|
||||
}
|
||||
} else if (auto save_restore_reg =
|
||||
std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) {
|
||||
@@ -1178,6 +1204,10 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
// Store a register to a static register.
|
||||
static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
|
||||
}
|
||||
} else if (std::holds_alternative<PauseProcessOpcode>(cur_opcode.opcode)) {
|
||||
// TODO: Pause cheat process
|
||||
} else if (std::holds_alternative<ResumeProcessOpcode>(cur_opcode.opcode)) {
|
||||
// TODO: Resume cheat process
|
||||
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
|
||||
// Read value from memory.
|
||||
u64 log_value = 0;
|
||||
@@ -1224,7 +1254,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width);
|
||||
callbacks->MemoryReadUnsafe(val_address, &log_value, debug_log->bit_width);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,12 +42,16 @@ enum class CheatVmOpcodeType : u32 {
|
||||
DoubleExtendedWidth = 0xF0,
|
||||
|
||||
// Double-extended width opcodes.
|
||||
PauseProcess = 0xFF0,
|
||||
ResumeProcess = 0xFF1,
|
||||
DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum class MemoryAccessType : u32 {
|
||||
MainNso = 0,
|
||||
Heap = 1,
|
||||
Alias = 2,
|
||||
Aslr = 3,
|
||||
};
|
||||
|
||||
enum class ConditionalComparisonType : u32 {
|
||||
@@ -131,7 +135,9 @@ struct BeginConditionalOpcode {
|
||||
VmInt value{};
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {};
|
||||
struct EndConditionalOpcode {
|
||||
bool is_else;
|
||||
};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop{};
|
||||
@@ -222,6 +228,10 @@ struct ReadWriteStaticRegisterOpcode {
|
||||
u32 idx{};
|
||||
};
|
||||
|
||||
struct PauseProcessOpcode {};
|
||||
|
||||
struct ResumeProcessOpcode {};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width{};
|
||||
u32 log_id{};
|
||||
@@ -244,8 +254,8 @@ struct CheatVmOpcode {
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, DebugLogOpcode,
|
||||
UnrecognizedInstruction>
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
|
||||
opcode{};
|
||||
};
|
||||
|
||||
@@ -256,8 +266,8 @@ public:
|
||||
public:
|
||||
virtual ~Callbacks();
|
||||
|
||||
virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
|
||||
virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
|
||||
virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
|
||||
virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;
|
||||
|
||||
virtual u64 HidKeysDown() = 0;
|
||||
|
||||
@@ -296,7 +306,7 @@ private:
|
||||
std::array<std::size_t, NumRegisters> loop_tops{};
|
||||
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out);
|
||||
void SkipConditionalBlock();
|
||||
void SkipConditionalBlock(bool is_if);
|
||||
void ResetState();
|
||||
|
||||
// For implementing the DebugLog opcode.
|
||||
|
||||
@@ -32,14 +32,9 @@ struct Client::Impl {
|
||||
Impl(std::string host_, std::string username_, std::string token_)
|
||||
: host{std::move(host_)}, username{std::move(username_)}, token{std::move(token_)} {
|
||||
std::scoped_lock lock{jwt_cache.mutex};
|
||||
if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
|
||||
if (username == jwt_cache.username && token == jwt_cache.token) {
|
||||
jwt = jwt_cache.jwt;
|
||||
}
|
||||
|
||||
// Normalize host expression
|
||||
if (!this->host.empty() && this->host.back() == '/') {
|
||||
static_cast<void>(this->host.pop_back());
|
||||
}
|
||||
}
|
||||
|
||||
/// A generic function handles POST, GET and DELETE request together
|
||||
@@ -76,16 +71,18 @@ struct Client::Impl {
|
||||
const std::string& jwt_ = "", const std::string& username_ = "",
|
||||
const std::string& token_ = "") {
|
||||
if (cli == nullptr) {
|
||||
cli = std::make_unique<httplib::Client>(host.c_str());
|
||||
cli->set_connection_timeout(TIMEOUT_SECONDS);
|
||||
cli->set_read_timeout(TIMEOUT_SECONDS);
|
||||
cli->set_write_timeout(TIMEOUT_SECONDS);
|
||||
cli = std::make_unique<httplib::Client>(host);
|
||||
}
|
||||
|
||||
if (!cli->is_valid()) {
|
||||
LOG_ERROR(WebService, "Invalid URL {}", host + path);
|
||||
return WebResult{WebResult::Code::InvalidURL, "Invalid URL", ""};
|
||||
LOG_ERROR(WebService, "Client is invalid, skipping request!");
|
||||
return {};
|
||||
}
|
||||
|
||||
cli->set_connection_timeout(TIMEOUT_SECONDS);
|
||||
cli->set_read_timeout(TIMEOUT_SECONDS);
|
||||
cli->set_write_timeout(TIMEOUT_SECONDS);
|
||||
|
||||
httplib::Headers params;
|
||||
if (!jwt_.empty()) {
|
||||
params = {
|
||||
@@ -110,15 +107,15 @@ struct Client::Impl {
|
||||
request.headers = params;
|
||||
request.body = data;
|
||||
|
||||
httplib::Result result = cli->send(request);
|
||||
httplib::Response response;
|
||||
httplib::Error error;
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
|
||||
if (!cli->send(request, response, error)) {
|
||||
LOG_ERROR(WebService, "{} to {} returned null (httplib Error: {})", method, host + path,
|
||||
httplib::to_string(error));
|
||||
return WebResult{WebResult::Code::LibError, "Null response", ""};
|
||||
}
|
||||
|
||||
httplib::Response response = result.value();
|
||||
|
||||
if (response.status >= 400) {
|
||||
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
|
||||
response.status);
|
||||
|
||||
@@ -45,15 +45,23 @@ ConfigureHotkeys::ConfigureHotkeys(Core::HID::HIDCore& hid_core, QWidget* parent
|
||||
|
||||
controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
|
||||
connect(timeout_timer.get(), &QTimer::timeout, [this] {
|
||||
const bool is_button_pressed = pressed_buttons != Core::HID::NpadButton::None ||
|
||||
pressed_home_button || pressed_capture_button;
|
||||
SetPollingResult(!is_button_pressed);
|
||||
});
|
||||
|
||||
connect(poll_timer.get(), &QTimer::timeout, [this] {
|
||||
const auto buttons = controller->GetNpadButtons();
|
||||
const auto home_pressed = controller->GetHomeButtons().home != 0;
|
||||
const auto capture_pressed = controller->GetCaptureButtons().capture != 0;
|
||||
if (home_pressed || capture_pressed) {
|
||||
SetPollingResult(buttons.raw, false);
|
||||
return;
|
||||
pressed_buttons |= controller->GetNpadButtons().raw;
|
||||
pressed_home_button |= this->controller->GetHomeButtons().home != 0;
|
||||
pressed_capture_button |= this->controller->GetCaptureButtons().capture != 0;
|
||||
if (pressed_buttons != Core::HID::NpadButton::None || pressed_home_button ||
|
||||
pressed_capture_button) {
|
||||
const QString button_name =
|
||||
GetButtonCombinationName(pressed_buttons, pressed_home_button,
|
||||
pressed_capture_button) +
|
||||
QStringLiteral("...");
|
||||
model->setData(button_model_index, button_name);
|
||||
}
|
||||
});
|
||||
RetranslateUI();
|
||||
@@ -154,16 +162,14 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) {
|
||||
|
||||
const auto previous_key = model->data(index);
|
||||
|
||||
input_setter = [this, index, previous_key](const Core::HID::NpadButton button,
|
||||
const bool cancel) {
|
||||
input_setter = [this, index, previous_key](const bool cancel) {
|
||||
if (cancel) {
|
||||
model->setData(index, previous_key);
|
||||
return;
|
||||
}
|
||||
const auto home_pressed = this->controller->GetHomeButtons().home != 0;
|
||||
const auto capture_pressed = this->controller->GetCaptureButtons().capture != 0;
|
||||
|
||||
const QString button_string =
|
||||
GetButtonCombinationName(button, home_pressed, capture_pressed);
|
||||
GetButtonCombinationName(pressed_buttons, pressed_home_button, pressed_capture_button);
|
||||
|
||||
const auto [key_sequence_used, used_action] = IsUsedControllerKey(button_string);
|
||||
|
||||
@@ -177,17 +183,22 @@ void ConfigureHotkeys::ConfigureController(QModelIndex index) {
|
||||
}
|
||||
};
|
||||
|
||||
button_model_index = index;
|
||||
pressed_buttons = Core::HID::NpadButton::None;
|
||||
pressed_home_button = false;
|
||||
pressed_capture_button = false;
|
||||
|
||||
model->setData(index, tr("[waiting]"));
|
||||
timeout_timer->start(2500); // Cancel after 2.5 seconds
|
||||
poll_timer->start(200); // Check for new inputs every 200ms
|
||||
poll_timer->start(100); // Check for new inputs every 100ms
|
||||
// We need to disable configuration to be able to read npad buttons
|
||||
controller->DisableConfiguration();
|
||||
}
|
||||
|
||||
void ConfigureHotkeys::SetPollingResult(Core::HID::NpadButton button, const bool cancel) {
|
||||
void ConfigureHotkeys::SetPollingResult(const bool cancel) {
|
||||
timeout_timer->stop();
|
||||
poll_timer->stop();
|
||||
(*input_setter)(button, cancel);
|
||||
(*input_setter)(cancel);
|
||||
// Re-Enable configuration
|
||||
controller->EnableConfiguration();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QStandardItemModel>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Common {
|
||||
@@ -54,14 +55,20 @@ private:
|
||||
void RestoreControllerHotkey(QModelIndex index);
|
||||
void RestoreHotkey(QModelIndex index);
|
||||
|
||||
void SetPollingResult(bool cancel);
|
||||
QString GetButtonCombinationName(Core::HID::NpadButton button, bool home, bool capture) const;
|
||||
|
||||
std::unique_ptr<Ui::ConfigureHotkeys> ui;
|
||||
|
||||
QStandardItemModel* model;
|
||||
|
||||
void SetPollingResult(Core::HID::NpadButton button, bool cancel);
|
||||
QString GetButtonCombinationName(Core::HID::NpadButton button, bool home, bool capture) const;
|
||||
bool pressed_home_button;
|
||||
bool pressed_capture_button;
|
||||
QModelIndex button_model_index;
|
||||
Core::HID::NpadButton pressed_buttons;
|
||||
|
||||
Core::HID::EmulatedController* controller;
|
||||
std::unique_ptr<QTimer> timeout_timer;
|
||||
std::unique_ptr<QTimer> poll_timer;
|
||||
std::optional<std::function<void(Core::HID::NpadButton, bool)>> input_setter;
|
||||
std::optional<std::function<void(bool)>> input_setter;
|
||||
};
|
||||
|
||||
@@ -73,8 +73,11 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
|
||||
ui->tabWidget->addTab(graphics_advanced_tab.get(), tr("Adv. Graphics"));
|
||||
ui->tabWidget->addTab(audio_tab.get(), tr("Audio"));
|
||||
ui->tabWidget->addTab(input_tab.get(), tr("Input Profiles"));
|
||||
|
||||
// Only show Linux tab on Unix
|
||||
linux_tab->setVisible(false);
|
||||
#ifdef __unix__
|
||||
linux_tab->setVisible(true);
|
||||
ui->tabWidget->addTab(linux_tab.get(), tr("Linux"));
|
||||
#endif
|
||||
|
||||
|
||||
@@ -248,22 +248,82 @@ void ConfigureUi::RetranslateUI() {
|
||||
}
|
||||
|
||||
void ConfigureUi::InitializeLanguageComboBox() {
|
||||
// This is a list of lexicographically sorted languages, only the available translations are
|
||||
// shown to the user.
|
||||
static const struct {
|
||||
const QString name;
|
||||
const char* id;
|
||||
} languages[] = {
|
||||
// clang-format off
|
||||
{QStringLiteral(u"Bahasa Indonesia"), "id"}, // Indonesian
|
||||
{QStringLiteral(u"Bahasa Melayu"), "ms"}, // Malay
|
||||
{QStringLiteral(u"Catal\u00E0"), "ca"}, // Catalan
|
||||
{QStringLiteral(u"\u010Ce\u0161tina"), "cs"}, // Czech
|
||||
{QStringLiteral(u"Dansk"), "da"}, // Danish
|
||||
{QStringLiteral(u"Deutsch"), "de"}, // German
|
||||
{QStringLiteral(u"English"), "en"}, // English
|
||||
{QStringLiteral(u"Espa\u00F1ol"), "es"}, // Spanish
|
||||
{QStringLiteral(u"Fran\u00E7ais"), "fr"}, // French
|
||||
{QStringLiteral(u"Hrvatski"), "hr"}, // Croatian
|
||||
{QStringLiteral(u"Italiano"), "it"}, // Italian
|
||||
{QStringLiteral(u"Magyar"), "hu"}, // Hungarian
|
||||
{QStringLiteral(u"Nederlands"), "nl"}, // Dutch
|
||||
{QStringLiteral(u"Norsk bokm\u00E5l"), "nb"}, // Norwegian
|
||||
{QStringLiteral(u"Polski"), "pl"}, // Polish
|
||||
{QStringLiteral(u"Portugu\u00EAs"), "pt_PT"}, // Portuguese
|
||||
{QStringLiteral(u"Portugu\u00EAs (Brasil)"), "pt_BR"}, // Portuguese (Brazil)
|
||||
{QStringLiteral(u"Rom\u00E2n\u0103"), "ro"}, // Romanian
|
||||
{QStringLiteral(u"Srpski"), "sr"}, // Serbian
|
||||
{QStringLiteral(u"Suomi"), "fi"}, // Finnish
|
||||
{QStringLiteral(u"Svenska"), "sv"}, // Swedish
|
||||
{QStringLiteral(u"Ti\u1EBFng Vi\u1EC7t"), "vi"}, // Vietnamese
|
||||
{QStringLiteral(u"Ti\u1EBFng Vi\u1EC7t (Vi\u1EC7t Nam)"), "vi_VN"}, // Vietnamese
|
||||
{QStringLiteral(u"T\u00FCrk\u00E7e"), "tr_TR"}, // Turkish
|
||||
{QStringLiteral(u"\u0395\u03BB\u03BB\u03B7\u03BD\u03B9\u03BA\u03AC"), "el"}, // Greek
|
||||
{QStringLiteral(u"\u0420\u0443\u0441\u0441\u043A\u0438\u0439"), "ru_RU"}, // Russian
|
||||
{QStringLiteral(u"\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"),
|
||||
"uk"}, // Ukrainian
|
||||
{QStringLiteral(u"\u0627\u0644\u0639\u0631\u0628\u064A\u0629"), "ar"}, // Arabic
|
||||
{QStringLiteral(u"\u0641\u0627\u0631\u0633\u06CC"), "fa"}, // Farsi
|
||||
{QStringLiteral(u"\uD55C\uAD6D\uC5B4"), "ko_KR"}, // Korean
|
||||
{QStringLiteral(u"\u65E5\u672C\u8A9E"), "ja_JP"}, // Japanese
|
||||
{QStringLiteral(u"\u7B80\u4F53\u4E2D\u6587"), "zh_CN"}, // Simplified Chinese
|
||||
{QStringLiteral(u"\u7E41\u9AD4\u4E2D\u6587"), "zh_TW"}, // Traditional Chinese
|
||||
// clang-format on
|
||||
};
|
||||
ui->language_combobox->addItem(tr("<System>"), QString{});
|
||||
ui->language_combobox->addItem(tr("English"), QStringLiteral("en"));
|
||||
QDirIterator it(QStringLiteral(":/languages"), QDirIterator::NoIteratorFlags);
|
||||
while (it.hasNext()) {
|
||||
QString locale = it.next();
|
||||
QDir languages_dir{QStringLiteral(":/languages")};
|
||||
QStringList language_files = languages_dir.entryList();
|
||||
for (const auto& lang : languages) {
|
||||
if (QString::fromLatin1(lang.id) == QStringLiteral("en")) {
|
||||
ui->language_combobox->addItem(lang.name, QStringLiteral("en"));
|
||||
language_files.removeOne(QStringLiteral("en.qm"));
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < language_files.size(); ++i) {
|
||||
QString locale = language_files[i];
|
||||
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
|
||||
if (QString::fromLatin1(lang.id) == locale) {
|
||||
ui->language_combobox->addItem(lang.name, locale);
|
||||
language_files.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Anything remaining will be at the bottom
|
||||
for (const QString& file : language_files) {
|
||||
LOG_CRITICAL(Frontend, "Unexpected Language File: {}", file.toStdString());
|
||||
QString locale = file;
|
||||
locale.truncate(locale.lastIndexOf(QLatin1Char{'.'}));
|
||||
locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1);
|
||||
const QString lang = QLocale::languageToString(QLocale(locale).language());
|
||||
const QString country = QLocale::countryToString(QLocale(locale).country());
|
||||
ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale);
|
||||
const QString language_name = QLocale::languageToString(QLocale(locale).language());
|
||||
const QString lang = QStringLiteral("%1 [%2]").arg(language_name, locale);
|
||||
ui->language_combobox->addItem(lang, locale);
|
||||
}
|
||||
|
||||
// Unlike other configuration changes, interface language changes need to be reflected on the
|
||||
// interface immediately. This is done by passing a signal to the main window, and then
|
||||
// retranslating when passing back.
|
||||
connect(ui->language_combobox, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
connect(ui->language_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&ConfigureUi::OnLanguageChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -193,8 +193,7 @@ void ControllerShortcut::ControllerUpdateEvent(Core::HID::ControllerTriggerType
|
||||
if (!Settings::values.controller_navigation) {
|
||||
return;
|
||||
}
|
||||
if (button_sequence.npad.raw == Core::HID::NpadButton::None &&
|
||||
button_sequence.capture.raw == 0 && button_sequence.home.raw == 0) {
|
||||
if (button_sequence.npad.raw == Core::HID::NpadButton::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user