mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-09 22:56:35 +00:00
dmnt: refactor to use sts:: namespace.
This commit is contained in:
parent
a750e55f75
commit
78a730ddf6
36 changed files with 3684 additions and 4318 deletions
|
@ -26,7 +26,7 @@ endif
|
|||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := source
|
||||
SOURCES := source source/cheat source/cheat/impl
|
||||
DATA := data
|
||||
INCLUDES := include ../../common/include
|
||||
EXEFS_SRC := exefs_src
|
||||
|
|
169
stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp
Normal file
169
stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "dmnt_cheat_service.hpp"
|
||||
#include "impl/dmnt_cheat_api.hpp"
|
||||
|
||||
namespace sts::dmnt::cheat {
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* ==================================== Meta Commands ==================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
void CheatService::HasCheatProcess(Out<bool> out) {
|
||||
out.SetValue(dmnt::cheat::impl::GetHasActiveCheatProcess());
|
||||
}
|
||||
|
||||
void CheatService::GetCheatProcessEvent(Out<CopiedHandle> out_event) {
|
||||
out_event.SetValue(dmnt::cheat::impl::GetCheatProcessEventHandle());
|
||||
}
|
||||
|
||||
Result CheatService::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||
return dmnt::cheat::impl::GetCheatProcessMetadata(out_metadata.GetPointer());
|
||||
}
|
||||
|
||||
Result CheatService::ForceOpenCheatProcess() {
|
||||
if (R_FAILED(dmnt::cheat::impl::ForceOpenCheatProcess())) {
|
||||
return ResultDmntCheatNotAttached;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Memory Commands =================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result CheatService::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||
return dmnt::cheat::impl::GetCheatProcessMappingCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result CheatService::GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset) {
|
||||
if (mappings.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::GetCheatProcessMappings(mappings.buffer, mappings.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result CheatService::ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size) {
|
||||
if (buffer.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::ReadCheatProcessMemory(address, buffer.buffer, std::min(out_size, buffer.num_elements));
|
||||
}
|
||||
|
||||
Result CheatService::WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size) {
|
||||
if (buffer.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::WriteCheatProcessMemory(address, buffer.buffer, std::min(in_size, buffer.num_elements));
|
||||
}
|
||||
|
||||
Result CheatService::QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address) {
|
||||
return dmnt::cheat::impl::QueryCheatProcessMemory(mapping.GetPointer(), address);
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Cheat Commands ==================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result CheatService::GetCheatCount(Out<u64> out_count) {
|
||||
return dmnt::cheat::impl::GetCheatCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result CheatService::GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset) {
|
||||
if (cheats.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::GetCheats(cheats.buffer, cheats.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result CheatService::GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id) {
|
||||
if (cheat.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
if (cheat.num_elements < 1) {
|
||||
return ResultDmntCheatInvalidBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::GetCheatById(cheat.buffer, cheat_id);
|
||||
}
|
||||
|
||||
Result CheatService::ToggleCheat(u32 cheat_id) {
|
||||
return dmnt::cheat::impl::ToggleCheat(cheat_id);
|
||||
}
|
||||
|
||||
Result CheatService::AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled) {
|
||||
if (cheat.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
if (cheat.num_elements < 1) {
|
||||
return ResultDmntCheatInvalidBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::AddCheat(out_cheat_id.GetPointer(), cheat.buffer, enabled);
|
||||
}
|
||||
|
||||
Result CheatService::RemoveCheat(u32 cheat_id) {
|
||||
return dmnt::cheat::impl::RemoveCheat(cheat_id);
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Address Commands ================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result CheatService::GetFrozenAddressCount(Out<u64> out_count) {
|
||||
return dmnt::cheat::impl::GetFrozenAddressCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result CheatService::GetFrozenAddresses(OutBuffer<FrozenAddressEntry> frz_addrs, Out<u64> out_count, u64 offset) {
|
||||
if (frz_addrs.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::GetFrozenAddresses(frz_addrs.buffer, frz_addrs.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result CheatService::GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address) {
|
||||
return dmnt::cheat::impl::GetFrozenAddress(entry.GetPointer(), address);
|
||||
}
|
||||
|
||||
Result CheatService::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
break;
|
||||
default:
|
||||
return ResultDmntCheatInvalidFreezeWidth;
|
||||
}
|
||||
|
||||
return dmnt::cheat::impl::EnableFrozenAddress(out_value.GetPointer(), address, width);
|
||||
}
|
||||
|
||||
Result CheatService::DisableFrozenAddress(u64 address) {
|
||||
return dmnt::cheat::impl::DisableFrozenAddress(address);
|
||||
}
|
||||
|
||||
}
|
108
stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp
Normal file
108
stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/dmnt.hpp>
|
||||
|
||||
namespace sts::dmnt::cheat {
|
||||
|
||||
class CheatService final : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
/* Meta */
|
||||
HasCheatProcess = 65000,
|
||||
GetCheatProcessEvent = 65001,
|
||||
GetCheatProcessMetadata = 65002,
|
||||
ForceOpenCheatProcess = 65003,
|
||||
|
||||
/* Interact with Memory */
|
||||
GetCheatProcessMappingCount = 65100,
|
||||
GetCheatProcessMappings = 65101,
|
||||
ReadCheatProcessMemory = 65102,
|
||||
WriteCheatProcessMemory = 65103,
|
||||
QueryCheatProcessMemory = 65104,
|
||||
|
||||
/* Interact with Cheats */
|
||||
GetCheatCount = 65200,
|
||||
GetCheats = 65201,
|
||||
GetCheatById = 65202,
|
||||
ToggleCheat = 65203,
|
||||
AddCheat = 65204,
|
||||
RemoveCheat = 65205,
|
||||
|
||||
/* Interact with Frozen Addresses */
|
||||
GetFrozenAddressCount = 65300,
|
||||
GetFrozenAddresses = 65301,
|
||||
GetFrozenAddress = 65302,
|
||||
EnableFrozenAddress = 65303,
|
||||
DisableFrozenAddress = 65304,
|
||||
};
|
||||
private:
|
||||
void HasCheatProcess(Out<bool> out);
|
||||
void GetCheatProcessEvent(Out<CopiedHandle> out_event);
|
||||
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||
Result ForceOpenCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||
Result GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset);
|
||||
Result ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size);
|
||||
Result WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size);
|
||||
Result QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address);
|
||||
|
||||
Result GetCheatCount(Out<u64> out_count);
|
||||
Result GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset);
|
||||
Result GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
Result AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
|
||||
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||
Result GetFrozenAddresses(OutBuffer<FrozenAddressEntry> addresses, Out<u64> out_count, u64 offset);
|
||||
Result GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address);
|
||||
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, HasCheatProcess),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessEvent),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMetadata),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, ForceOpenCheatProcess),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMappingCount),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMappings),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, ReadCheatProcessMemory),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, WriteCheatProcessMemory),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, QueryCheatProcessMemory),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatCount),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheats),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetCheatById),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, ToggleCheat),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, AddCheat),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, RemoveCheat),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddressCount),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddresses),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddress),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, EnableFrozenAddress),
|
||||
MAKE_SERVICE_COMMAND_META(CheatService, DisableFrozenAddress),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
1084
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp
Normal file
1084
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp
Normal file
File diff suppressed because it is too large
Load diff
50
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp
Normal file
50
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/dmnt.hpp>
|
||||
|
||||
namespace sts::dmnt::cheat::impl {
|
||||
|
||||
bool GetHasActiveCheatProcess();
|
||||
Handle GetCheatProcessEventHandle();
|
||||
Result GetCheatProcessMetadata(CheatProcessMetadata *out);
|
||||
Result ForceOpenCheatProcess();
|
||||
|
||||
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size);
|
||||
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size);
|
||||
|
||||
Result GetCheatProcessMappingCount(u64 *out_count);
|
||||
Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset);
|
||||
Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size);
|
||||
Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size);
|
||||
Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address);
|
||||
|
||||
Result GetCheatCount(u64 *out_count);
|
||||
Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset);
|
||||
Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
Result AddCheat(u32 *out_id, const CheatDefinition *def, bool enabled);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
|
||||
Result GetFrozenAddressCount(u64 *out_count);
|
||||
Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset);
|
||||
Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address);
|
||||
Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "dmnt_cheat_debug_events_manager.hpp"
|
||||
|
||||
/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */
|
||||
|
||||
namespace sts::dmnt::cheat::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
class DebugEventsManager {
|
||||
public:
|
||||
static constexpr size_t NumCores = 4;
|
||||
private:
|
||||
HosMessageQueue message_queues[NumCores];
|
||||
HosThread threads[NumCores];
|
||||
HosSignal continued_signal;
|
||||
private:
|
||||
static void PerCoreThreadFunction(void *_this) {
|
||||
/* This thread will wait on the appropriate message queue. */
|
||||
DebugEventsManager *this_ptr = reinterpret_cast<DebugEventsManager *>(_this);
|
||||
const u32 current_core = svcGetCurrentProcessorNumber();
|
||||
while (true) {
|
||||
/* Receive handle. */
|
||||
Handle debug_handle = this_ptr->WaitReceiveHandle(current_core);
|
||||
|
||||
/* Continue events on the correct core. */
|
||||
R_ASSERT(this_ptr->ContinueDebugEvent(debug_handle));
|
||||
|
||||
/* Signal that we've continued. */
|
||||
this_ptr->SignalContinued();
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetTargetCore(const svc::DebugEventInfo &dbg_event, Handle debug_handle) {
|
||||
/* If we don't need to continue on a specific core, use the system core. */
|
||||
u32 target_core = NumCores - 1;
|
||||
|
||||
/* Retrieve correct core for new thread event. */
|
||||
if (dbg_event.type == svc::DebugEventType::AttachThread) {
|
||||
u64 out64 = 0;
|
||||
u32 out32 = 0;
|
||||
R_ASSERT(svcGetDebugThreadParam(&out64, &out32, debug_handle, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore));
|
||||
target_core = out32;
|
||||
}
|
||||
|
||||
return target_core;
|
||||
}
|
||||
|
||||
void SendHandle(const svc::DebugEventInfo &dbg_event, Handle debug_handle) {
|
||||
this->message_queues[GetTargetCore(dbg_event, debug_handle)].Send(static_cast<uintptr_t>(debug_handle));
|
||||
}
|
||||
|
||||
Handle WaitReceiveHandle(u32 core_id) {
|
||||
uintptr_t x = 0;
|
||||
this->message_queues[core_id].Receive(&x);
|
||||
return static_cast<Handle>(x);
|
||||
}
|
||||
|
||||
Result ContinueDebugEvent(Handle debug_handle) {
|
||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) {
|
||||
return svcContinueDebugEvent(debug_handle, 5, nullptr, 0);
|
||||
} else {
|
||||
return svcLegacyContinueDebugEvent(debug_handle, 5, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void WaitContinued() {
|
||||
this->continued_signal.Wait();
|
||||
this->continued_signal.Reset();
|
||||
}
|
||||
|
||||
void SignalContinued() {
|
||||
this->continued_signal.Signal();
|
||||
}
|
||||
|
||||
public:
|
||||
DebugEventsManager() : message_queues{HosMessageQueue(1), HosMessageQueue(1), HosMessageQueue(1), HosMessageQueue(1)} {
|
||||
for (size_t i = 0; i < NumCores; i++) {
|
||||
/* Create thread. */
|
||||
R_ASSERT(this->threads[i].Initialize(&DebugEventsManager::PerCoreThreadFunction, reinterpret_cast<void *>(this), 0x1000, 24, i));
|
||||
|
||||
/* Set core mask. */
|
||||
R_ASSERT(svcSetThreadCoreMask(this->threads[i].GetHandle(), i, (1u << i)));
|
||||
|
||||
/* Start thread. */
|
||||
R_ASSERT(this->threads[i].Start());
|
||||
}
|
||||
}
|
||||
|
||||
void ContinueCheatProcess(Handle cheat_dbg_hnd) {
|
||||
/* Loop getting all debug events. */
|
||||
svc::DebugEventInfo d;
|
||||
while (R_SUCCEEDED(svcGetDebugEvent(reinterpret_cast<u8 *>(&d), cheat_dbg_hnd))) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* Send handle to correct core, wait for continue to finish. */
|
||||
this->SendHandle(d, cheat_dbg_hnd);
|
||||
this->WaitContinued();
|
||||
}
|
||||
};
|
||||
|
||||
/* Manager global. */
|
||||
DebugEventsManager g_events_manager;
|
||||
|
||||
}
|
||||
|
||||
void ContinueCheatProcess(Handle cheat_dbg_hnd) {
|
||||
g_events_manager.ContinueCheatProcess(cheat_dbg_hnd);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,16 +16,10 @@
|
|||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
struct OverrideKey {
|
||||
u64 key_combination;
|
||||
bool override_by_default;
|
||||
};
|
||||
namespace sts::dmnt::cheat::impl {
|
||||
|
||||
class DmntConfigManager {
|
||||
public:
|
||||
static void RefreshConfiguration();
|
||||
void ContinueCheatProcess(Handle cheat_dbg_hnd);
|
||||
|
||||
static OverrideKey GetTitleCheatEnableKey(u64 tid);
|
||||
static bool HasCheatEnableButton(u64 tid);
|
||||
};
|
||||
}
|
1213
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp
Normal file
1213
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp
Normal file
File diff suppressed because it is too large
Load diff
306
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp
Normal file
306
stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp
Normal file
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <stdarg.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/dmnt.hpp>
|
||||
|
||||
namespace sts::dmnt::cheat::impl {
|
||||
|
||||
enum CheatVmOpcodeType : u32 {
|
||||
CheatVmOpcodeType_StoreStatic = 0,
|
||||
CheatVmOpcodeType_BeginConditionalBlock = 1,
|
||||
CheatVmOpcodeType_EndConditionalBlock = 2,
|
||||
CheatVmOpcodeType_ControlLoop = 3,
|
||||
CheatVmOpcodeType_LoadRegisterStatic = 4,
|
||||
CheatVmOpcodeType_LoadRegisterMemory = 5,
|
||||
CheatVmOpcodeType_StoreStaticToAddress = 6,
|
||||
CheatVmOpcodeType_PerformArithmeticStatic = 7,
|
||||
CheatVmOpcodeType_BeginKeypressConditionalBlock = 8,
|
||||
|
||||
/* These are not implemented by Gateway's VM. */
|
||||
CheatVmOpcodeType_PerformArithmeticRegister = 9,
|
||||
CheatVmOpcodeType_StoreRegisterToAddress = 10,
|
||||
CheatVmOpcodeType_Reserved11 = 11,
|
||||
|
||||
/* This is a meta entry, and not a real opcode. */
|
||||
/* This is to facilitate multi-nybble instruction decoding. */
|
||||
CheatVmOpcodeType_ExtendedWidth = 12,
|
||||
|
||||
/* Extended width opcodes. */
|
||||
CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0,
|
||||
CheatVmOpcodeType_SaveRestoreRegister = 0xC1,
|
||||
CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2,
|
||||
|
||||
/* This is a meta entry, and not a real opcode. */
|
||||
/* This is to facilitate multi-nybble instruction decoding. */
|
||||
CheatVmOpcodeType_DoubleExtendedWidth = 0xF0,
|
||||
|
||||
/* Double-extended width opcodes. */
|
||||
CheatVmOpcodeType_DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum MemoryAccessType : u32 {
|
||||
MemoryAccessType_MainNso = 0,
|
||||
MemoryAccessType_Heap = 1,
|
||||
};
|
||||
|
||||
enum ConditionalComparisonType : u32 {
|
||||
ConditionalComparisonType_GT = 1,
|
||||
ConditionalComparisonType_GE = 2,
|
||||
ConditionalComparisonType_LT = 3,
|
||||
ConditionalComparisonType_LE = 4,
|
||||
ConditionalComparisonType_EQ = 5,
|
||||
ConditionalComparisonType_NE = 6,
|
||||
};
|
||||
|
||||
enum RegisterArithmeticType : u32 {
|
||||
RegisterArithmeticType_Addition = 0,
|
||||
RegisterArithmeticType_Subtraction = 1,
|
||||
RegisterArithmeticType_Multiplication = 2,
|
||||
RegisterArithmeticType_LeftShift = 3,
|
||||
RegisterArithmeticType_RightShift = 4,
|
||||
|
||||
/* These are not supported by Gateway's VM. */
|
||||
RegisterArithmeticType_LogicalAnd = 5,
|
||||
RegisterArithmeticType_LogicalOr = 6,
|
||||
RegisterArithmeticType_LogicalNot = 7,
|
||||
RegisterArithmeticType_LogicalXor = 8,
|
||||
|
||||
RegisterArithmeticType_None = 9,
|
||||
};
|
||||
|
||||
enum StoreRegisterOffsetType : u32 {
|
||||
StoreRegisterOffsetType_None = 0,
|
||||
StoreRegisterOffsetType_Reg = 1,
|
||||
StoreRegisterOffsetType_Imm = 2,
|
||||
StoreRegisterOffsetType_MemReg = 3,
|
||||
StoreRegisterOffsetType_MemImm = 4,
|
||||
StoreRegisterOffsetType_MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum CompareRegisterValueType : u32 {
|
||||
CompareRegisterValueType_MemoryRelAddr = 0,
|
||||
CompareRegisterValueType_MemoryOfsReg = 1,
|
||||
CompareRegisterValueType_RegisterRelAddr = 2,
|
||||
CompareRegisterValueType_RegisterOfsReg = 3,
|
||||
CompareRegisterValueType_StaticValue = 4,
|
||||
CompareRegisterValueType_OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum SaveRestoreRegisterOpType : u32 {
|
||||
SaveRestoreRegisterOpType_Restore = 0,
|
||||
SaveRestoreRegisterOpType_Save = 1,
|
||||
SaveRestoreRegisterOpType_ClearSaved = 2,
|
||||
SaveRestoreRegisterOpType_ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum DebugLogValueType : u32 {
|
||||
DebugLogValueType_MemoryRelAddr = 0,
|
||||
DebugLogValueType_MemoryOfsReg = 1,
|
||||
DebugLogValueType_RegisterRelAddr = 2,
|
||||
DebugLogValueType_RegisterOfsReg = 3,
|
||||
DebugLogValueType_RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
u32 offset_register;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
ConditionalComparisonType cond_type;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop;
|
||||
u32 reg_index;
|
||||
u32 num_iters;
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
u32 reg_index;
|
||||
bool load_from_reg;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width;
|
||||
u32 reg_index;
|
||||
bool increment_reg;
|
||||
bool add_offset_reg;
|
||||
u32 offset_reg_index;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width;
|
||||
u32 reg_index;
|
||||
RegisterArithmeticType math_type;
|
||||
u32 value;
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask;
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width;
|
||||
RegisterArithmeticType math_type;
|
||||
u32 dst_reg_index;
|
||||
u32 src_reg_1_index;
|
||||
u32 src_reg_2_index;
|
||||
bool has_immediate;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width;
|
||||
u32 str_reg_index;
|
||||
u32 addr_reg_index;
|
||||
bool increment_reg;
|
||||
StoreRegisterOffsetType ofs_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width;
|
||||
ConditionalComparisonType cond_type;
|
||||
u32 val_reg_index;
|
||||
CompareRegisterValueType comp_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 addr_reg_index;
|
||||
u32 other_reg_index;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index;
|
||||
u32 src_index;
|
||||
SaveRestoreRegisterOpType op_type;
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type;
|
||||
bool should_operate[0x10];
|
||||
};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width;
|
||||
u32 log_id;
|
||||
DebugLogValueType val_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 addr_reg_index;
|
||||
u32 val_reg_index;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
CheatVmOpcodeType opcode;
|
||||
bool begin_conditional_block;
|
||||
union {
|
||||
StoreStaticOpcode store_static;
|
||||
BeginConditionalOpcode begin_cond;
|
||||
EndConditionalOpcode end_cond;
|
||||
ControlLoopOpcode ctrl_loop;
|
||||
LoadRegisterStaticOpcode ldr_static;
|
||||
LoadRegisterMemoryOpcode ldr_memory;
|
||||
StoreStaticToAddressOpcode str_static;
|
||||
PerformArithmeticStaticOpcode perform_math_static;
|
||||
BeginKeypressConditionalOpcode begin_keypress_cond;
|
||||
PerformArithmeticRegisterOpcode perform_math_reg;
|
||||
StoreRegisterToAddressOpcode str_register;
|
||||
BeginRegisterConditionalOpcode begin_reg_cond;
|
||||
SaveRestoreRegisterOpcode save_restore_reg;
|
||||
SaveRestoreRegisterMaskOpcode save_restore_regmask;
|
||||
DebugLogOpcode debug_log;
|
||||
};
|
||||
};
|
||||
|
||||
class CheatVirtualMachine {
|
||||
public:
|
||||
constexpr static size_t MaximumProgramOpcodeCount = 0x400;
|
||||
constexpr static size_t NumRegisters = 0x10;
|
||||
private:
|
||||
size_t num_opcodes = 0;
|
||||
size_t instruction_ptr = 0;
|
||||
size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
u32 program[MaximumProgramOpcodeCount] = {0};
|
||||
u64 registers[NumRegisters] = {0};
|
||||
u64 saved_values[NumRegisters] = {0};
|
||||
size_t loop_tops[NumRegisters] = {0};
|
||||
private:
|
||||
bool DecodeNextOpcode(CheatVmOpcode *out);
|
||||
void SkipConditionalBlock();
|
||||
void ResetState();
|
||||
|
||||
/* For implementing the DebugLog opcode. */
|
||||
void DebugLog(u32 log_id, u64 value);
|
||||
|
||||
/* For debugging. These will be IFDEF'd out normally. */
|
||||
void OpenDebugLogFile();
|
||||
void CloseDebugLogFile();
|
||||
void LogToDebugFile(const char *format, ...);
|
||||
void LogOpcode(const CheatVmOpcode *opcode);
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address);
|
||||
public:
|
||||
CheatVirtualMachine() { }
|
||||
|
||||
size_t GetProgramSize() {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(const CheatEntry *cheats, size_t num_cheats);
|
||||
void Execute(const CheatProcessMetadata *metadata);
|
||||
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||
private:
|
||||
FILE *debug_log_file = NULL;
|
||||
#endif
|
||||
};
|
||||
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
#include <switch.h>
|
||||
#include "dmnt_config.hpp"
|
||||
#include "dmnt_cheat_debug_events_manager.hpp"
|
||||
|
||||
|
||||
/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */
|
||||
|
||||
static HosThread g_per_core_threads[DmntCheatDebugEventsManager::NumCores];
|
||||
static HosMessageQueue *g_per_core_queues[DmntCheatDebugEventsManager::NumCores];
|
||||
static HosSignal g_continued_signal;
|
||||
|
||||
void DmntCheatDebugEventsManager::PerCoreThreadFunc(void *arg) {
|
||||
/* This thread will simply wait on the appropriate message queue. */
|
||||
size_t current_core = reinterpret_cast<size_t>(arg);
|
||||
while (true) {
|
||||
Handle debug_handle = 0;
|
||||
/* Get the debug handle. */
|
||||
{
|
||||
uintptr_t x = 0;
|
||||
g_per_core_queues[current_core]->Receive(&x);
|
||||
debug_handle = static_cast<Handle>(x);
|
||||
}
|
||||
|
||||
/* Continue the process, if needed. */
|
||||
if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_300)) {
|
||||
svcContinueDebugEvent(debug_handle, 5, nullptr, 0);
|
||||
} else {
|
||||
svcLegacyContinueDebugEvent(debug_handle, 5, 0);
|
||||
}
|
||||
|
||||
g_continued_signal.Signal();
|
||||
}
|
||||
}
|
||||
|
||||
void DmntCheatDebugEventsManager::ContinueCheatProcess(Handle cheat_dbg_hnd) {
|
||||
/* Loop getting debug events. */
|
||||
DebugEventInfo dbg_event;
|
||||
while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&dbg_event, cheat_dbg_hnd))) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
size_t target_core = DmntCheatDebugEventsManager::NumCores - 1;
|
||||
/* Retrieve correct core for new thread event. */
|
||||
if (dbg_event.type == DebugEventType::AttachThread) {
|
||||
u64 out64;
|
||||
u32 out32;
|
||||
R_ASSERT(svcGetDebugThreadParam(&out64, &out32, cheat_dbg_hnd, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore));
|
||||
target_core = out32;
|
||||
}
|
||||
|
||||
/* Make appropriate thread continue. */
|
||||
g_per_core_queues[target_core]->Send(static_cast<uintptr_t>(cheat_dbg_hnd));
|
||||
|
||||
/* Wait. */
|
||||
g_continued_signal.Wait();
|
||||
g_continued_signal.Reset();
|
||||
}
|
||||
|
||||
void DmntCheatDebugEventsManager::Initialize() {
|
||||
/* Spawn per core resources. */
|
||||
for (size_t i = 0; i < DmntCheatDebugEventsManager::NumCores; i++) {
|
||||
/* Create queue. */
|
||||
g_per_core_queues[i] = new HosMessageQueue(1);
|
||||
|
||||
/* Create thread. */
|
||||
if (R_FAILED(g_per_core_threads[i].Initialize(&DmntCheatDebugEventsManager::PerCoreThreadFunc, reinterpret_cast<void *>(i), 0x1000, 24, i))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Set core mask. */
|
||||
if (R_FAILED(svcSetThreadCoreMask(g_per_core_threads[i].GetHandle(), i, (1u << i)))) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
/* Start thread. */
|
||||
if (R_FAILED(g_per_core_threads[i].Start())) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_cheat_types.hpp"
|
||||
|
||||
struct StackFrame {
|
||||
u64 fp;
|
||||
u64 lr;
|
||||
};
|
||||
|
||||
struct AttachProcessInfo {
|
||||
u64 title_id;
|
||||
u64 process_id;
|
||||
char name[0xC];
|
||||
u32 flags;
|
||||
u64 user_exception_context_address; /* 5.0.0+ */
|
||||
};
|
||||
|
||||
struct AttachThreadInfo {
|
||||
u64 thread_id;
|
||||
u64 tls_address;
|
||||
u64 entrypoint;
|
||||
};
|
||||
|
||||
/* TODO: ExitProcessInfo */
|
||||
/* TODO: ExitThreadInfo */
|
||||
|
||||
enum class DebugExceptionType : u32 {
|
||||
UndefinedInstruction = 0,
|
||||
InstructionAbort = 1,
|
||||
DataAbort = 2,
|
||||
AlignmentFault = 3,
|
||||
DebuggerAttached = 4,
|
||||
BreakPoint = 5,
|
||||
UserBreak = 6,
|
||||
DebuggerBreak = 7,
|
||||
BadSvc = 8,
|
||||
UnknownNine = 9,
|
||||
};
|
||||
|
||||
struct UndefinedInstructionInfo {
|
||||
u32 insn;
|
||||
};
|
||||
|
||||
struct DataAbortInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct AlignmentFaultInfo {
|
||||
u64 address;
|
||||
};
|
||||
|
||||
struct UserBreakInfo {
|
||||
u64 break_reason;
|
||||
u64 address;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct BadSvcInfo {
|
||||
u32 id;
|
||||
};
|
||||
|
||||
union SpecificExceptionInfo {
|
||||
UndefinedInstructionInfo undefined_instruction;
|
||||
DataAbortInfo data_abort;
|
||||
AlignmentFaultInfo alignment_fault;
|
||||
UserBreakInfo user_break;
|
||||
BadSvcInfo bad_svc;
|
||||
u64 raw;
|
||||
};
|
||||
|
||||
struct ExceptionInfo {
|
||||
DebugExceptionType type;
|
||||
u64 address;
|
||||
SpecificExceptionInfo specific;
|
||||
};
|
||||
|
||||
|
||||
enum class DebugEventType : u32 {
|
||||
AttachProcess = 0,
|
||||
AttachThread = 1,
|
||||
ExitProcess = 2,
|
||||
ExitThread = 3,
|
||||
Exception = 4
|
||||
};
|
||||
|
||||
union DebugInfo {
|
||||
AttachProcessInfo attach_process;
|
||||
AttachThreadInfo attach_thread;
|
||||
ExceptionInfo exception;
|
||||
};
|
||||
|
||||
struct DebugEventInfo {
|
||||
DebugEventType type;
|
||||
u32 flags;
|
||||
u64 thread_id;
|
||||
union {
|
||||
DebugInfo info;
|
||||
u64 _[0x40/sizeof(u64)];
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!");
|
||||
|
||||
class DmntCheatDebugEventsManager {
|
||||
public:
|
||||
static constexpr size_t NumCores = 4;
|
||||
private:
|
||||
static void PerCoreThreadFunc(void *arg);
|
||||
public:
|
||||
static void ContinueCheatProcess(Handle cheat_dbg_hnd);
|
||||
|
||||
static void Initialize();
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_cheat_types.hpp"
|
||||
|
||||
class DmntCheatManager {
|
||||
public:
|
||||
static constexpr size_t MaxCheatCount = 0x80;
|
||||
static constexpr size_t MaxFrozenAddressCount = 0x80;
|
||||
private:
|
||||
static Handle PrepareDebugNextApplication();
|
||||
static void OnNewApplicationLaunch();
|
||||
static void DetectThread(void *arg);
|
||||
static void VmThread(void *arg);
|
||||
static void DebugEventsThread(void *arg);
|
||||
|
||||
static void StartDebugEventsThread();
|
||||
static void WaitDebugEventsThread();
|
||||
|
||||
static bool HasActiveCheatProcess();
|
||||
static void CloseActiveCheatProcess();
|
||||
static void ContinueCheatProcess();
|
||||
|
||||
static void ResetCheatEntry(size_t i);
|
||||
static void ResetAllCheatEntries();
|
||||
static CheatEntry *GetFreeCheatEntry();
|
||||
static CheatEntry *GetCheatEntryById(size_t i);
|
||||
static CheatEntry *GetCheatEntryByReadableName(const char *readable_name);
|
||||
static bool ParseCheats(const char *cht_txt, size_t len);
|
||||
static bool LoadCheats(u64 title_id, const u8 *build_id);
|
||||
|
||||
static bool ParseCheatToggles(const char *s, size_t len);
|
||||
static bool LoadCheatToggles(u64 title_id);
|
||||
static void SaveCheatToggles(u64 title_id);
|
||||
|
||||
static void ResetFrozenAddresses();
|
||||
public:
|
||||
static bool GetHasActiveCheatProcess();
|
||||
static Handle GetCheatProcessEventHandle();
|
||||
static Result GetCheatProcessMetadata(CheatProcessMetadata *out);
|
||||
static Result ForceOpenCheatProcess();
|
||||
|
||||
static Result ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size);
|
||||
static Result WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size);
|
||||
|
||||
static Result GetCheatProcessMappingCount(u64 *out_count);
|
||||
static Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset);
|
||||
static Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size);
|
||||
static Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size);
|
||||
static Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address);
|
||||
|
||||
static Result GetCheatCount(u64 *out_count);
|
||||
static Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset);
|
||||
static Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id);
|
||||
static Result ToggleCheat(u32 cheat_id);
|
||||
static Result AddCheat(u32 *out_id, CheatDefinition *def, bool enabled);
|
||||
static Result RemoveCheat(u32 cheat_id);
|
||||
|
||||
static Result GetFrozenAddressCount(u64 *out_count);
|
||||
static Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset);
|
||||
static Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address);
|
||||
static Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width);
|
||||
static Result DisableFrozenAddress(u64 address);
|
||||
|
||||
static void InitializeCheatManager();
|
||||
};
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "dmnt_cheat_service.hpp"
|
||||
#include "dmnt_cheat_manager.hpp"
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* ==================================== Meta Commands ==================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
void DmntCheatService::HasCheatProcess(Out<bool> out) {
|
||||
out.SetValue(DmntCheatManager::GetHasActiveCheatProcess());
|
||||
}
|
||||
|
||||
void DmntCheatService::GetCheatProcessEvent(Out<CopiedHandle> out_event) {
|
||||
out_event.SetValue(DmntCheatManager::GetCheatProcessEventHandle());
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
|
||||
return DmntCheatManager::GetCheatProcessMetadata(out_metadata.GetPointer());
|
||||
}
|
||||
|
||||
Result DmntCheatService::ForceOpenCheatProcess() {
|
||||
if (R_FAILED(DmntCheatManager::ForceOpenCheatProcess())) {
|
||||
return ResultDmntCheatNotAttached;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Memory Commands =================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result DmntCheatService::GetCheatProcessMappingCount(Out<u64> out_count) {
|
||||
return DmntCheatManager::GetCheatProcessMappingCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset) {
|
||||
if (mappings.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return DmntCheatManager::GetCheatProcessMappings(mappings.buffer, mappings.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result DmntCheatService::ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size) {
|
||||
if (buffer.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
u64 sz = out_size;
|
||||
if (buffer.num_elements < sz) {
|
||||
sz = buffer.num_elements;
|
||||
}
|
||||
|
||||
return DmntCheatManager::ReadCheatProcessMemory(address, buffer.buffer, sz);
|
||||
}
|
||||
|
||||
Result DmntCheatService::WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size) {
|
||||
if (buffer.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
u64 sz = in_size;
|
||||
if (buffer.num_elements < sz) {
|
||||
sz = buffer.num_elements;
|
||||
}
|
||||
|
||||
return DmntCheatManager::WriteCheatProcessMemory(address, buffer.buffer, sz);
|
||||
}
|
||||
|
||||
Result DmntCheatService::QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address) {
|
||||
return DmntCheatManager::QueryCheatProcessMemory(mapping.GetPointer(), address);
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Cheat Commands ==================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result DmntCheatService::GetCheatCount(Out<u64> out_count) {
|
||||
return DmntCheatManager::GetCheatCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset) {
|
||||
if (cheats.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return DmntCheatManager::GetCheats(cheats.buffer, cheats.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id) {
|
||||
if (cheat.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
if (cheat.num_elements < 1) {
|
||||
return ResultDmntCheatInvalidBuffer;
|
||||
}
|
||||
|
||||
return DmntCheatManager::GetCheatById(cheat.buffer, cheat_id);
|
||||
}
|
||||
|
||||
Result DmntCheatService::ToggleCheat(u32 cheat_id) {
|
||||
return DmntCheatManager::ToggleCheat(cheat_id);
|
||||
}
|
||||
|
||||
Result DmntCheatService::AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled) {
|
||||
if (cheat.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
if (cheat.num_elements < 1) {
|
||||
return ResultDmntCheatInvalidBuffer;
|
||||
}
|
||||
|
||||
return DmntCheatManager::AddCheat(out_cheat_id.GetPointer(), cheat.buffer, enabled);
|
||||
}
|
||||
|
||||
Result DmntCheatService::RemoveCheat(u32 cheat_id) {
|
||||
return DmntCheatManager::RemoveCheat(cheat_id);
|
||||
}
|
||||
|
||||
/* ========================================================================================= */
|
||||
/* =================================== Address Commands ================================== */
|
||||
/* ========================================================================================= */
|
||||
|
||||
Result DmntCheatService::GetFrozenAddressCount(Out<u64> out_count) {
|
||||
return DmntCheatManager::GetFrozenAddressCount(out_count.GetPointer());
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetFrozenAddresses(OutBuffer<FrozenAddressEntry> frz_addrs, Out<u64> out_count, u64 offset) {
|
||||
if (frz_addrs.buffer == nullptr) {
|
||||
return ResultDmntCheatNullBuffer;
|
||||
}
|
||||
|
||||
return DmntCheatManager::GetFrozenAddresses(frz_addrs.buffer, frz_addrs.num_elements, out_count.GetPointer(), offset);
|
||||
}
|
||||
|
||||
Result DmntCheatService::GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address) {
|
||||
return DmntCheatManager::GetFrozenAddress(entry.GetPointer(), address);
|
||||
}
|
||||
|
||||
Result DmntCheatService::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
|
||||
switch (width) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
break;
|
||||
default:
|
||||
return ResultDmntCheatInvalidFreezeWidth;
|
||||
}
|
||||
|
||||
return DmntCheatManager::EnableFrozenAddress(out_value.GetPointer(), address, width);
|
||||
}
|
||||
|
||||
Result DmntCheatService::DisableFrozenAddress(u64 address) {
|
||||
return DmntCheatManager::DisableFrozenAddress(address);
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_cheat_types.hpp"
|
||||
|
||||
class DmntCheatService final : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
/* Meta */
|
||||
HasCheatProcess = 65000,
|
||||
GetCheatProcessEvent = 65001,
|
||||
GetCheatProcessMetadata = 65002,
|
||||
ForceOpenCheatProcess = 65003,
|
||||
|
||||
/* Interact with Memory */
|
||||
GetCheatProcessMappingCount = 65100,
|
||||
GetCheatProcessMappings = 65101,
|
||||
ReadCheatProcessMemory = 65102,
|
||||
WriteCheatProcessMemory = 65103,
|
||||
QueryCheatProcessMemory = 65104,
|
||||
|
||||
/* Interact with Cheats */
|
||||
GetCheatCount = 65200,
|
||||
GetCheats = 65201,
|
||||
GetCheatById = 65202,
|
||||
ToggleCheat = 65203,
|
||||
AddCheat = 65204,
|
||||
RemoveCheat = 65205,
|
||||
|
||||
/* Interact with Frozen Addresses */
|
||||
GetFrozenAddressCount = 65300,
|
||||
GetFrozenAddresses = 65301,
|
||||
GetFrozenAddress = 65302,
|
||||
EnableFrozenAddress = 65303,
|
||||
DisableFrozenAddress = 65304,
|
||||
};
|
||||
private:
|
||||
void HasCheatProcess(Out<bool> out);
|
||||
void GetCheatProcessEvent(Out<CopiedHandle> out_event);
|
||||
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
|
||||
Result ForceOpenCheatProcess();
|
||||
|
||||
Result GetCheatProcessMappingCount(Out<u64> out_count);
|
||||
Result GetCheatProcessMappings(OutBuffer<MemoryInfo> mappings, Out<u64> out_count, u64 offset);
|
||||
Result ReadCheatProcessMemory(OutBuffer<u8> buffer, u64 address, u64 out_size);
|
||||
Result WriteCheatProcessMemory(InBuffer<u8> buffer, u64 address, u64 in_size);
|
||||
Result QueryCheatProcessMemory(Out<MemoryInfo> mapping, u64 address);
|
||||
|
||||
Result GetCheatCount(Out<u64> out_count);
|
||||
Result GetCheats(OutBuffer<CheatEntry> cheats, Out<u64> out_count, u64 offset);
|
||||
Result GetCheatById(OutBuffer<CheatEntry> cheat, u32 cheat_id);
|
||||
Result ToggleCheat(u32 cheat_id);
|
||||
Result AddCheat(InBuffer<CheatDefinition> cheat, Out<u32> out_cheat_id, bool enabled);
|
||||
Result RemoveCheat(u32 cheat_id);
|
||||
|
||||
Result GetFrozenAddressCount(Out<u64> out_count);
|
||||
Result GetFrozenAddresses(OutBuffer<FrozenAddressEntry> addresses, Out<u64> out_count, u64 offset);
|
||||
Result GetFrozenAddress(Out<FrozenAddressEntry> entry, u64 address);
|
||||
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
|
||||
Result DisableFrozenAddress(u64 address);
|
||||
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, HasCheatProcess),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessEvent),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMetadata),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, ForceOpenCheatProcess),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMappingCount),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMappings),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, ReadCheatProcessMemory),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, WriteCheatProcessMemory),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, QueryCheatProcessMemory),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatCount),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheats),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatById),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, ToggleCheat),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, AddCheat),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, RemoveCheat),
|
||||
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddressCount),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddresses),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddress),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, EnableFrozenAddress),
|
||||
MAKE_SERVICE_COMMAND_META(DmntCheatService, DisableFrozenAddress),
|
||||
};
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
struct MemoryRegionExtents {
|
||||
u64 base;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
u64 process_id;
|
||||
u64 title_id;
|
||||
MemoryRegionExtents main_nso_extents;
|
||||
MemoryRegionExtents heap_extents;
|
||||
MemoryRegionExtents alias_extents;
|
||||
MemoryRegionExtents address_space_extents;
|
||||
u8 main_nso_build_id[0x20];
|
||||
};
|
||||
|
||||
struct CheatDefinition {
|
||||
char readable_name[0x40];
|
||||
uint32_t num_opcodes;
|
||||
uint32_t opcodes[0x100];
|
||||
};
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled;
|
||||
uint32_t cheat_id;
|
||||
CheatDefinition definition;
|
||||
};
|
||||
|
||||
struct FrozenAddressValue {
|
||||
u64 value;
|
||||
u8 width;
|
||||
};
|
||||
|
||||
struct FrozenAddressEntry {
|
||||
u64 address;
|
||||
FrozenAddressValue value;
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,304 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stdarg.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_cheat_types.hpp"
|
||||
|
||||
enum CheatVmOpcodeType : u32 {
|
||||
CheatVmOpcodeType_StoreStatic = 0,
|
||||
CheatVmOpcodeType_BeginConditionalBlock = 1,
|
||||
CheatVmOpcodeType_EndConditionalBlock = 2,
|
||||
CheatVmOpcodeType_ControlLoop = 3,
|
||||
CheatVmOpcodeType_LoadRegisterStatic = 4,
|
||||
CheatVmOpcodeType_LoadRegisterMemory = 5,
|
||||
CheatVmOpcodeType_StoreStaticToAddress = 6,
|
||||
CheatVmOpcodeType_PerformArithmeticStatic = 7,
|
||||
CheatVmOpcodeType_BeginKeypressConditionalBlock = 8,
|
||||
|
||||
/* These are not implemented by Gateway's VM. */
|
||||
CheatVmOpcodeType_PerformArithmeticRegister = 9,
|
||||
CheatVmOpcodeType_StoreRegisterToAddress = 10,
|
||||
CheatVmOpcodeType_Reserved11 = 11,
|
||||
|
||||
/* This is a meta entry, and not a real opcode. */
|
||||
/* This is to facilitate multi-nybble instruction decoding. */
|
||||
CheatVmOpcodeType_ExtendedWidth = 12,
|
||||
|
||||
/* Extended width opcodes. */
|
||||
CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0,
|
||||
CheatVmOpcodeType_SaveRestoreRegister = 0xC1,
|
||||
CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2,
|
||||
|
||||
/* This is a meta entry, and not a real opcode. */
|
||||
/* This is to facilitate multi-nybble instruction decoding. */
|
||||
CheatVmOpcodeType_DoubleExtendedWidth = 0xF0,
|
||||
|
||||
/* Double-extended width opcodes. */
|
||||
CheatVmOpcodeType_DebugLog = 0xFFF,
|
||||
};
|
||||
|
||||
enum MemoryAccessType : u32 {
|
||||
MemoryAccessType_MainNso = 0,
|
||||
MemoryAccessType_Heap = 1,
|
||||
};
|
||||
|
||||
enum ConditionalComparisonType : u32 {
|
||||
ConditionalComparisonType_GT = 1,
|
||||
ConditionalComparisonType_GE = 2,
|
||||
ConditionalComparisonType_LT = 3,
|
||||
ConditionalComparisonType_LE = 4,
|
||||
ConditionalComparisonType_EQ = 5,
|
||||
ConditionalComparisonType_NE = 6,
|
||||
};
|
||||
|
||||
enum RegisterArithmeticType : u32 {
|
||||
RegisterArithmeticType_Addition = 0,
|
||||
RegisterArithmeticType_Subtraction = 1,
|
||||
RegisterArithmeticType_Multiplication = 2,
|
||||
RegisterArithmeticType_LeftShift = 3,
|
||||
RegisterArithmeticType_RightShift = 4,
|
||||
|
||||
/* These are not supported by Gateway's VM. */
|
||||
RegisterArithmeticType_LogicalAnd = 5,
|
||||
RegisterArithmeticType_LogicalOr = 6,
|
||||
RegisterArithmeticType_LogicalNot = 7,
|
||||
RegisterArithmeticType_LogicalXor = 8,
|
||||
|
||||
RegisterArithmeticType_None = 9,
|
||||
};
|
||||
|
||||
enum StoreRegisterOffsetType : u32 {
|
||||
StoreRegisterOffsetType_None = 0,
|
||||
StoreRegisterOffsetType_Reg = 1,
|
||||
StoreRegisterOffsetType_Imm = 2,
|
||||
StoreRegisterOffsetType_MemReg = 3,
|
||||
StoreRegisterOffsetType_MemImm = 4,
|
||||
StoreRegisterOffsetType_MemImmReg = 5,
|
||||
};
|
||||
|
||||
enum CompareRegisterValueType : u32 {
|
||||
CompareRegisterValueType_MemoryRelAddr = 0,
|
||||
CompareRegisterValueType_MemoryOfsReg = 1,
|
||||
CompareRegisterValueType_RegisterRelAddr = 2,
|
||||
CompareRegisterValueType_RegisterOfsReg = 3,
|
||||
CompareRegisterValueType_StaticValue = 4,
|
||||
CompareRegisterValueType_OtherRegister = 5,
|
||||
};
|
||||
|
||||
enum SaveRestoreRegisterOpType : u32 {
|
||||
SaveRestoreRegisterOpType_Restore = 0,
|
||||
SaveRestoreRegisterOpType_Save = 1,
|
||||
SaveRestoreRegisterOpType_ClearSaved = 2,
|
||||
SaveRestoreRegisterOpType_ClearRegs = 3,
|
||||
};
|
||||
|
||||
enum DebugLogValueType : u32 {
|
||||
DebugLogValueType_MemoryRelAddr = 0,
|
||||
DebugLogValueType_MemoryOfsReg = 1,
|
||||
DebugLogValueType_RegisterRelAddr = 2,
|
||||
DebugLogValueType_RegisterOfsReg = 3,
|
||||
DebugLogValueType_RegisterValue = 4,
|
||||
};
|
||||
|
||||
union VmInt {
|
||||
u8 bit8;
|
||||
u16 bit16;
|
||||
u32 bit32;
|
||||
u64 bit64;
|
||||
};
|
||||
|
||||
struct StoreStaticOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
u32 offset_register;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct BeginConditionalOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
ConditionalComparisonType cond_type;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct EndConditionalOpcode {};
|
||||
|
||||
struct ControlLoopOpcode {
|
||||
bool start_loop;
|
||||
u32 reg_index;
|
||||
u32 num_iters;
|
||||
};
|
||||
|
||||
struct LoadRegisterStaticOpcode {
|
||||
u32 reg_index;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct LoadRegisterMemoryOpcode {
|
||||
u32 bit_width;
|
||||
MemoryAccessType mem_type;
|
||||
u32 reg_index;
|
||||
bool load_from_reg;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct StoreStaticToAddressOpcode {
|
||||
u32 bit_width;
|
||||
u32 reg_index;
|
||||
bool increment_reg;
|
||||
bool add_offset_reg;
|
||||
u32 offset_reg_index;
|
||||
u64 value;
|
||||
};
|
||||
|
||||
struct PerformArithmeticStaticOpcode {
|
||||
u32 bit_width;
|
||||
u32 reg_index;
|
||||
RegisterArithmeticType math_type;
|
||||
u32 value;
|
||||
};
|
||||
|
||||
struct BeginKeypressConditionalOpcode {
|
||||
u32 key_mask;
|
||||
};
|
||||
|
||||
struct PerformArithmeticRegisterOpcode {
|
||||
u32 bit_width;
|
||||
RegisterArithmeticType math_type;
|
||||
u32 dst_reg_index;
|
||||
u32 src_reg_1_index;
|
||||
u32 src_reg_2_index;
|
||||
bool has_immediate;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct StoreRegisterToAddressOpcode {
|
||||
u32 bit_width;
|
||||
u32 str_reg_index;
|
||||
u32 addr_reg_index;
|
||||
bool increment_reg;
|
||||
StoreRegisterOffsetType ofs_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct BeginRegisterConditionalOpcode {
|
||||
u32 bit_width;
|
||||
ConditionalComparisonType cond_type;
|
||||
u32 val_reg_index;
|
||||
CompareRegisterValueType comp_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 addr_reg_index;
|
||||
u32 other_reg_index;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
VmInt value;
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterOpcode {
|
||||
u32 dst_index;
|
||||
u32 src_index;
|
||||
SaveRestoreRegisterOpType op_type;
|
||||
};
|
||||
|
||||
struct SaveRestoreRegisterMaskOpcode {
|
||||
SaveRestoreRegisterOpType op_type;
|
||||
bool should_operate[0x10];
|
||||
};
|
||||
|
||||
struct DebugLogOpcode {
|
||||
u32 bit_width;
|
||||
u32 log_id;
|
||||
DebugLogValueType val_type;
|
||||
MemoryAccessType mem_type;
|
||||
u32 addr_reg_index;
|
||||
u32 val_reg_index;
|
||||
u32 ofs_reg_index;
|
||||
u64 rel_address;
|
||||
};
|
||||
|
||||
struct CheatVmOpcode {
|
||||
CheatVmOpcodeType opcode;
|
||||
bool begin_conditional_block;
|
||||
union {
|
||||
StoreStaticOpcode store_static;
|
||||
BeginConditionalOpcode begin_cond;
|
||||
EndConditionalOpcode end_cond;
|
||||
ControlLoopOpcode ctrl_loop;
|
||||
LoadRegisterStaticOpcode ldr_static;
|
||||
LoadRegisterMemoryOpcode ldr_memory;
|
||||
StoreStaticToAddressOpcode str_static;
|
||||
PerformArithmeticStaticOpcode perform_math_static;
|
||||
BeginKeypressConditionalOpcode begin_keypress_cond;
|
||||
PerformArithmeticRegisterOpcode perform_math_reg;
|
||||
StoreRegisterToAddressOpcode str_register;
|
||||
BeginRegisterConditionalOpcode begin_reg_cond;
|
||||
SaveRestoreRegisterOpcode save_restore_reg;
|
||||
SaveRestoreRegisterMaskOpcode save_restore_regmask;
|
||||
DebugLogOpcode debug_log;
|
||||
};
|
||||
};
|
||||
|
||||
class DmntCheatVm {
|
||||
public:
|
||||
constexpr static size_t MaximumProgramOpcodeCount = 0x400;
|
||||
constexpr static size_t NumRegisters = 0x10;
|
||||
private:
|
||||
size_t num_opcodes = 0;
|
||||
size_t instruction_ptr = 0;
|
||||
size_t condition_depth = 0;
|
||||
bool decode_success = false;
|
||||
u32 program[MaximumProgramOpcodeCount] = {0};
|
||||
u64 registers[NumRegisters] = {0};
|
||||
u64 saved_values[NumRegisters] = {0};
|
||||
size_t loop_tops[NumRegisters] = {0};
|
||||
private:
|
||||
bool DecodeNextOpcode(CheatVmOpcode *out);
|
||||
void SkipConditionalBlock();
|
||||
void ResetState();
|
||||
|
||||
/* For implementing the DebugLog opcode. */
|
||||
void DebugLog(u32 log_id, u64 value);
|
||||
|
||||
/* For debugging. These will be IFDEF'd out normally. */
|
||||
void OpenDebugLogFile();
|
||||
void CloseDebugLogFile();
|
||||
void LogToDebugFile(const char *format, ...);
|
||||
void LogOpcode(const CheatVmOpcode *opcode);
|
||||
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width);
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address);
|
||||
public:
|
||||
DmntCheatVm() { }
|
||||
|
||||
size_t GetProgramSize() {
|
||||
return this->num_opcodes;
|
||||
}
|
||||
|
||||
bool LoadProgram(const CheatEntry *cheats, size_t num_cheats);
|
||||
void Execute(const CheatProcessMetadata *metadata);
|
||||
#ifdef DMNT_CHEAT_VM_DEBUG_LOG
|
||||
private:
|
||||
FILE *debug_log_file = NULL;
|
||||
#endif
|
||||
};
|
|
@ -1,157 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_hid.hpp"
|
||||
#include "dmnt_config.hpp"
|
||||
#include "ini.h"
|
||||
|
||||
/* Support variables. */
|
||||
static OverrideKey g_default_cheat_enable_key = {
|
||||
.key_combination = KEY_L,
|
||||
.override_by_default = true
|
||||
};
|
||||
|
||||
/* Static buffer for loader.ini contents at runtime. */
|
||||
static char g_config_ini_data[0x800];
|
||||
|
||||
static OverrideKey ParseOverrideKey(const char *value) {
|
||||
OverrideKey cfg;
|
||||
|
||||
/* Parse on by default. */
|
||||
if (value[0] == '!') {
|
||||
cfg.override_by_default = true;
|
||||
value++;
|
||||
} else {
|
||||
cfg.override_by_default = false;
|
||||
}
|
||||
|
||||
/* Parse key combination. */
|
||||
if (strcasecmp(value, "A") == 0) {
|
||||
cfg.key_combination = KEY_A;
|
||||
} else if (strcasecmp(value, "B") == 0) {
|
||||
cfg.key_combination = KEY_B;
|
||||
} else if (strcasecmp(value, "X") == 0) {
|
||||
cfg.key_combination = KEY_X;
|
||||
} else if (strcasecmp(value, "Y") == 0) {
|
||||
cfg.key_combination = KEY_Y;
|
||||
} else if (strcasecmp(value, "LS") == 0) {
|
||||
cfg.key_combination = KEY_LSTICK;
|
||||
} else if (strcasecmp(value, "RS") == 0) {
|
||||
cfg.key_combination = KEY_RSTICK;
|
||||
} else if (strcasecmp(value, "L") == 0) {
|
||||
cfg.key_combination = KEY_L;
|
||||
} else if (strcasecmp(value, "R") == 0) {
|
||||
cfg.key_combination = KEY_R;
|
||||
} else if (strcasecmp(value, "ZL") == 0) {
|
||||
cfg.key_combination = KEY_ZL;
|
||||
} else if (strcasecmp(value, "ZR") == 0) {
|
||||
cfg.key_combination = KEY_ZR;
|
||||
} else if (strcasecmp(value, "PLUS") == 0) {
|
||||
cfg.key_combination = KEY_PLUS;
|
||||
} else if (strcasecmp(value, "MINUS") == 0) {
|
||||
cfg.key_combination = KEY_MINUS;
|
||||
} else if (strcasecmp(value, "DLEFT") == 0) {
|
||||
cfg.key_combination = KEY_DLEFT;
|
||||
} else if (strcasecmp(value, "DUP") == 0) {
|
||||
cfg.key_combination = KEY_DUP;
|
||||
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
||||
cfg.key_combination = KEY_DRIGHT;
|
||||
} else if (strcasecmp(value, "DDOWN") == 0) {
|
||||
cfg.key_combination = KEY_DDOWN;
|
||||
} else if (strcasecmp(value, "SL") == 0) {
|
||||
cfg.key_combination = KEY_SL;
|
||||
} else if (strcasecmp(value, "SR") == 0) {
|
||||
cfg.key_combination = KEY_SR;
|
||||
} else {
|
||||
cfg.key_combination = 0;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static int DmntIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
||||
if (strcasecmp(section, "default_config") == 0) {
|
||||
if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
g_default_cheat_enable_key = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int DmntTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
||||
/* We'll output an override key when relevant. */
|
||||
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
|
||||
|
||||
if (strcasecmp(section, "override_config") == 0) {
|
||||
if (strcasecmp(name, "cheat_enable_key") == 0) {
|
||||
*user_cfg = ParseOverrideKey(value);
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DmntConfigManager::RefreshConfiguration() {
|
||||
FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r");
|
||||
if (config == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(g_config_ini_data, 0, sizeof(g_config_ini_data));
|
||||
fread(g_config_ini_data, 1, sizeof(g_config_ini_data) - 1, config);
|
||||
fclose(config);
|
||||
|
||||
ini_parse_string(g_config_ini_data, DmntIniHandler, NULL);
|
||||
}
|
||||
|
||||
OverrideKey DmntConfigManager::GetTitleCheatEnableKey(u64 tid) {
|
||||
OverrideKey cfg = g_default_cheat_enable_key;
|
||||
char path[FS_MAX_PATH+1] = {0};
|
||||
snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/config.ini", tid);
|
||||
|
||||
|
||||
FILE *config = fopen(path, "r");
|
||||
if (config != NULL) {
|
||||
ON_SCOPE_EXIT { fclose(config); };
|
||||
|
||||
/* Parse current title ini. */
|
||||
ini_parse_file(config, DmntTitleSpecificIniHandler, &cfg);
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static bool HasOverrideKey(OverrideKey *cfg) {
|
||||
u64 kDown = 0;
|
||||
bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
||||
return (cfg->override_by_default ^ keys_triggered);
|
||||
}
|
||||
|
||||
bool DmntConfigManager::HasCheatEnableButton(u64 tid) {
|
||||
/* Unconditionally refresh loader.ini contents. */
|
||||
RefreshConfiguration();
|
||||
|
||||
OverrideKey title_cfg = GetTitleCheatEnableKey(tid);
|
||||
return HasOverrideKey(&title_cfg);
|
||||
}
|
|
@ -24,9 +24,7 @@
|
|||
#include <stratosphere.hpp>
|
||||
|
||||
#include "dmnt_service.hpp"
|
||||
#include "dmnt_cheat_service.hpp"
|
||||
#include "dmnt_cheat_manager.hpp"
|
||||
#include "dmnt_config.hpp"
|
||||
#include "cheat/dmnt_cheat_service.hpp"
|
||||
|
||||
extern "C" {
|
||||
extern u32 __start__;
|
||||
|
@ -96,14 +94,6 @@ void __appExit(void) {
|
|||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
consoleDebugInit(debugDevice_SVC);
|
||||
|
||||
/* Initialize configuration manager. */
|
||||
DmntConfigManager::RefreshConfiguration();
|
||||
|
||||
/* Start cheat manager. */
|
||||
DmntCheatManager::InitializeCheatManager();
|
||||
|
||||
/* Nintendo uses four threads. Add a fifth for our cheat service. */
|
||||
static auto s_server_manager = WaitableManager(5);
|
||||
|
||||
|
@ -111,9 +101,7 @@ int main(int argc, char **argv)
|
|||
|
||||
/* TODO: Implement rest of dmnt:- in ams.tma development branch. */
|
||||
/* server_manager->AddWaitable(new ServiceServer<DebugMonitorService>("dmnt:-", 4)); */
|
||||
|
||||
|
||||
s_server_manager.AddWaitable(new ServiceServer<DmntCheatService>("dmnt:cht", 1));
|
||||
s_server_manager.AddWaitable(new ServiceServer<sts::dmnt::cheat::CheatService>("dmnt:cht", 1));
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
s_server_manager.Process();
|
||||
|
|
|
@ -18,133 +18,137 @@
|
|||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
class DebugMonitorService final : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
BreakDebugProcess = 0,
|
||||
TerminateDebugProcess = 1,
|
||||
CloseHandle = 2,
|
||||
LoadImage = 3,
|
||||
GetProcessId = 4,
|
||||
GetProcessHandle = 5,
|
||||
WaitSynchronization = 6,
|
||||
GetDebugEvent = 7,
|
||||
GetProcessModuleInfo = 8,
|
||||
GetProcessList = 9,
|
||||
GetThreadList = 10,
|
||||
GetDebugThreadContext = 11,
|
||||
ContinueDebugEvent = 12,
|
||||
ReadDebugProcessMemory = 13,
|
||||
WriteDebugProcessMemory = 14,
|
||||
SetDebugThreadContext = 15,
|
||||
GetDebugThreadParam = 16,
|
||||
InitializeThreadInfo = 17,
|
||||
SetHardwareBreakPoint = 18,
|
||||
QueryDebugProcessMemory = 19,
|
||||
GetProcessMemoryDetails = 20,
|
||||
AttachByProgramId = 21,
|
||||
AttachOnLaunch = 22,
|
||||
GetDebugMonitorProcessId = 23,
|
||||
GetJitDebugProcessList = 25,
|
||||
CreateCoreDump = 26,
|
||||
GetAllDebugThreadInfo = 27,
|
||||
TargetIO_FileOpen = 29,
|
||||
TargetIO_FileClose = 30,
|
||||
TargetIO_FileRead = 31,
|
||||
TargetIO_FileWrite = 32,
|
||||
TargetIO_FileSetAttributes = 33,
|
||||
TargetIO_FileGetInformation = 34,
|
||||
TargetIO_FileSetTime = 35,
|
||||
TargetIO_FileSetSize = 36,
|
||||
TargetIO_FileDelete = 37,
|
||||
TargetIO_FileMove = 38,
|
||||
TargetIO_DirectoryCreate = 39,
|
||||
TargetIO_DirectoryDelete = 40,
|
||||
TargetIO_DirectoryRename = 41,
|
||||
TargetIO_DirectoryGetCount = 42,
|
||||
TargetIO_DirectoryOpen = 43,
|
||||
TargetIO_DirectoryGetNext = 44,
|
||||
TargetIO_DirectoryClose = 45,
|
||||
TargetIO_GetFreeSpace = 46,
|
||||
TargetIO_GetVolumeInformation = 47,
|
||||
InitiateCoreDump = 48,
|
||||
ContinueCoreDump = 49,
|
||||
AddTTYToCoreDump = 50,
|
||||
AddImageToCoreDump = 51,
|
||||
CloseCoreDump = 52,
|
||||
CancelAttach = 53,
|
||||
};
|
||||
private:
|
||||
Result BreakDebugProcess(Handle debug_hnd);
|
||||
Result TerminateDebugProcess(Handle debug_hnd);
|
||||
Result CloseHandle(Handle debug_hnd);
|
||||
Result GetProcessId(Out<u64> out_pid, Handle hnd);
|
||||
Result GetProcessHandle(Out<Handle> out_hnd, u64 pid);
|
||||
Result WaitSynchronization(Handle hnd, u64 ns);
|
||||
namespace sts::dmnt {
|
||||
|
||||
Result TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode);
|
||||
Result TargetIO_FileClose(InBuffer<u64> hnd);
|
||||
Result TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset);
|
||||
Result TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset);
|
||||
Result TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes);
|
||||
Result TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory);
|
||||
Result TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify);
|
||||
Result TargetIO_FileSetSize(InBuffer<char> path, u64 size);
|
||||
Result TargetIO_FileDelete(InBuffer<char> path);
|
||||
Result TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, BreakDebugProcess),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TerminateDebugProcess),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseHandle),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, LoadImage),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessId),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessHandle),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, WaitSynchronization),
|
||||
//MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugEvent),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessModuleInfo),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetThreadList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadContext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueDebugEvent),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ReadDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, WriteDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetDebugThreadContext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadParam),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitializeThreadInfo),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetHardwareBreakPoint),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, QueryDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessMemoryDetails),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachByProgramId),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachOnLaunch),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugMonitorProcessId),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetJitDebugProcessList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CreateCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetAllDebugThreadInfo),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileOpen),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileClose),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileRead),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileWrite),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetAttributes),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileGetInformation),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetTime),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetSize),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileDelete),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileMove),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryCreate),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryDelete),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryRename),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetCount),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryOpen),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetNext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryClose),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetFreeSpace),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetVolumeInformation),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitiateCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddTTYToCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddImageToCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CancelAttach),
|
||||
};
|
||||
};
|
||||
class DebugMonitorService final : public IServiceObject {
|
||||
private:
|
||||
enum class CommandId {
|
||||
BreakDebugProcess = 0,
|
||||
TerminateDebugProcess = 1,
|
||||
CloseHandle = 2,
|
||||
LoadImage = 3,
|
||||
GetProcessId = 4,
|
||||
GetProcessHandle = 5,
|
||||
WaitSynchronization = 6,
|
||||
GetDebugEvent = 7,
|
||||
GetProcessModuleInfo = 8,
|
||||
GetProcessList = 9,
|
||||
GetThreadList = 10,
|
||||
GetDebugThreadContext = 11,
|
||||
ContinueDebugEvent = 12,
|
||||
ReadDebugProcessMemory = 13,
|
||||
WriteDebugProcessMemory = 14,
|
||||
SetDebugThreadContext = 15,
|
||||
GetDebugThreadParam = 16,
|
||||
InitializeThreadInfo = 17,
|
||||
SetHardwareBreakPoint = 18,
|
||||
QueryDebugProcessMemory = 19,
|
||||
GetProcessMemoryDetails = 20,
|
||||
AttachByProgramId = 21,
|
||||
AttachOnLaunch = 22,
|
||||
GetDebugMonitorProcessId = 23,
|
||||
GetJitDebugProcessList = 25,
|
||||
CreateCoreDump = 26,
|
||||
GetAllDebugThreadInfo = 27,
|
||||
TargetIO_FileOpen = 29,
|
||||
TargetIO_FileClose = 30,
|
||||
TargetIO_FileRead = 31,
|
||||
TargetIO_FileWrite = 32,
|
||||
TargetIO_FileSetAttributes = 33,
|
||||
TargetIO_FileGetInformation = 34,
|
||||
TargetIO_FileSetTime = 35,
|
||||
TargetIO_FileSetSize = 36,
|
||||
TargetIO_FileDelete = 37,
|
||||
TargetIO_FileMove = 38,
|
||||
TargetIO_DirectoryCreate = 39,
|
||||
TargetIO_DirectoryDelete = 40,
|
||||
TargetIO_DirectoryRename = 41,
|
||||
TargetIO_DirectoryGetCount = 42,
|
||||
TargetIO_DirectoryOpen = 43,
|
||||
TargetIO_DirectoryGetNext = 44,
|
||||
TargetIO_DirectoryClose = 45,
|
||||
TargetIO_GetFreeSpace = 46,
|
||||
TargetIO_GetVolumeInformation = 47,
|
||||
InitiateCoreDump = 48,
|
||||
ContinueCoreDump = 49,
|
||||
AddTTYToCoreDump = 50,
|
||||
AddImageToCoreDump = 51,
|
||||
CloseCoreDump = 52,
|
||||
CancelAttach = 53,
|
||||
};
|
||||
private:
|
||||
Result BreakDebugProcess(Handle debug_hnd);
|
||||
Result TerminateDebugProcess(Handle debug_hnd);
|
||||
Result CloseHandle(Handle debug_hnd);
|
||||
Result GetProcessId(Out<u64> out_pid, Handle hnd);
|
||||
Result GetProcessHandle(Out<Handle> out_hnd, u64 pid);
|
||||
Result WaitSynchronization(Handle hnd, u64 ns);
|
||||
|
||||
Result TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode);
|
||||
Result TargetIO_FileClose(InBuffer<u64> hnd);
|
||||
Result TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset);
|
||||
Result TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset);
|
||||
Result TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes);
|
||||
Result TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory);
|
||||
Result TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify);
|
||||
Result TargetIO_FileSetSize(InBuffer<char> path, u64 size);
|
||||
Result TargetIO_FileDelete(InBuffer<char> path);
|
||||
Result TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1);
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, BreakDebugProcess),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TerminateDebugProcess),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseHandle),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, LoadImage),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessId),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessHandle),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, WaitSynchronization),
|
||||
//MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugEvent),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessModuleInfo),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetThreadList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadContext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueDebugEvent),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ReadDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, WriteDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetDebugThreadContext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadParam),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitializeThreadInfo),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetHardwareBreakPoint),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, QueryDebugProcessMemory),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessMemoryDetails),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachByProgramId),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachOnLaunch),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugMonitorProcessId),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetJitDebugProcessList),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CreateCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetAllDebugThreadInfo),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileOpen),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileClose),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileRead),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileWrite),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetAttributes),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileGetInformation),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetTime),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetSize),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileDelete),
|
||||
MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileMove),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryCreate),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryDelete),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryRename),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetCount),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryOpen),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetNext),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryClose),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetFreeSpace),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetVolumeInformation),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitiateCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddTTYToCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddImageToCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseCoreDump),
|
||||
// MAKE_SERVICE_COMMAND_META(DebugMonitorService, CancelAttach),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -14,41 +14,44 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "dmnt_service.hpp"
|
||||
|
||||
Result DebugMonitorService::BreakDebugProcess(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcBreakDebugProcess(debug_hnd);
|
||||
}
|
||||
namespace sts::dmnt {
|
||||
|
||||
Result DebugMonitorService::TerminateDebugProcess(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcTerminateDebugProcess(debug_hnd);
|
||||
}
|
||||
Result DebugMonitorService::BreakDebugProcess(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcBreakDebugProcess(debug_hnd);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::CloseHandle(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
/* This command is, entertainingly, also pretty unsafe in general... */
|
||||
return svcCloseHandle(debug_hnd);
|
||||
}
|
||||
Result DebugMonitorService::TerminateDebugProcess(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcTerminateDebugProcess(debug_hnd);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::GetProcessId(Out<u64> out_pid, Handle hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcGetProcessId(out_pid.GetPointer(), hnd);
|
||||
}
|
||||
Result DebugMonitorService::CloseHandle(Handle debug_hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
/* This command is, entertainingly, also pretty unsafe in general... */
|
||||
return svcCloseHandle(debug_hnd);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::GetProcessHandle(Out<Handle> out_hnd, u64 pid) {
|
||||
R_TRY_CATCH(svcDebugActiveProcess(out_hnd.GetPointer(), pid)) {
|
||||
R_CATCH(ResultKernelAlreadyExists) {
|
||||
return ResultDebugAlreadyAttached;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
Result DebugMonitorService::GetProcessId(Out<u64> out_pid, Handle hnd) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcGetProcessId(out_pid.GetPointer(), hnd);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
Result DebugMonitorService::GetProcessHandle(Out<Handle> out_hnd, u64 pid) {
|
||||
R_TRY_CATCH(svcDebugActiveProcess(out_hnd.GetPointer(), pid)) {
|
||||
R_CATCH(ResultKernelAlreadyExists) {
|
||||
return ResultDebugAlreadyAttached;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DebugMonitorService::WaitSynchronization(Handle hnd, u64 ns) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcWaitSynchronizationSingle(hnd, ns);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::WaitSynchronization(Handle hnd, u64 ns) {
|
||||
/* Nintendo discards the output of this command, but we will return it. */
|
||||
return svcWaitSynchronizationSingle(hnd, ns);
|
||||
}
|
||||
|
|
|
@ -15,259 +15,266 @@
|
|||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
#include <switch.h>
|
||||
#include "dmnt_service.hpp"
|
||||
|
||||
enum TIOCreateOption : u32 {
|
||||
TIOCreateOption_CreateNew = 1,
|
||||
TIOCreateOption_CreateAlways = 2,
|
||||
TIOCreateOption_OpenExisting = 3,
|
||||
TIOCreateOption_OpenAlways = 4,
|
||||
TIOCreateOption_ResetSize = 5,
|
||||
};
|
||||
namespace sts::dmnt {
|
||||
|
||||
/* Nintendo uses actual pointers as file handles. We'll add a layer of indirection... */
|
||||
static bool g_sd_initialized = false;
|
||||
static HosMutex g_sd_lock;
|
||||
static FsFileSystem g_sd_fs;
|
||||
namespace {
|
||||
|
||||
static HosMutex g_file_handle_lock;
|
||||
static u64 g_cur_fd = 0;
|
||||
static std::unordered_map<u64, FsFile> g_file_handles;
|
||||
enum TIOCreateOption : u32 {
|
||||
TIOCreateOption_CreateNew = 1,
|
||||
TIOCreateOption_CreateAlways = 2,
|
||||
TIOCreateOption_OpenExisting = 3,
|
||||
TIOCreateOption_OpenAlways = 4,
|
||||
TIOCreateOption_ResetSize = 5,
|
||||
};
|
||||
|
||||
static Result EnsureSdInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_lock);
|
||||
if (g_sd_initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
/* Nintendo uses actual pointers as file handles. We'll add a layer of indirection... */
|
||||
bool g_sd_initialized = false;
|
||||
HosMutex g_sd_lock;
|
||||
FsFileSystem g_sd_fs;
|
||||
|
||||
R_TRY(fsMountSdcard(&g_sd_fs));
|
||||
g_sd_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
HosMutex g_file_handle_lock;
|
||||
u64 g_cur_fd;
|
||||
std::unordered_map<u64, FsFile> g_file_handles;
|
||||
|
||||
static u64 GetNewFileHandle(FsFile f) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
Result EnsureSdInitialized() {
|
||||
std::scoped_lock<HosMutex> lk(g_sd_lock);
|
||||
if (g_sd_initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u64 fd = g_cur_fd++;
|
||||
g_file_handles[fd] = f;
|
||||
return fd;
|
||||
}
|
||||
|
||||
static Result GetFileByHandle(FsFile *out, u64 handle) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
|
||||
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||
*out = g_file_handles[handle];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
|
||||
static Result CloseFileByHandle(u64 handle) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
|
||||
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||
fsFileClose(&g_file_handles[handle]);
|
||||
g_file_handles.erase(handle);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
|
||||
static void FixPath(char *dst, size_t dst_size, InBuffer<char> &path) {
|
||||
dst[dst_size - 1] = 0;
|
||||
strncpy(dst, "/", dst_size - 1);
|
||||
|
||||
const char *src = path.buffer;
|
||||
size_t src_idx = 0;
|
||||
size_t dst_idx = 1;
|
||||
while (src_idx < path.num_elements && (src[src_idx] == '/' || src[src_idx] == '\\')) {
|
||||
src_idx++;
|
||||
}
|
||||
|
||||
while (src_idx < path.num_elements && dst_idx < dst_size - 1 && src[src_idx] != 0) {
|
||||
if (src[src_idx] == '\\') {
|
||||
dst[dst_idx] = '/';
|
||||
} else {
|
||||
dst[dst_idx] = src[src_idx];
|
||||
R_TRY(fsMountSdcard(&g_sd_fs));
|
||||
g_sd_initialized = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u64 GetNewFileHandle(FsFile f) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
|
||||
u64 fd = g_cur_fd++;
|
||||
g_file_handles[fd] = f;
|
||||
return fd;
|
||||
}
|
||||
|
||||
Result GetFileByHandle(FsFile *out, u64 handle) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
|
||||
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||
*out = g_file_handles[handle];
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
|
||||
Result CloseFileByHandle(u64 handle) {
|
||||
std::scoped_lock<HosMutex> lk(g_file_handle_lock);
|
||||
|
||||
if (g_file_handles.find(handle) != g_file_handles.end()) {
|
||||
fsFileClose(&g_file_handles[handle]);
|
||||
g_file_handles.erase(handle);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultFsInvalidArgument;
|
||||
}
|
||||
|
||||
void FixPath(char *dst, size_t dst_size, InBuffer<char> &path) {
|
||||
dst[dst_size - 1] = 0;
|
||||
strncpy(dst, "/", dst_size - 1);
|
||||
|
||||
const char *src = path.buffer;
|
||||
size_t src_idx = 0;
|
||||
size_t dst_idx = 1;
|
||||
while (src_idx < path.num_elements && (src[src_idx] == '/' || src[src_idx] == '\\')) {
|
||||
src_idx++;
|
||||
}
|
||||
|
||||
while (src_idx < path.num_elements && dst_idx < dst_size - 1 && src[src_idx] != 0) {
|
||||
if (src[src_idx] == '\\') {
|
||||
dst[dst_idx] = '/';
|
||||
} else {
|
||||
dst[dst_idx] = src[src_idx];
|
||||
}
|
||||
|
||||
src_idx++;
|
||||
dst_idx++;
|
||||
}
|
||||
|
||||
if (dst_idx < dst_size) {
|
||||
dst[dst_idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
src_idx++;
|
||||
dst_idx++;
|
||||
}
|
||||
|
||||
if (dst_idx < dst_size) {
|
||||
dst[dst_idx] = 0;
|
||||
}
|
||||
}
|
||||
Result DebugMonitorService::TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode) {
|
||||
if (out_hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileOpen(OutBuffer<u64> out_hnd, InBuffer<char> path, int open_mode, u32 create_mode) {
|
||||
if (out_hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
|
||||
/* Create file as required by mode. */
|
||||
if (create_mode == TIOCreateOption_CreateAlways) {
|
||||
fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||
R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0));
|
||||
} else if (create_mode == TIOCreateOption_CreateNew) {
|
||||
R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0));
|
||||
} else if (create_mode == TIOCreateOption_OpenAlways) {
|
||||
fsFsCreateFile(&g_sd_fs, fs_path, 0, 0);
|
||||
}
|
||||
|
||||
/* Open the file, guard to prevent failure to close. */
|
||||
FsFile f;
|
||||
R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f));
|
||||
auto file_guard = SCOPE_GUARD {
|
||||
fsFileClose(&f);
|
||||
};
|
||||
|
||||
/* Set size if needed. */
|
||||
if (create_mode == TIOCreateOption_ResetSize) {
|
||||
R_TRY(fsFileSetSize(&f, 0));
|
||||
}
|
||||
|
||||
/* Cancel guard, output file handle. */
|
||||
file_guard.Cancel();
|
||||
out_hnd[0] = GetNewFileHandle(f);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY(EnsureSdInitialized());
|
||||
Result DebugMonitorService::TargetIO_FileClose(InBuffer<u64> hnd) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
}
|
||||
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
|
||||
/* Create file as required by mode. */
|
||||
if (create_mode == TIOCreateOption_CreateAlways) {
|
||||
fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||
R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0));
|
||||
} else if (create_mode == TIOCreateOption_CreateNew) {
|
||||
R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0));
|
||||
} else if (create_mode == TIOCreateOption_OpenAlways) {
|
||||
fsFsCreateFile(&g_sd_fs, fs_path, 0, 0);
|
||||
return CloseFileByHandle(hnd[0]);
|
||||
}
|
||||
|
||||
/* Open the file, guard to prevent failure to close. */
|
||||
FsFile f;
|
||||
R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f));
|
||||
auto file_guard = SCOPE_GUARD {
|
||||
fsFileClose(&f);
|
||||
};
|
||||
Result DebugMonitorService::TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
}
|
||||
|
||||
/* Set size if needed. */
|
||||
if (create_mode == TIOCreateOption_ResetSize) {
|
||||
R_TRY(fsFileSetSize(&f, 0));
|
||||
FsFile f;
|
||||
size_t read = 0;
|
||||
|
||||
R_TRY(GetFileByHandle(&f, hnd[0]));
|
||||
R_TRY(fsFileRead(&f, offset, out_data.buffer, out_data.num_elements, FS_READOPTION_NONE, &read));
|
||||
|
||||
out_read.SetValue(static_cast<u32>(read));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* Cancel guard, output file handle. */
|
||||
file_guard.Cancel();
|
||||
out_hnd[0] = GetNewFileHandle(f);
|
||||
Result DebugMonitorService::TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
FsFile f;
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileClose(InBuffer<u64> hnd) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
R_TRY(GetFileByHandle(&f, hnd[0]));
|
||||
R_TRY(fsFileWrite(&f, offset, data.buffer, data.num_elements, FS_WRITEOPTION_NONE));
|
||||
|
||||
out_written.SetValue(data.num_elements);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return CloseFileByHandle(hnd[0]);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileRead(InBuffer<u64> hnd, OutBuffer<u8, BufferType_Type1> out_data, Out<u32> out_read, u64 offset) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
Result DebugMonitorService::TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes) {
|
||||
/* I don't really know why this command exists, Horizon doesn't allow you to set any attributes. */
|
||||
/* N just returns ResultSuccess unconditionally here. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
size_t read = 0;
|
||||
Result DebugMonitorService::TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory) {
|
||||
if (out_info.num_elements != 4) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
}
|
||||
|
||||
R_TRY(GetFileByHandle(&f, hnd[0]));
|
||||
R_TRY(fsFileRead(&f, offset, out_data.buffer, out_data.num_elements, FS_READOPTION_NONE, &read));
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
out_read.SetValue(static_cast<u32>(read));
|
||||
return ResultSuccess;
|
||||
}
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileWrite(InBuffer<u64> hnd, InBuffer<u8, BufferType_Type1> data, Out<u32> out_written, u64 offset) {
|
||||
if (hnd.num_elements != 1) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
for (size_t i = 0; i < out_info.num_elements; i++) {
|
||||
out_info[i] = 0;
|
||||
}
|
||||
is_directory.SetValue(0);
|
||||
|
||||
FsFile f;
|
||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_READ, &f))) {
|
||||
ON_SCOPE_EXIT { fsFileClose(&f); };
|
||||
|
||||
/* N doesn't check this return code. */
|
||||
fsFileGetSize(&f, &out_info[0]);
|
||||
|
||||
/* TODO: N does not call fsFsGetFileTimestampRaw here, but we possibly could. */
|
||||
} else {
|
||||
FsDir dir;
|
||||
R_TRY(fsFsOpenDirectory(&g_sd_fs, fs_path, FS_DIROPEN_FILE | FS_DIROPEN_DIRECTORY, &dir));
|
||||
fsDirClose(&dir);
|
||||
is_directory.SetValue(1);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
|
||||
R_TRY(GetFileByHandle(&f, hnd[0]));
|
||||
R_TRY(fsFileWrite(&f, offset, data.buffer, data.num_elements, FS_WRITEOPTION_NONE));
|
||||
|
||||
out_written.SetValue(data.num_elements);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileSetAttributes(InBuffer<char> path, InBuffer<u8> attributes) {
|
||||
/* I don't really know why this command exists, Horizon doesn't allow you to set any attributes. */
|
||||
/* N just returns ResultSuccess unconditionally here. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileGetInformation(InBuffer<char> path, OutBuffer<u64> out_info, Out<int> is_directory) {
|
||||
if (out_info.num_elements != 4) {
|
||||
/* Serialization error. */
|
||||
return ResultKernelConnectionClosed;
|
||||
Result DebugMonitorService::TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify) {
|
||||
/* This is another function that doesn't really need to exist, because Horizon doesn't let you set anything. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY(EnsureSdInitialized());
|
||||
Result DebugMonitorService::TargetIO_FileSetSize(InBuffer<char> input, u64 size) {
|
||||
/* Why does this function take in a path and not a file handle? */
|
||||
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
/* We will try to be better than N, here. N only treats input as a path. */
|
||||
if (input.num_elements == sizeof(u64)) {
|
||||
FsFile f;
|
||||
if (R_SUCCEEDED(GetFileByHandle(&f, reinterpret_cast<u64 *>(input.buffer)[0]))) {
|
||||
return fsFileSetSize(&f, size);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < out_info.num_elements; i++) {
|
||||
out_info[i] = 0;
|
||||
}
|
||||
is_directory.SetValue(0);
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
FsFile f;
|
||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_READ, &f))) {
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), input);
|
||||
|
||||
FsFile f;
|
||||
R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_WRITE, &f));
|
||||
ON_SCOPE_EXIT { fsFileClose(&f); };
|
||||
|
||||
/* N doesn't check this return code. */
|
||||
fsFileGetSize(&f, &out_info[0]);
|
||||
|
||||
/* TODO: N does not call fsFsGetFileTimestampRaw here, but we possibly could. */
|
||||
} else {
|
||||
FsDir dir;
|
||||
R_TRY(fsFsOpenDirectory(&g_sd_fs, fs_path, FS_DIROPEN_FILE | FS_DIROPEN_DIRECTORY, &dir));
|
||||
fsDirClose(&dir);
|
||||
is_directory.SetValue(1);
|
||||
return fsFileSetSize(&f, size);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
Result DebugMonitorService::TargetIO_FileDelete(InBuffer<char> path) {
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileSetTime(InBuffer<char> path, u64 create, u64 access, u64 modify) {
|
||||
/* This is another function that doesn't really need to exist, because Horizon doesn't let you set anything. */
|
||||
return ResultSuccess;
|
||||
}
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileSetSize(InBuffer<char> input, u64 size) {
|
||||
/* Why does this function take in a path and not a file handle? */
|
||||
|
||||
/* We will try to be better than N, here. N only treats input as a path. */
|
||||
if (input.num_elements == sizeof(u64)) {
|
||||
FsFile f;
|
||||
if (R_SUCCEEDED(GetFileByHandle(&f, reinterpret_cast<u64 *>(input.buffer)[0]))) {
|
||||
return fsFileSetSize(&f, size);
|
||||
}
|
||||
return fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||
}
|
||||
|
||||
R_TRY(EnsureSdInitialized());
|
||||
Result DebugMonitorService::TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1) {
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), input);
|
||||
char fs_path0[FS_MAX_PATH];
|
||||
char fs_path1[FS_MAX_PATH];
|
||||
FixPath(fs_path0, sizeof(fs_path0), path0);
|
||||
FixPath(fs_path1, sizeof(fs_path1), path1);
|
||||
|
||||
FsFile f;
|
||||
R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_WRITE, &f));
|
||||
ON_SCOPE_EXIT { fsFileClose(&f); };
|
||||
return fsFsRenameFile(&g_sd_fs, fs_path0, fs_path1);
|
||||
}
|
||||
|
||||
return fsFileSetSize(&f, size);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileDelete(InBuffer<char> path) {
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
char fs_path[FS_MAX_PATH];
|
||||
FixPath(fs_path, sizeof(fs_path), path);
|
||||
|
||||
return fsFsDeleteFile(&g_sd_fs, fs_path);
|
||||
}
|
||||
|
||||
Result DebugMonitorService::TargetIO_FileMove(InBuffer<char> path0, InBuffer<char> path1) {
|
||||
R_TRY(EnsureSdInitialized());
|
||||
|
||||
char fs_path0[FS_MAX_PATH];
|
||||
char fs_path1[FS_MAX_PATH];
|
||||
FixPath(fs_path0, sizeof(fs_path0), path0);
|
||||
FixPath(fs_path1, sizeof(fs_path1), path1);
|
||||
|
||||
return fsFsRenameFile(&g_sd_fs, fs_path0, fs_path1);
|
||||
}
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ini.h"
|
||||
|
||||
#if !INI_USE_STACK
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#define MAX_SECTION 50
|
||||
#define MAX_NAME 50
|
||||
|
||||
/* Used by ini_parse_string() to keep track of string parsing state. */
|
||||
typedef struct {
|
||||
const char* ptr;
|
||||
size_t num_left;
|
||||
} ini_parse_string_ctx;
|
||||
|
||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||
static char* rstrip(char* s)
|
||||
{
|
||||
char* p = s + strlen(s);
|
||||
while (p > s && isspace((unsigned char)(*--p)))
|
||||
*p = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Return pointer to first non-whitespace char in given string. */
|
||||
static char* lskip(const char* s)
|
||||
{
|
||||
while (*s && isspace((unsigned char)(*s)))
|
||||
s++;
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||
or pointer to null at end of string if neither found. Inline comment must
|
||||
be prefixed by a whitespace character to register as a comment. */
|
||||
static char* find_chars_or_comment(const char* s, const char* chars)
|
||||
{
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
int was_space = 0;
|
||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||
was_space = isspace((unsigned char)(*s));
|
||||
s++;
|
||||
}
|
||||
#else
|
||||
while (*s && (!chars || !strchr(chars, *s))) {
|
||||
s++;
|
||||
}
|
||||
#endif
|
||||
return (char*)s;
|
||||
}
|
||||
|
||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
||||
{
|
||||
strncpy(dest, src, size - 1);
|
||||
dest[size - 1] = '\0';
|
||||
return dest;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user)
|
||||
{
|
||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||
#if INI_USE_STACK
|
||||
char line[INI_MAX_LINE];
|
||||
int max_line = INI_MAX_LINE;
|
||||
#else
|
||||
char* line;
|
||||
int max_line = INI_INITIAL_ALLOC;
|
||||
#endif
|
||||
#if INI_ALLOW_REALLOC
|
||||
char* new_line;
|
||||
int offset;
|
||||
#endif
|
||||
char section[MAX_SECTION] = "";
|
||||
char prev_name[MAX_NAME] = "";
|
||||
|
||||
char* start;
|
||||
char* end;
|
||||
char* name;
|
||||
char* value;
|
||||
int lineno = 0;
|
||||
int error = 0;
|
||||
|
||||
#if !INI_USE_STACK
|
||||
line = (char*)malloc(INI_INITIAL_ALLOC);
|
||||
if (!line) {
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if INI_HANDLER_LINENO
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
||||
#else
|
||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
||||
#endif
|
||||
|
||||
/* Scan through stream line by line */
|
||||
while (reader(line, max_line, stream) != NULL) {
|
||||
#if INI_ALLOW_REALLOC
|
||||
offset = strlen(line);
|
||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
||||
max_line *= 2;
|
||||
if (max_line > INI_MAX_LINE)
|
||||
max_line = INI_MAX_LINE;
|
||||
new_line = realloc(line, max_line);
|
||||
if (!new_line) {
|
||||
free(line);
|
||||
return -2;
|
||||
}
|
||||
line = new_line;
|
||||
if (reader(line + offset, max_line - offset, stream) == NULL)
|
||||
break;
|
||||
if (max_line >= INI_MAX_LINE)
|
||||
break;
|
||||
offset += strlen(line + offset);
|
||||
}
|
||||
#endif
|
||||
|
||||
lineno++;
|
||||
|
||||
start = line;
|
||||
#if INI_ALLOW_BOM
|
||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||
(unsigned char)start[1] == 0xBB &&
|
||||
(unsigned char)start[2] == 0xBF) {
|
||||
start += 3;
|
||||
}
|
||||
#endif
|
||||
start = lskip(rstrip(start));
|
||||
|
||||
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
||||
/* Start-of-line comment */
|
||||
}
|
||||
#if INI_ALLOW_MULTILINE
|
||||
else if (*prev_name && *start && start > line) {
|
||||
/* Non-blank line with leading whitespace, treat as continuation
|
||||
of previous name's value (as per Python configparser). */
|
||||
if (!HANDLER(user, section, prev_name, start) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
#endif
|
||||
else if (*start == '[') {
|
||||
/* A "[section]" line */
|
||||
end = find_chars_or_comment(start + 1, "]");
|
||||
if (*end == ']') {
|
||||
*end = '\0';
|
||||
strncpy0(section, start + 1, sizeof(section));
|
||||
*prev_name = '\0';
|
||||
}
|
||||
else if (!error) {
|
||||
/* No ']' found on section line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
else if (*start) {
|
||||
/* Not a comment, must be a name[=:]value pair */
|
||||
end = find_chars_or_comment(start, "=:");
|
||||
if (*end == '=' || *end == ':') {
|
||||
*end = '\0';
|
||||
name = rstrip(start);
|
||||
value = end + 1;
|
||||
#if INI_ALLOW_INLINE_COMMENTS
|
||||
end = find_chars_or_comment(value, NULL);
|
||||
if (*end)
|
||||
*end = '\0';
|
||||
#endif
|
||||
value = lskip(value);
|
||||
rstrip(value);
|
||||
|
||||
/* Valid name[=:]value pair found, call handler */
|
||||
strncpy0(prev_name, name, sizeof(prev_name));
|
||||
if (!HANDLER(user, section, name, value) && !error)
|
||||
error = lineno;
|
||||
}
|
||||
else if (!error) {
|
||||
/* No '=' or ':' found on name[=:]value line */
|
||||
error = lineno;
|
||||
}
|
||||
}
|
||||
|
||||
#if INI_STOP_ON_FIRST_ERROR
|
||||
if (error)
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !INI_USE_STACK
|
||||
free(line);
|
||||
#endif
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||
{
|
||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||
{
|
||||
FILE* file;
|
||||
int error;
|
||||
|
||||
file = fopen(filename, "r");
|
||||
if (!file)
|
||||
return -1;
|
||||
error = ini_parse_file(file, handler, user);
|
||||
fclose(file);
|
||||
return error;
|
||||
}
|
||||
|
||||
/* An ini_reader function to read the next line from a string buffer. This
|
||||
is the fgets() equivalent used by ini_parse_string(). */
|
||||
static char* ini_reader_string(char* str, int num, void* stream) {
|
||||
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
||||
const char* ctx_ptr = ctx->ptr;
|
||||
size_t ctx_num_left = ctx->num_left;
|
||||
char* strp = str;
|
||||
char c;
|
||||
|
||||
if (ctx_num_left == 0 || num < 2)
|
||||
return NULL;
|
||||
|
||||
while (num > 1 && ctx_num_left != 0) {
|
||||
c = *ctx_ptr++;
|
||||
ctx_num_left--;
|
||||
*strp++ = c;
|
||||
if (c == '\n')
|
||||
break;
|
||||
num--;
|
||||
}
|
||||
|
||||
*strp = '\0';
|
||||
ctx->ptr = ctx_ptr;
|
||||
ctx->num_left = ctx_num_left;
|
||||
return str;
|
||||
}
|
||||
|
||||
/* See documentation in header file. */
|
||||
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
||||
ini_parse_string_ctx ctx;
|
||||
|
||||
ctx.ptr = string;
|
||||
ctx.num_left = strlen(string);
|
||||
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
||||
user);
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/* inih -- simple .INI file parser
|
||||
|
||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||
home page for more info:
|
||||
|
||||
https://github.com/benhoyt/inih
|
||||
|
||||
*/
|
||||
|
||||
#ifndef __INI_H__
|
||||
#define __INI_H__
|
||||
|
||||
/* Make this header file easier to include in C++ code */
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
||||
#ifndef INI_HANDLER_LINENO
|
||||
#define INI_HANDLER_LINENO 0
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of handler function. */
|
||||
#if INI_HANDLER_LINENO
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value,
|
||||
int lineno);
|
||||
#else
|
||||
typedef int (*ini_handler)(void* user, const char* section,
|
||||
const char* name, const char* value);
|
||||
#endif
|
||||
|
||||
/* Typedef for prototype of fgets-style reader function. */
|
||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||
|
||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||
is "" if name=value pair parsed before any section heading. name:value
|
||||
pairs are also supported as a concession to Python's configparser.
|
||||
|
||||
For each name=value pair parsed, call handler function with given user
|
||||
pointer as well as section, name, and value (data only valid for duration
|
||||
of handler call). Handler should return nonzero on success, zero on error.
|
||||
|
||||
Returns 0 on success, line number of first error on parse error (doesn't
|
||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||
error (only when INI_USE_STACK is zero).
|
||||
*/
|
||||
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||
close the file when it's finished -- the caller must do that. */
|
||||
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||
filename. Used for implementing custom or string-based I/O (see also
|
||||
ini_parse_string). */
|
||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||
void* user);
|
||||
|
||||
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
||||
instead of a file. Useful for parsing INI data from a network socket or
|
||||
already in memory. */
|
||||
int ini_parse_string(const char* string, ini_handler handler, void* user);
|
||||
|
||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||
configparser. If allowed, ini_parse() will call the handler with the same
|
||||
name for each subsequent line parsed. */
|
||||
#ifndef INI_ALLOW_MULTILINE
|
||||
#define INI_ALLOW_MULTILINE 1
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||
#ifndef INI_ALLOW_BOM
|
||||
#define INI_ALLOW_BOM 1
|
||||
#endif
|
||||
|
||||
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
||||
both ; and # comments at the start of a line by default. */
|
||||
#ifndef INI_START_COMMENT_PREFIXES
|
||||
#define INI_START_COMMENT_PREFIXES ";#"
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||
Python 3.2+ configparser behaviour. */
|
||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||
#endif
|
||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||
#endif
|
||||
|
||||
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
||||
#ifndef INI_USE_STACK
|
||||
#define INI_USE_STACK 1
|
||||
#endif
|
||||
|
||||
/* Maximum line length for any line in INI file (stack or heap). Note that
|
||||
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
||||
#ifndef INI_MAX_LINE
|
||||
#define INI_MAX_LINE 200
|
||||
#endif
|
||||
|
||||
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
||||
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
||||
zero. */
|
||||
#ifndef INI_ALLOW_REALLOC
|
||||
#define INI_ALLOW_REALLOC 0
|
||||
#endif
|
||||
|
||||
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
||||
is zero. */
|
||||
#ifndef INI_INITIAL_ALLOC
|
||||
#define INI_INITIAL_ALLOC 200
|
||||
#endif
|
||||
|
||||
/* Stop parsing on first error (default is to keep parsing). */
|
||||
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||
#define INI_STOP_ON_FIRST_ERROR 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __INI_H__ */
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include "pm_shim.h"
|
||||
|
||||
/* Atmosphere extension commands. */
|
||||
Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *s = pmdmntGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 pid;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->pid = pid;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 title_id;
|
||||
FsStorageId storage_id;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) {
|
||||
*out = r.Handles[0];
|
||||
} else {
|
||||
svcCloseHandle(r.Handles[0]);
|
||||
}
|
||||
if (tid_out) *tid_out = resp->title_id;
|
||||
if (sid_out) *sid_out = resp->storage_id;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/**
|
||||
* @file pm_shim.h
|
||||
* @brief Process Management (pm) IPC wrapper.
|
||||
* @author SciresM
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Atmosphere extension commands. */
|
||||
Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -32,7 +32,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \
|
|||
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -flto -std=gnu++17
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
|
|
@ -17,7 +17,4 @@
|
|||
#pragma once
|
||||
#include <switch.h>
|
||||
|
||||
class HidManagement {
|
||||
public:
|
||||
static Result GetKeysDown(u64 *keys);
|
||||
};
|
||||
#include "dmnt/dmnt_cheat_types.hpp"
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ncm/ncm_types.hpp"
|
||||
|
||||
namespace sts::dmnt::cheat {
|
||||
|
||||
struct CheatProcessMetadata {
|
||||
struct MemoryRegionExtents {
|
||||
u64 base;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
u64 process_id;
|
||||
ncm::TitleId title_id;
|
||||
MemoryRegionExtents main_nso_extents;
|
||||
MemoryRegionExtents heap_extents;
|
||||
MemoryRegionExtents alias_extents;
|
||||
MemoryRegionExtents aslr_extents;
|
||||
u8 main_nso_build_id[0x20];
|
||||
};
|
||||
|
||||
static_assert(std::is_pod<CheatProcessMetadata>::value && sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata definition!");
|
||||
|
||||
struct CheatDefinition {
|
||||
char readable_name[0x40];
|
||||
uint32_t num_opcodes;
|
||||
uint32_t opcodes[0x100];
|
||||
};
|
||||
|
||||
struct CheatEntry {
|
||||
bool enabled;
|
||||
uint32_t cheat_id;
|
||||
CheatDefinition definition;
|
||||
};
|
||||
|
||||
struct FrozenAddressValue {
|
||||
u64 value;
|
||||
u8 width;
|
||||
};
|
||||
|
||||
struct FrozenAddressEntry {
|
||||
u64 address;
|
||||
FrozenAddressValue value;
|
||||
};
|
||||
|
||||
}
|
|
@ -224,24 +224,33 @@ class HosSignal {
|
|||
mutexUnlock(&m);
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
void Wait(bool reset = false) {
|
||||
mutexLock(&m);
|
||||
|
||||
while (!signaled) {
|
||||
condvarWait(&cv, &m);
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
this->signaled = false;
|
||||
}
|
||||
|
||||
mutexUnlock(&m);
|
||||
}
|
||||
|
||||
bool TryWait() {
|
||||
bool TryWait(bool reset = false) {
|
||||
mutexLock(&m);
|
||||
|
||||
bool success = signaled;
|
||||
if (reset) {
|
||||
this->signaled = false;
|
||||
}
|
||||
|
||||
mutexUnlock(&m);
|
||||
return success;
|
||||
}
|
||||
|
||||
Result TimedWait(u64 ns) {
|
||||
Result TimedWait(u64 ns, bool reset = false) {
|
||||
mutexLock(&m);
|
||||
TimeoutHelper timeout_helper(ns);
|
||||
|
||||
|
@ -251,6 +260,10 @@ class HosSignal {
|
|||
}
|
||||
}
|
||||
|
||||
if (reset) {
|
||||
this->signaled = false;
|
||||
}
|
||||
|
||||
mutexUnlock(&m);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@
|
|||
#include "pm/pm_types.hpp"
|
||||
#include "pm/pm_boot_mode_api.hpp"
|
||||
#include "pm/pm_info_api.hpp"
|
||||
#include "pm/pm_shell_api.hpp"
|
||||
#include "pm/pm_shell_api.hpp"
|
||||
#include "pm/pm_dmnt_api.hpp"
|
|
@ -14,23 +14,19 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stratosphere.hpp>
|
||||
#pragma once
|
||||
|
||||
#include "dmnt_hid.hpp"
|
||||
#include "../ldr.hpp"
|
||||
#include "pm_types.hpp"
|
||||
|
||||
static HosMutex g_hid_keys_down_lock;
|
||||
namespace sts::pm::dmnt {
|
||||
|
||||
Result HidManagement::GetKeysDown(u64 *keys) {
|
||||
std::scoped_lock<HosMutex> lk(g_hid_keys_down_lock);
|
||||
/* Debug Monitor API. */
|
||||
Result StartProcess(u64 process_id);
|
||||
Result GetProcessId(u64 *out_process_id, const ncm::TitleId title_id);
|
||||
Result GetApplicationProcessId(u64 *out_process_id);
|
||||
Result HookToCreateApplicationProcess(Handle *out_handle);
|
||||
Result AtmosphereGetProcessInfo(Handle *out_handle, ncm::TitleLocation *out_loc, u64 process_id);
|
||||
Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, LimitableResource resource);
|
||||
|
||||
hidScanInput();
|
||||
*keys = 0;
|
||||
|
||||
for (int controller = 0; controller < 10; controller++) {
|
||||
*keys |= hidKeysHeld((HidControllerID) controller);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
|
@ -94,3 +94,94 @@ Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid) {
|
|||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, u8 *sid_out, u64 pid) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *s = pmdmntGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u64 pid;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65000;
|
||||
raw->pid = pid;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 title_id;
|
||||
FsStorageId storage_id;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out) {
|
||||
*out = r.Handles[0];
|
||||
} else {
|
||||
svcCloseHandle(r.Handles[0]);
|
||||
}
|
||||
if (tid_out) *tid_out = resp->title_id;
|
||||
if (sid_out) *sid_out = resp->storage_id;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource) {
|
||||
IpcCommand c;
|
||||
ipcInitialize(&c);
|
||||
Service *s = pmdmntGetServiceSession();
|
||||
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 cmd_id;
|
||||
u32 group;
|
||||
u32 resource;
|
||||
} *raw;
|
||||
|
||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
||||
|
||||
raw->magic = SFCI_MAGIC;
|
||||
raw->cmd_id = 65001;
|
||||
raw->group = group;
|
||||
raw->resource = resource;
|
||||
|
||||
Result rc = serviceIpcDispatch(s);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
IpcParsedCommand r;
|
||||
struct {
|
||||
u64 magic;
|
||||
u64 result;
|
||||
u64 cur_value;
|
||||
u64 lim_value;
|
||||
} *resp;
|
||||
|
||||
serviceIpcParse(s, &r, sizeof(*resp));
|
||||
resp = r.Raw;
|
||||
|
||||
rc = resp->result;
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (out_cur) *out_cur = resp->cur_value;
|
||||
if (out_lim) *out_lim = resp->lim_value;
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ extern "C" {
|
|||
Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid);
|
||||
Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid);
|
||||
|
||||
Result pmdmntAtmosphereGetProcessInfo(Handle *out, u64 *tid_out, u8 *sid_out, u64 pid);
|
||||
Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
54
stratosphere/libstratosphere/source/pm/pm_dmnt_api.cpp
Normal file
54
stratosphere/libstratosphere/source/pm/pm_dmnt_api.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/pm.hpp>
|
||||
|
||||
#include "pm_ams.h"
|
||||
|
||||
namespace sts::pm::dmnt {
|
||||
|
||||
/* Debug Monitor API. */
|
||||
Result StartProcess(u64 process_id) {
|
||||
return pmdmntStartProcess(process_id);
|
||||
}
|
||||
|
||||
Result GetProcessId(u64 *out_process_id, const ncm::TitleId title_id) {
|
||||
return pmdmntGetTitlePid(out_process_id, static_cast<u64>(title_id));
|
||||
}
|
||||
|
||||
Result GetApplicationProcessId(u64 *out_process_id) {
|
||||
return pmdmntGetApplicationPid(out_process_id);
|
||||
}
|
||||
|
||||
Result HookToCreateApplicationProcess(Handle *out_handle) {
|
||||
return pmdmntEnableDebugForApplication(out_handle);
|
||||
}
|
||||
|
||||
Result AtmosphereGetProcessInfo(Handle *out_handle, ncm::TitleLocation *out_loc, u64 process_id) {
|
||||
*out_handle = INVALID_HANDLE;
|
||||
*out_loc = {};
|
||||
return pmdmntAtmosphereGetProcessInfo(out_handle, reinterpret_cast<u64 *>(&out_loc->title_id), &out_loc->storage_id, process_id);
|
||||
}
|
||||
|
||||
Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, LimitableResource resource) {
|
||||
*out_current_value = 0;
|
||||
*out_limit_value = 0;
|
||||
return pmdmntAtmosphereGetCurrentLimitInfo(out_current_value, out_limit_value, group, resource);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue